|
48261
|
NULL
|
0
|
2026-04-17T12:32:17.000772+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776429137000_m1.jpg...
|
PhpStorm
|
faVsco.js – crm_configurations [PROD]
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20692-fix-integration- Project: faVsco.js, menu
JY-20692-fix-integration-app-[API_KEY], menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
cachedStages
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
2/4
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Code changed:
Hide
Sync Changes
Hide This Notification
33
2
19
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use HubSpot\Client\Crm\Deals\Model\CollectionResponseAssociatedId;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Models\Account;
use Exception;
use Jiminny\Component\DealInsights\Forecast\Forecast;
use Jiminny\Jobs\Crm\MatchActivitiesToNewOpportunity;
use Jiminny\Models\Contact;
use Jiminny\Models\Crm\BusinessProcess;
use Jiminny\Exceptions\CrmException;
use Jiminny\Models\Opportunity;
use Illuminate\Support\Collection;
use Jiminny\Models\Stage;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Crm\Hubspot\DealFieldsService;
use Jiminny\Services\Crm\Hubspot\OpportunitySyncStrategy\HubspotSingleSyncStrategy;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Services\Crm\OpportunitySyncStrategyResolver;
use Jiminny\Utils\CurrencyFormatter;
/**
* Optimized sync methods for better performance
* These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains
*/
trait OpportunitySyncTrait
{
private const int BATCH_SIZE = 100;
private const int BATCH_PROCESS_SIZE = 800;
protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;
protected CrmEntityRepository $crmEntityRepository;
protected DealFieldsService $dealFieldsService;
private ?array $cachedClosedDealStages = null;
private array $cachedBusinessProcesses = [];
private array $cachedStages = [];
public function syncOpportunities(array $parameters, ?string $strategy = null): int
{
$strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);
$parameters['config'] = $this->config;
$syncCount = 0;
$reportedTotal = 0;
$lastSyncedId = [];
try {
foreach ($strategies as $strategyName => $syncStrategy) {
$this->logger->info(
'[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .
$strategyName
);
$total = 0;
$lastId = null;
$buffer = [];
// HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies
foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {
$buffer[] = $hsOpportunity;
// process every 800 rows (fits < 1 000 association limit)
if (\count($buffer) >= self::BATCH_PROCESS_SIZE) {
$syncCount += $this->processOpportunityBatch($buffer);
$buffer = [];
}
}
// leftovers
if ($buffer) {
$syncCount += $this->processOpportunityBatch($buffer);
}
$reportedTotal += $total;
$lastSyncedId = $lastId;
}
} catch (\HubSpot\Client\Crm\Deals\ApiException | CrmException $e) {
$this->handleSyncException($e, $parameters);
}
$this->logger->info(
'[HubSpot] Synced opportunities',
[
'team' => $this->team->getId(),
'sync_count' => $syncCount,
'total' => $reportedTotal,
'last_synced_id' => $lastSyncedId,
]
);
return $reportedTotal;
}
private function handleSyncException(\Throwable $e, array $parameters): void
{
if (($parameters['since'] ?? null) instanceof Carbon) {
$parameters['since'] = $parameters['since']->toDateTimeString();
}
$parameters['config'] = $this->config->getId();
$this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [
'teamId' => $this->team->getUuid(),
'parameters' => $parameters,
'reason' => $e->getMessage(),
]);
}
/**
* @inheritdoc
*/
public function syncOpportunity(string $crmId): ?Opportunity
{
$strategy = $this->opportunitySyncStrategyResolver->resolve(
$this->config,
OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,
);
$parameters = [
'config' => $this->config,
'crm_id' => $crmId,
];
try {
if (! $strategy instanceof HubspotSingleSyncStrategy) {
throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');
}
$hsOpportunity = $strategy->fetchOpportunity($parameters);
} catch (\HubSpot\Client\Crm\Deals\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
$hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);
return $this->importOrUpdateOpportunity($hsOpportunity);
}
/**
* Process webhook-collected opportunity batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportOpportunityBatch jobs for batch processing.
*
* @return int Number of opportunity IDs dispatched to jobs
*/
public function batchSyncOpportunities(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,
$configId
);
}
/**
* Import a batch of opportunities by their CRM IDs.
* Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().
*
* @param array<string> $crmIds HubSpot deal CRM IDs
*
* @return array{success: array, failed_ids: array, errors?: array<string, string>}
*/
public function importOpportunityBatchByIds(array $crmIds): array
{
$fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);
$allDeals = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
$deals = $this->client->getOpportunitiesByIds($chunk, $fields);
foreach ($deals as $deal) {
$allDeals[] = $deal;
}
}
// IDs not returned by HubSpot are likely deleted or inaccessible deals.
// These are not failures — retrying won't bring them back.
$fetchedIds = array_map('strval', array_column($allDeals, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allDeals),
]);
}
if (empty($allDeals)) {
return ['success' => [], 'failed_ids' => []];
}
return $this->importOpportunityBatch($allDeals);
}
private function getClosedDealStages(): array
{
if ($this->cachedClosedDealStages !== null) {
return $this->cachedClosedDealStages;
}
$stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);
$data = [
'lost' => [],
'won' => [],
];
foreach ($stages as $stage) {
if ($stage->probability == 0.00) {
$data['lost'][] = $stage->crm_provider_id;
}
if ($stage->probability == 100.00) {
$data['won'][] = $stage->crm_provider_id;
}
}
$this->cachedClosedDealStages = $data;
return $data;
}
/**
* Import deals into the database with pre-fetched associations.
*
* API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT
* caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()
* where Laravel retries the whole job with backoff. After all retries exhausted,
* failed() requeues all IDs to Redis.
*
* The per-deal loop catches exceptions individually. A deal can end up in three states:
* - success: imported/updated successfully
* - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)
* These are permanent issues — retrying won't fix them.
* - skipped (null): missing dependencies (no account, unknown pipeline/stage).
* This is acceptable — the deal cannot be imported until those exist.
*/
private function importOpportunityBatch(array $deals): array
{
$syncedOpportunities = [
'success' => [],
'failed_ids' => [],
];
$dealIds = array_column($deals, 'id');
// Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the
// queue job retries the whole batch and eventually requeues all deal IDs back to Redis.
try {
$companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');
$contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');
$associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);
$existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(
$this->config,
array_map('strval', $dealIds)
);
$existingCrmIdSet = array_flip($existingCrmIds);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [
'teamId' => $this->team->getId(),
'dealCount' => count($dealIds),
'error' => $e->getMessage(),
]);
throw $e;
}
foreach ($deals as $deal) {
try {
$deal['associations'] = $this->prepareAssociationsForOpportunity(
$deal['id'],
$companyAssociations,
$contactAssociations,
$associationsData
);
$syncedOpportunity = $this->importOrUpdateOpportunity(
$deal,
isset($existingCrmIdSet[(string) $deal['id']])
);
if ($syncedOpportunity) {
$syncedOpportunities['success'][] = $syncedOpportunity;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [
'teamId' => $this->team->getId(),
'crmId' => $deal['id'],
'error' => $e->getMessage(),
]);
$syncedOpportunities['failed_ids'][] = $deal['id'];
$syncedOpportunities['errors'][$deal['id']] = $e->getMessage();
}
}
return $syncedOpportunities;
}
/**
* Prepare associated entities for opportunities with optimized batch processing
* Returns structured data with CRM ID to DB ID mappings for each opportunity
*/
private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array
{
// Step 1: Collect all unique company and contact IDs from associations
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
// Step 2: Batch sync missing entities and get CRM ID to DB ID mappings
$companyIdMappings = [];
$contactIdMappings = [];
if (! empty($allCompanyIds)) {
$companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);
}
if (! empty($allContactIds)) {
$contactIdMappings = $this->prepareAssociatedContacts($allContactIds);
}
return [
'company_id_mappings' => $companyIdMappings,
'contact_id_mappings' => $contactIdMappings,
];
}
/**
* Flatten association data to get unique IDs
*/
private function flattenAssociationIds(array $associations): array
{
$ids = [];
foreach ($associations as $dealAssociations) {
if (is_array($dealAssociations)) {
foreach ($dealAssociations as $id) {
$ids[$id] = true;
}
}
}
return array_keys($ids);
}
/**
* Batch sync missing accounts
*/
private function prepareAssociatedAccounts(array $companyIds): array
{
// Find which accounts already exist
$existingAccounts = $this->crmEntityRepository
->findAccountsByExternalIds($this->config, $companyIds);
$existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();
$existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {
return [$account->getCrmProviderId() => $account->getId()];
})->toArray();
$missingCompanyIds = array_diff($companyIds, $existingCompanyIds);
if (empty($missingCompanyIds)) {
return $existingAccountsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [
'teamId' => $this->team->getUuid(),
'total_companies' => count($companyIds),
'existing_companies' => count($existingCompanyIds),
'missing_companies' => count($missingCompanyIds),
]);
// we already have limit on opportunity ids count
// Initialize variable before try block
$syncedAccountsData = [];
try {
$syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [
'size' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
$syncedAccountsData = [];
}
return $existingAccountsData + $syncedAccountsData;
}
/**
* Prepare associated contacts - find existing and sync missing ones
* Returns mapping of CRM ID to DB ID
*/
private function prepareAssociatedContacts(array $contactIds): array
{
// Find which contacts already exist
$existingContacts = $this->crmEntityRepository
->findContactsByExternalIds($this->config, $contactIds);
$existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();
// Create mapping for existing contacts
$existingContactsData = $existingContacts->mapWithKeys(function ($contact) {
return [$contact->getCrmProviderId() => $contact->getId()];
})->toArray();
$missingContactIds = array_diff($contactIds, $existingContactIds);
if (empty($missingContactIds)) {
return $existingContactsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [
'teamId' => $this->team->getUuid(),
'total_contacts' => count($contactIds),
'existing_contacts' => count($existingContactIds),
'missing_contacts' => count($missingContactIds),
]);
// Sync missing contacts using batch API
try {
$syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [
'size' => count($missingContactIds),
'error' => $e->getMessage(),
]);
$syncedContactsData = [];
}
return $existingContactsData + $syncedContactsData;
}
private function batchSyncCrmObjects(string $objectType, array $crmIds): array
{
$syncObjects = [];
$crmObjectIds = array_values($crmIds);
foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {
try {
$objects = $objectType === 'companies' ?
$this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :
$this->client->getContactsByIds($chunk, $this->getContactFields());
foreach ($objects as $objectId => $objectData) {
$this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [
'requested_count' => count($chunk),
'synced_count' => count($objects),
]);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [
'ids' => $chunk,
'error' => $e->getMessage(),
]);
}
}
return $syncObjects;
}
private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void
{
try {
$object = $objectType === 'companies' ?
$this->importAccount($objectData) :
$this->importContact($objectData);
if ($object) {
$syncObjects[$object->getCrmProviderId()] = $object->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [
'id' => $objectId,
'error' => $e->getMessage(),
]);
}
}
/**
* Prepare associations for a single opportunity
*
* The return value is an array with the following structure:
* [
* 'companies' => [
* $companyCrmId => $companyId,
* ...
* ],
* 'contacts' => [
* $contactCrmId => $contactId,
* ...
* ],
* 'account_id' => $accountId,
* ]
*/
private function prepareAssociationsForOpportunity(
string $oppCrmId,
array $companyAssociations,
array $contactAssociations,
array $associationsData
): array {
$associations = [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
$oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];
foreach ($oppCompanyIds as $companyCrmId) {
if (isset($associationsData['company_id_mappings'][$companyCrmId])) {
$associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];
// Set primary account (first company becomes primary account)
if ($associations['account_id'] === null) {
$associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];
}
}
}
$oppContactIds = $contactAssociations[$oppCrmId] ?? [];
foreach ($oppContactIds as $contactCrmId) {
if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {
$associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];
}
}
return $associations;
}
/**
* Update only associations for an opportunity
*/
private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void
{
// Update contact associations
$this->importOpportunityContacts($opportunity, $associations['contacts']);
// Update company (account) associations
$this->updateOpportunityAccount($opportunity, $associations['account_id']);
}
/**
* Remove all contact associations from an opportunity
*/
private function removeAllOpportunityContacts(Opportunity $opportunity): void
{
$currentCount = (int) $opportunity->contacts()->count();
if ($currentCount > 0) {
$opportunity->contacts()->detach();
$this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_count' => $currentCount,
]);
}
}
private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void
{
if ($accountId === null) {
// No account ID provided - keep current account
return;
}
$currentAccountId = $opportunity->getAccountId();
// Only update if account has changed
if ($currentAccountId !== $accountId) {
$opportunity->account_id = $accountId;
$opportunity->save();
$this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [
'opportunity_id' => $opportunity->getId(),
'old_account_id' => $currentAccountId,
'new_account_id' => $accountId,
]);
}
}
/**
* Find existing opportunities by external IDs (OPTIMIZED VERSION)
* Uses batch query for better performance
*/
private function findExistingOpportunities(array $crmIds): Collection
{
return $this->crmEntityRepository
->findOpportunitiesByExternalIds($this->config, $crmIds);
}
private function processOpportunityBatch(array $opportunities): int
{
$syncedOpportunities = $this->importOpportunityBatch($opportunities);
return count($syncedOpportunities['success'] ?? []);
}
/**
* Convert single deal associations from HubSpot format to internal format
* Handles both HubSpot SDK objects and array formats
*
* @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed
*
* @return array Processed associations with DB IDs
*/
private function convertDealAssociations(array $opportunityAssociations): array
{
$associations = $this->initializeAssociationsStructure();
if (empty($opportunityAssociations)) {
return $associations;
}
$associationIds = $this->extractAssociationIds($opportunityAssociations);
$this->processCompanyAssociations($associationIds, $associations);
$this->processContactAssociations($associationIds, $associations);
return $associations;
}
private function initializeAssociationsStructure(): array
{
return [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
}
private function extractAssociationIds(array $opportunityAssociations): array
{
$associationIds = [];
foreach ($opportunityAssociations as $type => $associationData) {
if (! empty($associationData)) {
$associationIds[$type] = $this->convertSingleDealAssociations($associationData);
}
}
return $associationIds;
}
private function processCompanyAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['companies'])) {
return;
}
$companyId = $associationIds['companies'][0];
$account = $this->findOrSyncAccount($companyId);
if ($account instanceof Account) {
$associations['companies'][$companyId] = $account->getId();
$associations['account_id'] = $account->getId();
}
}
private function processContactAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['contacts'])) {
return;
}
foreach ($associationIds['contacts'] as $contactId) {
$contact = $this->findOrSyncContact($contactId);
if ($contact instanceof Contact) {
$associations['contacts'][$contactId] = $contact->getId();
}
}
}
private function findOrSyncAccount(string $companyId): ?Account
{
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);
if (! $account instanceof Account) {
$account = $this->syncAccount($companyId);
}
return $account;
}
private function findOrSyncContact(string $contactId): ?Contact
{
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);
if (! $contact instanceof Contact) {
$contact = $this->syncContact($contactId);
}
return $contact;
}
private function convertSingleDealAssociations($opportunityAssociations = null): array
{
$associationData = [];
if ($opportunityAssociations === null) {
return $associationData;
}
// Handle array input (from extractAssociationIds)
if (is_array($opportunityAssociations)) {
return $opportunityAssociations;
}
// Handle CollectionResponseAssociatedId object
if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {
foreach ($opportunityAssociations->getResults() as $association) {
$associationData[] = $association->getId();
}
}
return $associationData;
}
private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity
{
if (empty($crmData['properties'])) {
return null;
}
$crmId = (string) $crmData['id'];
$properties = $crmData['properties'];
$associations = $crmData['associations'] ?? [];
$opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(
$this->config,
$crmId
);
if ($opportunityExists) {
return $this->updateOpportunity($crmId, $properties, $associations);
} else {
return $this->createOpportunity($crmId, $properties, $associations);
}
}
/**
* Create new opportunity
*/
private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity
{
$accountId = $this->resolveAccountId($associations);
if (! $accountId) {
return null;
}
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
if (! $businessProcess) {
return null;
}
$stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);
if (! $stage) {
return null;
}
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->importOpportunityContacts($opportunity, $associations['contacts']);
if ($opportunity->wasRecentlyCreated) {
MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());
}
return $opportunity;
}
/**
* Update existing opportunity
*/
private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity
{
$accountId = $this->resolveAccountId($associations);
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
$stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->updateOpportunityAssociations($opportunity, $associations);
return $opportunity;
}
private function resolveAccountId(array $associations): ?int
{
if (! empty($associations['accountId'])) {
return $associations['accountId'];
}
if (empty($associations)) {
return null;
}
// we can't resolve multiple account ids (currently SDK returns one company)
foreach ($associations['companies'] as $accountId) {
return $accountId;
}
return null;
}
private function buildOpportunityData(
array $properties,
?int $accountId,
?BusinessProcess $businessProcess,
?Stage $stage
): array {
$ownerId = null;
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$name = 'Unknown';
if (isset($properties['dealname'])) {
$name = mb_strimwidth($properties['dealname'], 0, 128);
}
$amount = $this->resolveAmount($properties);
$currency = $properties['deal_currency_code'] ?? null;
$closeDate = null;
if (! empty($properties['closedate'])) {
$closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');
}
$remotelyCreatedAt = null;
if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {
$date = $this->parseCleanDatetime($properties['createdate']);
$remotelyCreatedAt = $date?->format('Y-m-d H:i:s');
}
$closedStages = $this->getClosedDealStages();
$isWon = in_array($properties['dealstage'], $closedStages['won']);
$isLost = in_array($properties['dealstage'], $closedStages['lost']);
$data = [
'team_id' => $this->team->getId(),
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => $name,
'value' => ! empty($amount) ? $amount : null,
'currency_code' => CurrencyFormatter::formatCode($currency),
'close_date' => $closeDate,
'is_closed' => $isWon || $isLost,
'is_won' => $isWon,
'remotely_created_at' => $remotelyCreatedAt,
'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),
'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),
];
if ($accountId) {
$data['account_id'] = $accountId;
}
if ($stage) {
$data['stage_id'] = $stage->id;
}
if ($businessProcess) {
$recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);
if ($recordType) {
$data['record_type_id'] = $recordType->id;
}
}
return $data;
}
private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess
{
if ($pipelineId === null) {
return null;
}
if (isset($this->cachedBusinessProcesses[$pipelineId])) {
return $this->cachedBusinessProcesses[$pipelineId];
}
$businessProcess = $this->getBusinessProcess($pipelineId);
if (! $businessProcess instanceof BusinessProcess) {
$this->importStages();
$businessProcess = $this->getBusinessProcess($pipelineId);
}
if (! $businessProcess instanceof BusinessProcess) {
$this->logger->info(
'[HubSpot] Deal is not attached to a pipeline',
[
'pipeline' => $pipelineId]
);
}
$this->cachedBusinessProcesses[$pipelineId] = $businessProcess;
return $businessProcess;
}
private function getBusinessProcess(string $pipelineId): ?BusinessProcess
{
return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);
}
private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage
{
if (empty($stageId)) {
return null;
}
$cacheKey = $businessProcess->getId() . ':' . $stageId;
if (isset($this->cachedStages[$cacheKey])) {
return $this->cachedStages[$cacheKey];
}
$stage = $this->crmEntityRepository->getPipelineStageByConditions(
$businessProcess,
[
'crm_provider_id' => $stageId,
'type' => Stage::TYPE_OPPORTUNITY,
]
);
if ($stage === null) {
$this->importStages(null, $stageId);
}
if ($stage === null) {
$this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);
}
$this->cachedStages[$cacheKey] = $stage;
return $stage;
}
private function resolveAmount(array $properties): ?string
{
$amount = null;
if (! empty($properties['amount'])) {
$amount = str_replace(',', '', $properties['amount']);
}
if ($this->config->hasDefaultCurrencyFieldSet()) {
$valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();
$amount = $properties[$valueFieldName] ?? $amount;
}
return $amount;
}
private function parseCleanDatetime(string $datetime): ?Carbon
{
// Treat pre-1980 values as invalid
$minValidDate = Carbon::parse('1980-01-01 00:00:00');
try {
$date = Carbon::parse($datetime);
if ($minValidDate->gt($date)) {
return null;
}
return $date;
} catch (Exception) {
return null; // On parse error, treat as null
}
}
private function resolveDealProbability(?string $stageProbability): int
{
if ($stageProbability === null) {
return 0;
}
$probability = (float) $stageProbability;
return $probability > 1 ? 0 : (int) ($probability * 100);
}
private function resolveForecastCategory(?string $forecastCategory): string
{
if (! $forecastCategory) {
return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;
}
$forecastCategory = str_replace('_', ' ', $forecastCategory);
return ucwords(strtolower($forecastCategory));
}
private function importExternalFieldData(array $properties, int $opportunityId): void
{
$crmFields = $this->getOpportunitySyncableFields();
$this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);
}
private function importOpportunityContacts(Opportunity $opportunity, array $associations): void
{
// Handle empty or missing contact associations
if (empty($associations)) {
// Remove all existing contact associations if none provided
$this->removeAllOpportunityContacts($opportunity);
return;
}
// Use differential sync approach for better performance and accuracy
$this->syncOpportunityContactsDifferential($opportunity, $associations);
}
/**
* Sync opportunity contacts using differential approach
* This compares current vs new associations and only makes necessary changes
*/
private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void
{
$currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);
$contactAssociationIds = array_keys($contactAssociations);
$contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);
$contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);
if (empty($contactsToAdd) && empty($contactsToRemove)) {
return;
}
$this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);
$this->removeContactAssociations($opportunity, $contactsToRemove);
$this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);
}
private function getCurrentContactCrmIds(Opportunity $opportunity): array
{
return $opportunity->contacts()
->pluck('contacts.crm_provider_id')
->toArray();
}
private function logContactAssociationChanges(
Opportunity $opportunity,
array $currentContactCrmIds,
array $contactAssociations,
array $contactsToAdd,
array $contactsToRemove
): void {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [
'opportunity_id' => $opportunity->getId(),
'current_contacts' => $currentContactCrmIds,
'new_contacts' => $contactAssociations,
'contacts_to_add' => $contactsToAdd,
'contacts_to_remove' => $contactsToRemove,
]);
}
private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void
{
if (empty($contactsToRemove)) {
return;
}
$contactsToDetach = $opportunity->contacts()
->whereIn('contacts.crm_provider_id', $contactsToRemove)
->pluck('contacts.id')
->toArray();
if (! empty($contactsToDetach)) {
$opportunity->contacts()->detach($contactsToDetach);
$this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_contact_crm_ids' => $contactsToRemove,
'removed_contact_count' => count($contactsToDetach),
]);
}
}
private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void
{
if (empty($contactsToAdd)) {
return;
}
$contactsAdded = [];
foreach ($contactsToAdd as $crmId) {
$id = $contactAssociations[$crmId];
if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {
$contactsAdded[] = $crmId;
}
}
$this->logAddedContacts($opportunity, $contactsAdded);
}
private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool
{
try {
$contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);
if (! $contact) {
return false;
}
return $this->performContactAttachment($opportunity, $contact, $crmId);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [
'opportunity_id' => $opportunity->getId(),
'contact_crm_id' => $crmId,
'error' => $e->getMessage(),
]);
return false;
}
}
private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool
{
try {
$opportunity->contacts()->attach($contact->getId(), [
'crm_provider_id' => $crmId,
]);
return true;
} catch (\Illuminate\Database\QueryException $e) {
if (str_contains($e->getMessage(), 'Duplicate entry')) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [
'contact_id' => $contact->getId(),
'contact_crm_id' => $crmId,
'opportunity_id' => $opportunity->getId(),
]);
return false;
}
throw $e;
}
}
private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void
{
if (! empty($contactsAdded)) {
$this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [
'opportunity_id' => $opportunity->getId(),
'contacts_to_add_count' => count($contactsAdded),
'added_contact_crm_ids' => $contactsAdded,
'added_contacts_count' => count($contactsAdded),
]);
}
}
}...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20692-fix-integration-app-token-auth-response-change, menu","depth":5,"help_text":"Git Branch: JY-20692-fix-integration-app-token-auth-response-change","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsCommandTest","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsCommandTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsCommandTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show Replace Field","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Search History","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"cachedStages","depth":4,"value":"cachedStages","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Match Case","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Words","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Regex","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Replace History","depth":3,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Replace","depth":4,"role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Preserve case","depth":3,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2/4","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Occurrence","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Occurrence","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Filter Search Results","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open in Window, Multiple Cursors","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Click to highlight","depth":4,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"33","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"19","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\CollectionResponseAssociatedId;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Models\\Account;\nuse Exception;\nuse Jiminny\\Component\\DealInsights\\Forecast\\Forecast;\nuse Jiminny\\Jobs\\Crm\\MatchActivitiesToNewOpportunity;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Crm\\BusinessProcess;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Models\\Opportunity;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DealFieldsService;\nuse Jiminny\\Services\\Crm\\Hubspot\\OpportunitySyncStrategy\\HubspotSingleSyncStrategy;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Services\\Crm\\OpportunitySyncStrategyResolver;\nuse Jiminny\\Utils\\CurrencyFormatter;\n\n/**\n * Optimized sync methods for better performance\n * These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains\n */\ntrait OpportunitySyncTrait\n{\n private const int BATCH_SIZE = 100;\n private const int BATCH_PROCESS_SIZE = 800;\n\n protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;\n protected CrmEntityRepository $crmEntityRepository;\n protected DealFieldsService $dealFieldsService;\n\n private ?array $cachedClosedDealStages = null;\n private array $cachedBusinessProcesses = [];\n private array $cachedStages = [];\n\n public function syncOpportunities(array $parameters, ?string $strategy = null): int\n {\n $strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);\n $parameters['config'] = $this->config;\n $syncCount = 0;\n $reportedTotal = 0;\n $lastSyncedId = [];\n\n try {\n foreach ($strategies as $strategyName => $syncStrategy) {\n $this->logger->info(\n '[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .\n $strategyName\n );\n\n $total = 0;\n $lastId = null;\n $buffer = [];\n\n // HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies\n foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {\n $buffer[] = $hsOpportunity;\n\n // process every 800 rows (fits < 1 000 association limit)\n if (\\count($buffer) >= self::BATCH_PROCESS_SIZE) {\n $syncCount += $this->processOpportunityBatch($buffer);\n $buffer = [];\n }\n }\n\n // leftovers\n if ($buffer) {\n $syncCount += $this->processOpportunityBatch($buffer);\n }\n\n $reportedTotal += $total;\n $lastSyncedId = $lastId;\n }\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException | CrmException $e) {\n $this->handleSyncException($e, $parameters);\n }\n\n $this->logger->info(\n '[HubSpot] Synced opportunities',\n [\n 'team' => $this->team->getId(),\n 'sync_count' => $syncCount,\n 'total' => $reportedTotal,\n 'last_synced_id' => $lastSyncedId,\n ]\n );\n\n return $reportedTotal;\n }\n\n private function handleSyncException(\\Throwable $e, array $parameters): void\n {\n if (($parameters['since'] ?? null) instanceof Carbon) {\n $parameters['since'] = $parameters['since']->toDateTimeString();\n }\n $parameters['config'] = $this->config->getId();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [\n 'teamId' => $this->team->getUuid(),\n 'parameters' => $parameters,\n 'reason' => $e->getMessage(),\n ]);\n }\n\n /**\n * @inheritdoc\n */\n public function syncOpportunity(string $crmId): ?Opportunity\n {\n $strategy = $this->opportunitySyncStrategyResolver->resolve(\n $this->config,\n OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,\n );\n\n $parameters = [\n 'config' => $this->config,\n 'crm_id' => $crmId,\n ];\n\n try {\n if (! $strategy instanceof HubspotSingleSyncStrategy) {\n throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');\n }\n\n $hsOpportunity = $strategy->fetchOpportunity($parameters);\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);\n\n return $this->importOrUpdateOpportunity($hsOpportunity);\n }\n\n /**\n * Process webhook-collected opportunity batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportOpportunityBatch jobs for batch processing.\n *\n * @return int Number of opportunity IDs dispatched to jobs\n */\n public function batchSyncOpportunities(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,\n $configId\n );\n }\n\n /**\n * Import a batch of opportunities by their CRM IDs.\n * Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().\n *\n * @param array<string> $crmIds HubSpot deal CRM IDs\n *\n * @return array{success: array, failed_ids: array, errors?: array<string, string>}\n */\n public function importOpportunityBatchByIds(array $crmIds): array\n {\n $fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);\n\n $allDeals = [];\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n $deals = $this->client->getOpportunitiesByIds($chunk, $fields);\n foreach ($deals as $deal) {\n $allDeals[] = $deal;\n }\n }\n\n // IDs not returned by HubSpot are likely deleted or inaccessible deals.\n // These are not failures — retrying won't bring them back.\n $fetchedIds = array_map('strval', array_column($allDeals, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allDeals),\n ]);\n }\n\n if (empty($allDeals)) {\n return ['success' => [], 'failed_ids' => []];\n }\n\n return $this->importOpportunityBatch($allDeals);\n }\n\n private function getClosedDealStages(): array\n {\n if ($this->cachedClosedDealStages !== null) {\n return $this->cachedClosedDealStages;\n }\n\n $stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);\n $data = [\n 'lost' => [],\n 'won' => [],\n ];\n\n foreach ($stages as $stage) {\n if ($stage->probability == 0.00) {\n $data['lost'][] = $stage->crm_provider_id;\n }\n if ($stage->probability == 100.00) {\n $data['won'][] = $stage->crm_provider_id;\n }\n }\n\n $this->cachedClosedDealStages = $data;\n\n return $data;\n }\n\n /**\n * Import deals into the database with pre-fetched associations.\n *\n * API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT\n * caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()\n * where Laravel retries the whole job with backoff. After all retries exhausted,\n * failed() requeues all IDs to Redis.\n *\n * The per-deal loop catches exceptions individually. A deal can end up in three states:\n * - success: imported/updated successfully\n * - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)\n * These are permanent issues — retrying won't fix them.\n * - skipped (null): missing dependencies (no account, unknown pipeline/stage).\n * This is acceptable — the deal cannot be imported until those exist.\n */\n private function importOpportunityBatch(array $deals): array\n {\n $syncedOpportunities = [\n 'success' => [],\n 'failed_ids' => [],\n ];\n $dealIds = array_column($deals, 'id');\n\n // Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the\n // queue job retries the whole batch and eventually requeues all deal IDs back to Redis.\n try {\n $companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');\n $contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');\n\n $associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);\n\n $existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(\n $this->config,\n array_map('strval', $dealIds)\n );\n $existingCrmIdSet = array_flip($existingCrmIds);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [\n 'teamId' => $this->team->getId(),\n 'dealCount' => count($dealIds),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n foreach ($deals as $deal) {\n try {\n $deal['associations'] = $this->prepareAssociationsForOpportunity(\n $deal['id'],\n $companyAssociations,\n $contactAssociations,\n $associationsData\n );\n\n $syncedOpportunity = $this->importOrUpdateOpportunity(\n $deal,\n isset($existingCrmIdSet[(string) $deal['id']])\n );\n if ($syncedOpportunity) {\n $syncedOpportunities['success'][] = $syncedOpportunity;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $deal['id'],\n 'error' => $e->getMessage(),\n ]);\n $syncedOpportunities['failed_ids'][] = $deal['id'];\n $syncedOpportunities['errors'][$deal['id']] = $e->getMessage();\n }\n }\n\n return $syncedOpportunities;\n }\n\n /**\n * Prepare associated entities for opportunities with optimized batch processing\n * Returns structured data with CRM ID to DB ID mappings for each opportunity\n */\n private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array\n {\n // Step 1: Collect all unique company and contact IDs from associations\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n\n // Step 2: Batch sync missing entities and get CRM ID to DB ID mappings\n $companyIdMappings = [];\n $contactIdMappings = [];\n\n if (! empty($allCompanyIds)) {\n $companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);\n }\n\n if (! empty($allContactIds)) {\n $contactIdMappings = $this->prepareAssociatedContacts($allContactIds);\n }\n\n return [\n 'company_id_mappings' => $companyIdMappings,\n 'contact_id_mappings' => $contactIdMappings,\n ];\n }\n\n /**\n * Flatten association data to get unique IDs\n */\n private function flattenAssociationIds(array $associations): array\n {\n $ids = [];\n foreach ($associations as $dealAssociations) {\n if (is_array($dealAssociations)) {\n foreach ($dealAssociations as $id) {\n $ids[$id] = true;\n }\n }\n }\n\n return array_keys($ids);\n }\n\n /**\n * Batch sync missing accounts\n */\n private function prepareAssociatedAccounts(array $companyIds): array\n {\n // Find which accounts already exist\n $existingAccounts = $this->crmEntityRepository\n ->findAccountsByExternalIds($this->config, $companyIds);\n\n $existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();\n\n $existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {\n return [$account->getCrmProviderId() => $account->getId()];\n })->toArray();\n\n $missingCompanyIds = array_diff($companyIds, $existingCompanyIds);\n\n if (empty($missingCompanyIds)) {\n return $existingAccountsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [\n 'teamId' => $this->team->getUuid(),\n 'total_companies' => count($companyIds),\n 'existing_companies' => count($existingCompanyIds),\n 'missing_companies' => count($missingCompanyIds),\n ]);\n\n // we already have limit on opportunity ids count\n // Initialize variable before try block\n $syncedAccountsData = [];\n\n try {\n $syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [\n 'size' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedAccountsData = [];\n }\n\n return $existingAccountsData + $syncedAccountsData;\n }\n\n /**\n * Prepare associated contacts - find existing and sync missing ones\n * Returns mapping of CRM ID to DB ID\n */\n private function prepareAssociatedContacts(array $contactIds): array\n {\n // Find which contacts already exist\n $existingContacts = $this->crmEntityRepository\n ->findContactsByExternalIds($this->config, $contactIds);\n\n $existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();\n\n // Create mapping for existing contacts\n $existingContactsData = $existingContacts->mapWithKeys(function ($contact) {\n return [$contact->getCrmProviderId() => $contact->getId()];\n })->toArray();\n\n $missingContactIds = array_diff($contactIds, $existingContactIds);\n\n if (empty($missingContactIds)) {\n return $existingContactsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [\n 'teamId' => $this->team->getUuid(),\n 'total_contacts' => count($contactIds),\n 'existing_contacts' => count($existingContactIds),\n 'missing_contacts' => count($missingContactIds),\n ]);\n\n // Sync missing contacts using batch API\n try {\n $syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [\n 'size' => count($missingContactIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedContactsData = [];\n }\n\n return $existingContactsData + $syncedContactsData;\n }\n\n private function batchSyncCrmObjects(string $objectType, array $crmIds): array\n {\n $syncObjects = [];\n $crmObjectIds = array_values($crmIds);\n\n foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {\n try {\n $objects = $objectType === 'companies' ?\n $this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :\n $this->client->getContactsByIds($chunk, $this->getContactFields());\n\n foreach ($objects as $objectId => $objectData) {\n $this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [\n 'requested_count' => count($chunk),\n 'synced_count' => count($objects),\n ]);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [\n 'ids' => $chunk,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncObjects;\n }\n\n private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void\n {\n try {\n $object = $objectType === 'companies' ?\n $this->importAccount($objectData) :\n $this->importContact($objectData);\n\n if ($object) {\n $syncObjects[$object->getCrmProviderId()] = $object->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [\n 'id' => $objectId,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n /**\n * Prepare associations for a single opportunity\n *\n * The return value is an array with the following structure:\n * [\n * 'companies' => [\n * $companyCrmId => $companyId,\n * ...\n * ],\n * 'contacts' => [\n * $contactCrmId => $contactId,\n * ...\n * ],\n * 'account_id' => $accountId,\n * ]\n */\n private function prepareAssociationsForOpportunity(\n string $oppCrmId,\n array $companyAssociations,\n array $contactAssociations,\n array $associationsData\n ): array {\n $associations = [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n\n $oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];\n foreach ($oppCompanyIds as $companyCrmId) {\n if (isset($associationsData['company_id_mappings'][$companyCrmId])) {\n $associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];\n\n // Set primary account (first company becomes primary account)\n if ($associations['account_id'] === null) {\n $associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];\n }\n }\n }\n\n $oppContactIds = $contactAssociations[$oppCrmId] ?? [];\n foreach ($oppContactIds as $contactCrmId) {\n if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {\n $associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];\n }\n }\n\n return $associations;\n }\n\n /**\n * Update only associations for an opportunity\n */\n private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void\n {\n // Update contact associations\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n // Update company (account) associations\n $this->updateOpportunityAccount($opportunity, $associations['account_id']);\n }\n\n /**\n * Remove all contact associations from an opportunity\n */\n private function removeAllOpportunityContacts(Opportunity $opportunity): void\n {\n $currentCount = (int) $opportunity->contacts()->count();\n\n if ($currentCount > 0) {\n $opportunity->contacts()->detach();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_count' => $currentCount,\n ]);\n }\n }\n\n private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void\n {\n if ($accountId === null) {\n // No account ID provided - keep current account\n return;\n }\n\n $currentAccountId = $opportunity->getAccountId();\n\n // Only update if account has changed\n if ($currentAccountId !== $accountId) {\n $opportunity->account_id = $accountId;\n $opportunity->save();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [\n 'opportunity_id' => $opportunity->getId(),\n 'old_account_id' => $currentAccountId,\n 'new_account_id' => $accountId,\n ]);\n }\n }\n\n /**\n * Find existing opportunities by external IDs (OPTIMIZED VERSION)\n * Uses batch query for better performance\n */\n private function findExistingOpportunities(array $crmIds): Collection\n {\n return $this->crmEntityRepository\n ->findOpportunitiesByExternalIds($this->config, $crmIds);\n }\n\n private function processOpportunityBatch(array $opportunities): int\n {\n $syncedOpportunities = $this->importOpportunityBatch($opportunities);\n\n return count($syncedOpportunities['success'] ?? []);\n }\n\n /**\n * Convert single deal associations from HubSpot format to internal format\n * Handles both HubSpot SDK objects and array formats\n *\n * @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed\n *\n * @return array Processed associations with DB IDs\n */\n private function convertDealAssociations(array $opportunityAssociations): array\n {\n $associations = $this->initializeAssociationsStructure();\n\n if (empty($opportunityAssociations)) {\n return $associations;\n }\n\n $associationIds = $this->extractAssociationIds($opportunityAssociations);\n\n $this->processCompanyAssociations($associationIds, $associations);\n $this->processContactAssociations($associationIds, $associations);\n\n return $associations;\n }\n\n private function initializeAssociationsStructure(): array\n {\n return [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n }\n\n private function extractAssociationIds(array $opportunityAssociations): array\n {\n $associationIds = [];\n\n foreach ($opportunityAssociations as $type => $associationData) {\n if (! empty($associationData)) {\n $associationIds[$type] = $this->convertSingleDealAssociations($associationData);\n }\n }\n\n return $associationIds;\n }\n\n private function processCompanyAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['companies'])) {\n return;\n }\n\n $companyId = $associationIds['companies'][0];\n $account = $this->findOrSyncAccount($companyId);\n\n if ($account instanceof Account) {\n $associations['companies'][$companyId] = $account->getId();\n $associations['account_id'] = $account->getId();\n }\n }\n\n private function processContactAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['contacts'])) {\n return;\n }\n\n foreach ($associationIds['contacts'] as $contactId) {\n $contact = $this->findOrSyncContact($contactId);\n\n if ($contact instanceof Contact) {\n $associations['contacts'][$contactId] = $contact->getId();\n }\n }\n }\n\n private function findOrSyncAccount(string $companyId): ?Account\n {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);\n\n if (! $account instanceof Account) {\n $account = $this->syncAccount($companyId);\n }\n\n return $account;\n }\n\n private function findOrSyncContact(string $contactId): ?Contact\n {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);\n\n if (! $contact instanceof Contact) {\n $contact = $this->syncContact($contactId);\n }\n\n return $contact;\n }\n\n private function convertSingleDealAssociations($opportunityAssociations = null): array\n {\n $associationData = [];\n\n if ($opportunityAssociations === null) {\n return $associationData;\n }\n\n // Handle array input (from extractAssociationIds)\n if (is_array($opportunityAssociations)) {\n return $opportunityAssociations;\n }\n\n // Handle CollectionResponseAssociatedId object\n if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {\n foreach ($opportunityAssociations->getResults() as $association) {\n $associationData[] = $association->getId();\n }\n }\n\n return $associationData;\n }\n\n private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity\n {\n if (empty($crmData['properties'])) {\n return null;\n }\n\n $crmId = (string) $crmData['id'];\n $properties = $crmData['properties'];\n $associations = $crmData['associations'] ?? [];\n\n $opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(\n $this->config,\n $crmId\n );\n\n if ($opportunityExists) {\n return $this->updateOpportunity($crmId, $properties, $associations);\n } else {\n return $this->createOpportunity($crmId, $properties, $associations);\n }\n }\n\n /**\n * Create new opportunity\n */\n private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n if (! $accountId) {\n return null;\n }\n\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n if (! $businessProcess) {\n return null;\n }\n\n $stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);\n if (! $stage) {\n return null;\n }\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n if ($opportunity->wasRecentlyCreated) {\n MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());\n }\n\n return $opportunity;\n }\n\n /**\n * Update existing opportunity\n */\n private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n $stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->updateOpportunityAssociations($opportunity, $associations);\n\n return $opportunity;\n }\n\n private function resolveAccountId(array $associations): ?int\n {\n if (! empty($associations['accountId'])) {\n return $associations['accountId'];\n }\n\n if (empty($associations)) {\n return null;\n }\n\n // we can't resolve multiple account ids (currently SDK returns one company)\n foreach ($associations['companies'] as $accountId) {\n return $accountId;\n }\n\n return null;\n }\n\n private function buildOpportunityData(\n array $properties,\n ?int $accountId,\n ?BusinessProcess $businessProcess,\n ?Stage $stage\n ): array {\n $ownerId = null;\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $name = 'Unknown';\n if (isset($properties['dealname'])) {\n $name = mb_strimwidth($properties['dealname'], 0, 128);\n }\n\n $amount = $this->resolveAmount($properties);\n $currency = $properties['deal_currency_code'] ?? null;\n\n $closeDate = null;\n if (! empty($properties['closedate'])) {\n $closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');\n }\n\n $remotelyCreatedAt = null;\n if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {\n $date = $this->parseCleanDatetime($properties['createdate']);\n $remotelyCreatedAt = $date?->format('Y-m-d H:i:s');\n }\n\n $closedStages = $this->getClosedDealStages();\n $isWon = in_array($properties['dealstage'], $closedStages['won']);\n $isLost = in_array($properties['dealstage'], $closedStages['lost']);\n\n $data = [\n 'team_id' => $this->team->getId(),\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'value' => ! empty($amount) ? $amount : null,\n 'currency_code' => CurrencyFormatter::formatCode($currency),\n 'close_date' => $closeDate,\n 'is_closed' => $isWon || $isLost,\n 'is_won' => $isWon,\n 'remotely_created_at' => $remotelyCreatedAt,\n 'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),\n 'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),\n ];\n\n if ($accountId) {\n $data['account_id'] = $accountId;\n }\n\n if ($stage) {\n $data['stage_id'] = $stage->id;\n }\n\n if ($businessProcess) {\n $recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);\n if ($recordType) {\n $data['record_type_id'] = $recordType->id;\n }\n }\n\n return $data;\n }\n\n private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess\n {\n if ($pipelineId === null) {\n return null;\n }\n\n if (isset($this->cachedBusinessProcesses[$pipelineId])) {\n return $this->cachedBusinessProcesses[$pipelineId];\n }\n\n $businessProcess = $this->getBusinessProcess($pipelineId);\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->importStages();\n $businessProcess = $this->getBusinessProcess($pipelineId);\n }\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->logger->info(\n '[HubSpot] Deal is not attached to a pipeline',\n [\n 'pipeline' => $pipelineId]\n );\n }\n\n $this->cachedBusinessProcesses[$pipelineId] = $businessProcess;\n\n return $businessProcess;\n }\n\n private function getBusinessProcess(string $pipelineId): ?BusinessProcess\n {\n return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);\n }\n\n private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage\n {\n if (empty($stageId)) {\n return null;\n }\n\n $cacheKey = $businessProcess->getId() . ':' . $stageId;\n if (isset($this->cachedStages[$cacheKey])) {\n return $this->cachedStages[$cacheKey];\n }\n\n $stage = $this->crmEntityRepository->getPipelineStageByConditions(\n $businessProcess,\n [\n 'crm_provider_id' => $stageId,\n 'type' => Stage::TYPE_OPPORTUNITY,\n ]\n );\n\n if ($stage === null) {\n $this->importStages(null, $stageId);\n }\n\n if ($stage === null) {\n $this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);\n }\n\n $this->cachedStages[$cacheKey] = $stage;\n\n return $stage;\n }\n\n private function resolveAmount(array $properties): ?string\n {\n $amount = null;\n if (! empty($properties['amount'])) {\n $amount = str_replace(',', '', $properties['amount']);\n }\n\n if ($this->config->hasDefaultCurrencyFieldSet()) {\n $valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();\n $amount = $properties[$valueFieldName] ?? $amount;\n }\n\n return $amount;\n }\n\n private function parseCleanDatetime(string $datetime): ?Carbon\n {\n // Treat pre-1980 values as invalid\n $minValidDate = Carbon::parse('1980-01-01 00:00:00');\n\n try {\n $date = Carbon::parse($datetime);\n\n if ($minValidDate->gt($date)) {\n return null;\n }\n\n return $date;\n } catch (Exception) {\n return null; // On parse error, treat as null\n }\n }\n\n private function resolveDealProbability(?string $stageProbability): int\n {\n if ($stageProbability === null) {\n return 0;\n }\n\n $probability = (float) $stageProbability;\n\n return $probability > 1 ? 0 : (int) ($probability * 100);\n }\n\n private function resolveForecastCategory(?string $forecastCategory): string\n {\n if (! $forecastCategory) {\n return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;\n }\n\n $forecastCategory = str_replace('_', ' ', $forecastCategory);\n\n return ucwords(strtolower($forecastCategory));\n }\n\n private function importExternalFieldData(array $properties, int $opportunityId): void\n {\n $crmFields = $this->getOpportunitySyncableFields();\n $this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);\n }\n\n private function importOpportunityContacts(Opportunity $opportunity, array $associations): void\n {\n // Handle empty or missing contact associations\n if (empty($associations)) {\n // Remove all existing contact associations if none provided\n $this->removeAllOpportunityContacts($opportunity);\n\n return;\n }\n\n // Use differential sync approach for better performance and accuracy\n $this->syncOpportunityContactsDifferential($opportunity, $associations);\n }\n\n /**\n * Sync opportunity contacts using differential approach\n * This compares current vs new associations and only makes necessary changes\n */\n private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void\n {\n $currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);\n $contactAssociationIds = array_keys($contactAssociations);\n\n $contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);\n $contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);\n\n if (empty($contactsToAdd) && empty($contactsToRemove)) {\n return;\n }\n\n $this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);\n\n $this->removeContactAssociations($opportunity, $contactsToRemove);\n $this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);\n }\n\n private function getCurrentContactCrmIds(Opportunity $opportunity): array\n {\n return $opportunity->contacts()\n ->pluck('contacts.crm_provider_id')\n ->toArray();\n }\n\n private function logContactAssociationChanges(\n Opportunity $opportunity,\n array $currentContactCrmIds,\n array $contactAssociations,\n array $contactsToAdd,\n array $contactsToRemove\n ): void {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [\n 'opportunity_id' => $opportunity->getId(),\n 'current_contacts' => $currentContactCrmIds,\n 'new_contacts' => $contactAssociations,\n 'contacts_to_add' => $contactsToAdd,\n 'contacts_to_remove' => $contactsToRemove,\n ]);\n }\n\n private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void\n {\n if (empty($contactsToRemove)) {\n return;\n }\n\n $contactsToDetach = $opportunity->contacts()\n ->whereIn('contacts.crm_provider_id', $contactsToRemove)\n ->pluck('contacts.id')\n ->toArray();\n\n if (! empty($contactsToDetach)) {\n $opportunity->contacts()->detach($contactsToDetach);\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_contact_crm_ids' => $contactsToRemove,\n 'removed_contact_count' => count($contactsToDetach),\n ]);\n }\n }\n\n private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void\n {\n if (empty($contactsToAdd)) {\n return;\n }\n\n $contactsAdded = [];\n foreach ($contactsToAdd as $crmId) {\n $id = $contactAssociations[$crmId];\n\n if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {\n $contactsAdded[] = $crmId;\n }\n }\n\n $this->logAddedContacts($opportunity, $contactsAdded);\n }\n\n private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);\n\n if (! $contact) {\n return false;\n }\n\n return $this->performContactAttachment($opportunity, $contact, $crmId);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [\n 'opportunity_id' => $opportunity->getId(),\n 'contact_crm_id' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool\n {\n try {\n $opportunity->contacts()->attach($contact->getId(), [\n 'crm_provider_id' => $crmId,\n ]);\n\n return true;\n } catch (\\Illuminate\\Database\\QueryException $e) {\n if (str_contains($e->getMessage(), 'Duplicate entry')) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [\n 'contact_id' => $contact->getId(),\n 'contact_crm_id' => $crmId,\n 'opportunity_id' => $opportunity->getId(),\n ]);\n\n return false;\n }\n\n throw $e;\n }\n }\n\n private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void\n {\n if (! empty($contactsAdded)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'contacts_to_add_count' => count($contactsAdded),\n 'added_contact_crm_ids' => $contactsAdded,\n 'added_contacts_count' => count($contactsAdded),\n ]);\n }\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\CollectionResponseAssociatedId;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Models\\Account;\nuse Exception;\nuse Jiminny\\Component\\DealInsights\\Forecast\\Forecast;\nuse Jiminny\\Jobs\\Crm\\MatchActivitiesToNewOpportunity;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Crm\\BusinessProcess;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Models\\Opportunity;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DealFieldsService;\nuse Jiminny\\Services\\Crm\\Hubspot\\OpportunitySyncStrategy\\HubspotSingleSyncStrategy;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Services\\Crm\\OpportunitySyncStrategyResolver;\nuse Jiminny\\Utils\\CurrencyFormatter;\n\n/**\n * Optimized sync methods for better performance\n * These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains\n */\ntrait OpportunitySyncTrait\n{\n private const int BATCH_SIZE = 100;\n private const int BATCH_PROCESS_SIZE = 800;\n\n protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;\n protected CrmEntityRepository $crmEntityRepository;\n protected DealFieldsService $dealFieldsService;\n\n private ?array $cachedClosedDealStages = null;\n private array $cachedBusinessProcesses = [];\n private array $cachedStages = [];\n\n public function syncOpportunities(array $parameters, ?string $strategy = null): int\n {\n $strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);\n $parameters['config'] = $this->config;\n $syncCount = 0;\n $reportedTotal = 0;\n $lastSyncedId = [];\n\n try {\n foreach ($strategies as $strategyName => $syncStrategy) {\n $this->logger->info(\n '[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .\n $strategyName\n );\n\n $total = 0;\n $lastId = null;\n $buffer = [];\n\n // HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies\n foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {\n $buffer[] = $hsOpportunity;\n\n // process every 800 rows (fits < 1 000 association limit)\n if (\\count($buffer) >= self::BATCH_PROCESS_SIZE) {\n $syncCount += $this->processOpportunityBatch($buffer);\n $buffer = [];\n }\n }\n\n // leftovers\n if ($buffer) {\n $syncCount += $this->processOpportunityBatch($buffer);\n }\n\n $reportedTotal += $total;\n $lastSyncedId = $lastId;\n }\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException | CrmException $e) {\n $this->handleSyncException($e, $parameters);\n }\n\n $this->logger->info(\n '[HubSpot] Synced opportunities',\n [\n 'team' => $this->team->getId(),\n 'sync_count' => $syncCount,\n 'total' => $reportedTotal,\n 'last_synced_id' => $lastSyncedId,\n ]\n );\n\n return $reportedTotal;\n }\n\n private function handleSyncException(\\Throwable $e, array $parameters): void\n {\n if (($parameters['since'] ?? null) instanceof Carbon) {\n $parameters['since'] = $parameters['since']->toDateTimeString();\n }\n $parameters['config'] = $this->config->getId();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [\n 'teamId' => $this->team->getUuid(),\n 'parameters' => $parameters,\n 'reason' => $e->getMessage(),\n ]);\n }\n\n /**\n * @inheritdoc\n */\n public function syncOpportunity(string $crmId): ?Opportunity\n {\n $strategy = $this->opportunitySyncStrategyResolver->resolve(\n $this->config,\n OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,\n );\n\n $parameters = [\n 'config' => $this->config,\n 'crm_id' => $crmId,\n ];\n\n try {\n if (! $strategy instanceof HubspotSingleSyncStrategy) {\n throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');\n }\n\n $hsOpportunity = $strategy->fetchOpportunity($parameters);\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);\n\n return $this->importOrUpdateOpportunity($hsOpportunity);\n }\n\n /**\n * Process webhook-collected opportunity batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportOpportunityBatch jobs for batch processing.\n *\n * @return int Number of opportunity IDs dispatched to jobs\n */\n public function batchSyncOpportunities(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,\n $configId\n );\n }\n\n /**\n * Import a batch of opportunities by their CRM IDs.\n * Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().\n *\n * @param array<string> $crmIds HubSpot deal CRM IDs\n *\n * @return array{success: array, failed_ids: array, errors?: array<string, string>}\n */\n public function importOpportunityBatchByIds(array $crmIds): array\n {\n $fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);\n\n $allDeals = [];\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n $deals = $this->client->getOpportunitiesByIds($chunk, $fields);\n foreach ($deals as $deal) {\n $allDeals[] = $deal;\n }\n }\n\n // IDs not returned by HubSpot are likely deleted or inaccessible deals.\n // These are not failures — retrying won't bring them back.\n $fetchedIds = array_map('strval', array_column($allDeals, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allDeals),\n ]);\n }\n\n if (empty($allDeals)) {\n return ['success' => [], 'failed_ids' => []];\n }\n\n return $this->importOpportunityBatch($allDeals);\n }\n\n private function getClosedDealStages(): array\n {\n if ($this->cachedClosedDealStages !== null) {\n return $this->cachedClosedDealStages;\n }\n\n $stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);\n $data = [\n 'lost' => [],\n 'won' => [],\n ];\n\n foreach ($stages as $stage) {\n if ($stage->probability == 0.00) {\n $data['lost'][] = $stage->crm_provider_id;\n }\n if ($stage->probability == 100.00) {\n $data['won'][] = $stage->crm_provider_id;\n }\n }\n\n $this->cachedClosedDealStages = $data;\n\n return $data;\n }\n\n /**\n * Import deals into the database with pre-fetched associations.\n *\n * API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT\n * caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()\n * where Laravel retries the whole job with backoff. After all retries exhausted,\n * failed() requeues all IDs to Redis.\n *\n * The per-deal loop catches exceptions individually. A deal can end up in three states:\n * - success: imported/updated successfully\n * - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)\n * These are permanent issues — retrying won't fix them.\n * - skipped (null): missing dependencies (no account, unknown pipeline/stage).\n * This is acceptable — the deal cannot be imported until those exist.\n */\n private function importOpportunityBatch(array $deals): array\n {\n $syncedOpportunities = [\n 'success' => [],\n 'failed_ids' => [],\n ];\n $dealIds = array_column($deals, 'id');\n\n // Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the\n // queue job retries the whole batch and eventually requeues all deal IDs back to Redis.\n try {\n $companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');\n $contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');\n\n $associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);\n\n $existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(\n $this->config,\n array_map('strval', $dealIds)\n );\n $existingCrmIdSet = array_flip($existingCrmIds);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [\n 'teamId' => $this->team->getId(),\n 'dealCount' => count($dealIds),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n foreach ($deals as $deal) {\n try {\n $deal['associations'] = $this->prepareAssociationsForOpportunity(\n $deal['id'],\n $companyAssociations,\n $contactAssociations,\n $associationsData\n );\n\n $syncedOpportunity = $this->importOrUpdateOpportunity(\n $deal,\n isset($existingCrmIdSet[(string) $deal['id']])\n );\n if ($syncedOpportunity) {\n $syncedOpportunities['success'][] = $syncedOpportunity;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $deal['id'],\n 'error' => $e->getMessage(),\n ]);\n $syncedOpportunities['failed_ids'][] = $deal['id'];\n $syncedOpportunities['errors'][$deal['id']] = $e->getMessage();\n }\n }\n\n return $syncedOpportunities;\n }\n\n /**\n * Prepare associated entities for opportunities with optimized batch processing\n * Returns structured data with CRM ID to DB ID mappings for each opportunity\n */\n private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array\n {\n // Step 1: Collect all unique company and contact IDs from associations\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n\n // Step 2: Batch sync missing entities and get CRM ID to DB ID mappings\n $companyIdMappings = [];\n $contactIdMappings = [];\n\n if (! empty($allCompanyIds)) {\n $companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);\n }\n\n if (! empty($allContactIds)) {\n $contactIdMappings = $this->prepareAssociatedContacts($allContactIds);\n }\n\n return [\n 'company_id_mappings' => $companyIdMappings,\n 'contact_id_mappings' => $contactIdMappings,\n ];\n }\n\n /**\n * Flatten association data to get unique IDs\n */\n private function flattenAssociationIds(array $associations): array\n {\n $ids = [];\n foreach ($associations as $dealAssociations) {\n if (is_array($dealAssociations)) {\n foreach ($dealAssociations as $id) {\n $ids[$id] = true;\n }\n }\n }\n\n return array_keys($ids);\n }\n\n /**\n * Batch sync missing accounts\n */\n private function prepareAssociatedAccounts(array $companyIds): array\n {\n // Find which accounts already exist\n $existingAccounts = $this->crmEntityRepository\n ->findAccountsByExternalIds($this->config, $companyIds);\n\n $existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();\n\n $existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {\n return [$account->getCrmProviderId() => $account->getId()];\n })->toArray();\n\n $missingCompanyIds = array_diff($companyIds, $existingCompanyIds);\n\n if (empty($missingCompanyIds)) {\n return $existingAccountsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [\n 'teamId' => $this->team->getUuid(),\n 'total_companies' => count($companyIds),\n 'existing_companies' => count($existingCompanyIds),\n 'missing_companies' => count($missingCompanyIds),\n ]);\n\n // we already have limit on opportunity ids count\n // Initialize variable before try block\n $syncedAccountsData = [];\n\n try {\n $syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [\n 'size' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedAccountsData = [];\n }\n\n return $existingAccountsData + $syncedAccountsData;\n }\n\n /**\n * Prepare associated contacts - find existing and sync missing ones\n * Returns mapping of CRM ID to DB ID\n */\n private function prepareAssociatedContacts(array $contactIds): array\n {\n // Find which contacts already exist\n $existingContacts = $this->crmEntityRepository\n ->findContactsByExternalIds($this->config, $contactIds);\n\n $existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();\n\n // Create mapping for existing contacts\n $existingContactsData = $existingContacts->mapWithKeys(function ($contact) {\n return [$contact->getCrmProviderId() => $contact->getId()];\n })->toArray();\n\n $missingContactIds = array_diff($contactIds, $existingContactIds);\n\n if (empty($missingContactIds)) {\n return $existingContactsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [\n 'teamId' => $this->team->getUuid(),\n 'total_contacts' => count($contactIds),\n 'existing_contacts' => count($existingContactIds),\n 'missing_contacts' => count($missingContactIds),\n ]);\n\n // Sync missing contacts using batch API\n try {\n $syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [\n 'size' => count($missingContactIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedContactsData = [];\n }\n\n return $existingContactsData + $syncedContactsData;\n }\n\n private function batchSyncCrmObjects(string $objectType, array $crmIds): array\n {\n $syncObjects = [];\n $crmObjectIds = array_values($crmIds);\n\n foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {\n try {\n $objects = $objectType === 'companies' ?\n $this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :\n $this->client->getContactsByIds($chunk, $this->getContactFields());\n\n foreach ($objects as $objectId => $objectData) {\n $this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [\n 'requested_count' => count($chunk),\n 'synced_count' => count($objects),\n ]);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [\n 'ids' => $chunk,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncObjects;\n }\n\n private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void\n {\n try {\n $object = $objectType === 'companies' ?\n $this->importAccount($objectData) :\n $this->importContact($objectData);\n\n if ($object) {\n $syncObjects[$object->getCrmProviderId()] = $object->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [\n 'id' => $objectId,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n /**\n * Prepare associations for a single opportunity\n *\n * The return value is an array with the following structure:\n * [\n * 'companies' => [\n * $companyCrmId => $companyId,\n * ...\n * ],\n * 'contacts' => [\n * $contactCrmId => $contactId,\n * ...\n * ],\n * 'account_id' => $accountId,\n * ]\n */\n private function prepareAssociationsForOpportunity(\n string $oppCrmId,\n array $companyAssociations,\n array $contactAssociations,\n array $associationsData\n ): array {\n $associations = [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n\n $oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];\n foreach ($oppCompanyIds as $companyCrmId) {\n if (isset($associationsData['company_id_mappings'][$companyCrmId])) {\n $associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];\n\n // Set primary account (first company becomes primary account)\n if ($associations['account_id'] === null) {\n $associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];\n }\n }\n }\n\n $oppContactIds = $contactAssociations[$oppCrmId] ?? [];\n foreach ($oppContactIds as $contactCrmId) {\n if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {\n $associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];\n }\n }\n\n return $associations;\n }\n\n /**\n * Update only associations for an opportunity\n */\n private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void\n {\n // Update contact associations\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n // Update company (account) associations\n $this->updateOpportunityAccount($opportunity, $associations['account_id']);\n }\n\n /**\n * Remove all contact associations from an opportunity\n */\n private function removeAllOpportunityContacts(Opportunity $opportunity): void\n {\n $currentCount = (int) $opportunity->contacts()->count();\n\n if ($currentCount > 0) {\n $opportunity->contacts()->detach();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_count' => $currentCount,\n ]);\n }\n }\n\n private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void\n {\n if ($accountId === null) {\n // No account ID provided - keep current account\n return;\n }\n\n $currentAccountId = $opportunity->getAccountId();\n\n // Only update if account has changed\n if ($currentAccountId !== $accountId) {\n $opportunity->account_id = $accountId;\n $opportunity->save();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [\n 'opportunity_id' => $opportunity->getId(),\n 'old_account_id' => $currentAccountId,\n 'new_account_id' => $accountId,\n ]);\n }\n }\n\n /**\n * Find existing opportunities by external IDs (OPTIMIZED VERSION)\n * Uses batch query for better performance\n */\n private function findExistingOpportunities(array $crmIds): Collection\n {\n return $this->crmEntityRepository\n ->findOpportunitiesByExternalIds($this->config, $crmIds);\n }\n\n private function processOpportunityBatch(array $opportunities): int\n {\n $syncedOpportunities = $this->importOpportunityBatch($opportunities);\n\n return count($syncedOpportunities['success'] ?? []);\n }\n\n /**\n * Convert single deal associations from HubSpot format to internal format\n * Handles both HubSpot SDK objects and array formats\n *\n * @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed\n *\n * @return array Processed associations with DB IDs\n */\n private function convertDealAssociations(array $opportunityAssociations): array\n {\n $associations = $this->initializeAssociationsStructure();\n\n if (empty($opportunityAssociations)) {\n return $associations;\n }\n\n $associationIds = $this->extractAssociationIds($opportunityAssociations);\n\n $this->processCompanyAssociations($associationIds, $associations);\n $this->processContactAssociations($associationIds, $associations);\n\n return $associations;\n }\n\n private function initializeAssociationsStructure(): array\n {\n return [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n }\n\n private function extractAssociationIds(array $opportunityAssociations): array\n {\n $associationIds = [];\n\n foreach ($opportunityAssociations as $type => $associationData) {\n if (! empty($associationData)) {\n $associationIds[$type] = $this->convertSingleDealAssociations($associationData);\n }\n }\n\n return $associationIds;\n }\n\n private function processCompanyAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['companies'])) {\n return;\n }\n\n $companyId = $associationIds['companies'][0];\n $account = $this->findOrSyncAccount($companyId);\n\n if ($account instanceof Account) {\n $associations['companies'][$companyId] = $account->getId();\n $associations['account_id'] = $account->getId();\n }\n }\n\n private function processContactAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['contacts'])) {\n return;\n }\n\n foreach ($associationIds['contacts'] as $contactId) {\n $contact = $this->findOrSyncContact($contactId);\n\n if ($contact instanceof Contact) {\n $associations['contacts'][$contactId] = $contact->getId();\n }\n }\n }\n\n private function findOrSyncAccount(string $companyId): ?Account\n {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);\n\n if (! $account instanceof Account) {\n $account = $this->syncAccount($companyId);\n }\n\n return $account;\n }\n\n private function findOrSyncContact(string $contactId): ?Contact\n {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);\n\n if (! $contact instanceof Contact) {\n $contact = $this->syncContact($contactId);\n }\n\n return $contact;\n }\n\n private function convertSingleDealAssociations($opportunityAssociations = null): array\n {\n $associationData = [];\n\n if ($opportunityAssociations === null) {\n return $associationData;\n }\n\n // Handle array input (from extractAssociationIds)\n if (is_array($opportunityAssociations)) {\n return $opportunityAssociations;\n }\n\n // Handle CollectionResponseAssociatedId object\n if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {\n foreach ($opportunityAssociations->getResults() as $association) {\n $associationData[] = $association->getId();\n }\n }\n\n return $associationData;\n }\n\n private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity\n {\n if (empty($crmData['properties'])) {\n return null;\n }\n\n $crmId = (string) $crmData['id'];\n $properties = $crmData['properties'];\n $associations = $crmData['associations'] ?? [];\n\n $opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(\n $this->config,\n $crmId\n );\n\n if ($opportunityExists) {\n return $this->updateOpportunity($crmId, $properties, $associations);\n } else {\n return $this->createOpportunity($crmId, $properties, $associations);\n }\n }\n\n /**\n * Create new opportunity\n */\n private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n if (! $accountId) {\n return null;\n }\n\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n if (! $businessProcess) {\n return null;\n }\n\n $stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);\n if (! $stage) {\n return null;\n }\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n if ($opportunity->wasRecentlyCreated) {\n MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());\n }\n\n return $opportunity;\n }\n\n /**\n * Update existing opportunity\n */\n private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n $stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->updateOpportunityAssociations($opportunity, $associations);\n\n return $opportunity;\n }\n\n private function resolveAccountId(array $associations): ?int\n {\n if (! empty($associations['accountId'])) {\n return $associations['accountId'];\n }\n\n if (empty($associations)) {\n return null;\n }\n\n // we can't resolve multiple account ids (currently SDK returns one company)\n foreach ($associations['companies'] as $accountId) {\n return $accountId;\n }\n\n return null;\n }\n\n private function buildOpportunityData(\n array $properties,\n ?int $accountId,\n ?BusinessProcess $businessProcess,\n ?Stage $stage\n ): array {\n $ownerId = null;\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $name = 'Unknown';\n if (isset($properties['dealname'])) {\n $name = mb_strimwidth($properties['dealname'], 0, 128);\n }\n\n $amount = $this->resolveAmount($properties);\n $currency = $properties['deal_currency_code'] ?? null;\n\n $closeDate = null;\n if (! empty($properties['closedate'])) {\n $closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');\n }\n\n $remotelyCreatedAt = null;\n if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {\n $date = $this->parseCleanDatetime($properties['createdate']);\n $remotelyCreatedAt = $date?->format('Y-m-d H:i:s');\n }\n\n $closedStages = $this->getClosedDealStages();\n $isWon = in_array($properties['dealstage'], $closedStages['won']);\n $isLost = in_array($properties['dealstage'], $closedStages['lost']);\n\n $data = [\n 'team_id' => $this->team->getId(),\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'value' => ! empty($amount) ? $amount : null,\n 'currency_code' => CurrencyFormatter::formatCode($currency),\n 'close_date' => $closeDate,\n 'is_closed' => $isWon || $isLost,\n 'is_won' => $isWon,\n 'remotely_created_at' => $remotelyCreatedAt,\n 'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),\n 'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),\n ];\n\n if ($accountId) {\n $data['account_id'] = $accountId;\n }\n\n if ($stage) {\n $data['stage_id'] = $stage->id;\n }\n\n if ($businessProcess) {\n $recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);\n if ($recordType) {\n $data['record_type_id'] = $recordType->id;\n }\n }\n\n return $data;\n }\n\n private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess\n {\n if ($pipelineId === null) {\n return null;\n }\n\n if (isset($this->cachedBusinessProcesses[$pipelineId])) {\n return $this->cachedBusinessProcesses[$pipelineId];\n }\n\n $businessProcess = $this->getBusinessProcess($pipelineId);\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->importStages();\n $businessProcess = $this->getBusinessProcess($pipelineId);\n }\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->logger->info(\n '[HubSpot] Deal is not attached to a pipeline',\n [\n 'pipeline' => $pipelineId]\n );\n }\n\n $this->cachedBusinessProcesses[$pipelineId] = $businessProcess;\n\n return $businessProcess;\n }\n\n private function getBusinessProcess(string $pipelineId): ?BusinessProcess\n {\n return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);\n }\n\n private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage\n {\n if (empty($stageId)) {\n return null;\n }\n\n $cacheKey = $businessProcess->getId() . ':' . $stageId;\n if (isset($this->cachedStages[$cacheKey])) {\n return $this->cachedStages[$cacheKey];\n }\n\n $stage = $this->crmEntityRepository->getPipelineStageByConditions(\n $businessProcess,\n [\n 'crm_provider_id' => $stageId,\n 'type' => Stage::TYPE_OPPORTUNITY,\n ]\n );\n\n if ($stage === null) {\n $this->importStages(null, $stageId);\n }\n\n if ($stage === null) {\n $this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);\n }\n\n $this->cachedStages[$cacheKey] = $stage;\n\n return $stage;\n }\n\n private function resolveAmount(array $properties): ?string\n {\n $amount = null;\n if (! empty($properties['amount'])) {\n $amount = str_replace(',', '', $properties['amount']);\n }\n\n if ($this->config->hasDefaultCurrencyFieldSet()) {\n $valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();\n $amount = $properties[$valueFieldName] ?? $amount;\n }\n\n return $amount;\n }\n\n private function parseCleanDatetime(string $datetime): ?Carbon\n {\n // Treat pre-1980 values as invalid\n $minValidDate = Carbon::parse('1980-01-01 00:00:00');\n\n try {\n $date = Carbon::parse($datetime);\n\n if ($minValidDate->gt($date)) {\n return null;\n }\n\n return $date;\n } catch (Exception) {\n return null; // On parse error, treat as null\n }\n }\n\n private function resolveDealProbability(?string $stageProbability): int\n {\n if ($stageProbability === null) {\n return 0;\n }\n\n $probability = (float) $stageProbability;\n\n return $probability > 1 ? 0 : (int) ($probability * 100);\n }\n\n private function resolveForecastCategory(?string $forecastCategory): string\n {\n if (! $forecastCategory) {\n return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;\n }\n\n $forecastCategory = str_replace('_', ' ', $forecastCategory);\n\n return ucwords(strtolower($forecastCategory));\n }\n\n private function importExternalFieldData(array $properties, int $opportunityId): void\n {\n $crmFields = $this->getOpportunitySyncableFields();\n $this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);\n }\n\n private function importOpportunityContacts(Opportunity $opportunity, array $associations): void\n {\n // Handle empty or missing contact associations\n if (empty($associations)) {\n // Remove all existing contact associations if none provided\n $this->removeAllOpportunityContacts($opportunity);\n\n return;\n }\n\n // Use differential sync approach for better performance and accuracy\n $this->syncOpportunityContactsDifferential($opportunity, $associations);\n }\n\n /**\n * Sync opportunity contacts using differential approach\n * This compares current vs new associations and only makes necessary changes\n */\n private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void\n {\n $currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);\n $contactAssociationIds = array_keys($contactAssociations);\n\n $contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);\n $contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);\n\n if (empty($contactsToAdd) && empty($contactsToRemove)) {\n return;\n }\n\n $this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);\n\n $this->removeContactAssociations($opportunity, $contactsToRemove);\n $this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);\n }\n\n private function getCurrentContactCrmIds(Opportunity $opportunity): array\n {\n return $opportunity->contacts()\n ->pluck('contacts.crm_provider_id')\n ->toArray();\n }\n\n private function logContactAssociationChanges(\n Opportunity $opportunity,\n array $currentContactCrmIds,\n array $contactAssociations,\n array $contactsToAdd,\n array $contactsToRemove\n ): void {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [\n 'opportunity_id' => $opportunity->getId(),\n 'current_contacts' => $currentContactCrmIds,\n 'new_contacts' => $contactAssociations,\n 'contacts_to_add' => $contactsToAdd,\n 'contacts_to_remove' => $contactsToRemove,\n ]);\n }\n\n private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void\n {\n if (empty($contactsToRemove)) {\n return;\n }\n\n $contactsToDetach = $opportunity->contacts()\n ->whereIn('contacts.crm_provider_id', $contactsToRemove)\n ->pluck('contacts.id')\n ->toArray();\n\n if (! empty($contactsToDetach)) {\n $opportunity->contacts()->detach($contactsToDetach);\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_contact_crm_ids' => $contactsToRemove,\n 'removed_contact_count' => count($contactsToDetach),\n ]);\n }\n }\n\n private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void\n {\n if (empty($contactsToAdd)) {\n return;\n }\n\n $contactsAdded = [];\n foreach ($contactsToAdd as $crmId) {\n $id = $contactAssociations[$crmId];\n\n if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {\n $contactsAdded[] = $crmId;\n }\n }\n\n $this->logAddedContacts($opportunity, $contactsAdded);\n }\n\n private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);\n\n if (! $contact) {\n return false;\n }\n\n return $this->performContactAttachment($opportunity, $contact, $crmId);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [\n 'opportunity_id' => $opportunity->getId(),\n 'contact_crm_id' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool\n {\n try {\n $opportunity->contacts()->attach($contact->getId(), [\n 'crm_provider_id' => $crmId,\n ]);\n\n return true;\n } catch (\\Illuminate\\Database\\QueryException $e) {\n if (str_contains($e->getMessage(), 'Duplicate entry')) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [\n 'contact_id' => $contact->getId(),\n 'contact_crm_id' => $crmId,\n 'opportunity_id' => $opportunity->getId(),\n ]);\n\n return false;\n }\n\n throw $e;\n }\n }\n\n private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void\n {\n if (! empty($contactsAdded)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'contacts_to_add_count' => count($contactsAdded),\n 'added_contact_crm_ids' => $contactsAdded,\n 'added_contacts_count' => count($contactsAdded),\n ]);\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
1155562738616735296
|
-8178087273554472666
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20692-fix-integration- Project: faVsco.js, menu
JY-20692-fix-integration-app-[API_KEY], menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
cachedStages
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
2/4
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Code changed:
Hide
Sync Changes
Hide This Notification
33
2
19
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use HubSpot\Client\Crm\Deals\Model\CollectionResponseAssociatedId;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Models\Account;
use Exception;
use Jiminny\Component\DealInsights\Forecast\Forecast;
use Jiminny\Jobs\Crm\MatchActivitiesToNewOpportunity;
use Jiminny\Models\Contact;
use Jiminny\Models\Crm\BusinessProcess;
use Jiminny\Exceptions\CrmException;
use Jiminny\Models\Opportunity;
use Illuminate\Support\Collection;
use Jiminny\Models\Stage;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Crm\Hubspot\DealFieldsService;
use Jiminny\Services\Crm\Hubspot\OpportunitySyncStrategy\HubspotSingleSyncStrategy;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Services\Crm\OpportunitySyncStrategyResolver;
use Jiminny\Utils\CurrencyFormatter;
/**
* Optimized sync methods for better performance
* These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains
*/
trait OpportunitySyncTrait
{
private const int BATCH_SIZE = 100;
private const int BATCH_PROCESS_SIZE = 800;
protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;
protected CrmEntityRepository $crmEntityRepository;
protected DealFieldsService $dealFieldsService;
private ?array $cachedClosedDealStages = null;
private array $cachedBusinessProcesses = [];
private array $cachedStages = [];
public function syncOpportunities(array $parameters, ?string $strategy = null): int
{
$strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);
$parameters['config'] = $this->config;
$syncCount = 0;
$reportedTotal = 0;
$lastSyncedId = [];
try {
foreach ($strategies as $strategyName => $syncStrategy) {
$this->logger->info(
'[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .
$strategyName
);
$total = 0;
$lastId = null;
$buffer = [];
// HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies
foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {
$buffer[] = $hsOpportunity;
// process every 800 rows (fits < 1 000 association limit)
if (\count($buffer) >= self::BATCH_PROCESS_SIZE) {
$syncCount += $this->processOpportunityBatch($buffer);
$buffer = [];
}
}
// leftovers
if ($buffer) {
$syncCount += $this->processOpportunityBatch($buffer);
}
$reportedTotal += $total;
$lastSyncedId = $lastId;
}
} catch (\HubSpot\Client\Crm\Deals\ApiException | CrmException $e) {
$this->handleSyncException($e, $parameters);
}
$this->logger->info(
'[HubSpot] Synced opportunities',
[
'team' => $this->team->getId(),
'sync_count' => $syncCount,
'total' => $reportedTotal,
'last_synced_id' => $lastSyncedId,
]
);
return $reportedTotal;
}
private function handleSyncException(\Throwable $e, array $parameters): void
{
if (($parameters['since'] ?? null) instanceof Carbon) {
$parameters['since'] = $parameters['since']->toDateTimeString();
}
$parameters['config'] = $this->config->getId();
$this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [
'teamId' => $this->team->getUuid(),
'parameters' => $parameters,
'reason' => $e->getMessage(),
]);
}
/**
* @inheritdoc
*/
public function syncOpportunity(string $crmId): ?Opportunity
{
$strategy = $this->opportunitySyncStrategyResolver->resolve(
$this->config,
OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,
);
$parameters = [
'config' => $this->config,
'crm_id' => $crmId,
];
try {
if (! $strategy instanceof HubspotSingleSyncStrategy) {
throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');
}
$hsOpportunity = $strategy->fetchOpportunity($parameters);
} catch (\HubSpot\Client\Crm\Deals\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
$hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);
return $this->importOrUpdateOpportunity($hsOpportunity);
}
/**
* Process webhook-collected opportunity batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportOpportunityBatch jobs for batch processing.
*
* @return int Number of opportunity IDs dispatched to jobs
*/
public function batchSyncOpportunities(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,
$configId
);
}
/**
* Import a batch of opportunities by their CRM IDs.
* Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().
*
* @param array<string> $crmIds HubSpot deal CRM IDs
*
* @return array{success: array, failed_ids: array, errors?: array<string, string>}
*/
public function importOpportunityBatchByIds(array $crmIds): array
{
$fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);
$allDeals = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
$deals = $this->client->getOpportunitiesByIds($chunk, $fields);
foreach ($deals as $deal) {
$allDeals[] = $deal;
}
}
// IDs not returned by HubSpot are likely deleted or inaccessible deals.
// These are not failures — retrying won't bring them back.
$fetchedIds = array_map('strval', array_column($allDeals, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allDeals),
]);
}
if (empty($allDeals)) {
return ['success' => [], 'failed_ids' => []];
}
return $this->importOpportunityBatch($allDeals);
}
private function getClosedDealStages(): array
{
if ($this->cachedClosedDealStages !== null) {
return $this->cachedClosedDealStages;
}
$stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);
$data = [
'lost' => [],
'won' => [],
];
foreach ($stages as $stage) {
if ($stage->probability == 0.00) {
$data['lost'][] = $stage->crm_provider_id;
}
if ($stage->probability == 100.00) {
$data['won'][] = $stage->crm_provider_id;
}
}
$this->cachedClosedDealStages = $data;
return $data;
}
/**
* Import deals into the database with pre-fetched associations.
*
* API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT
* caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()
* where Laravel retries the whole job with backoff. After all retries exhausted,
* failed() requeues all IDs to Redis.
*
* The per-deal loop catches exceptions individually. A deal can end up in three states:
* - success: imported/updated successfully
* - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)
* These are permanent issues — retrying won't fix them.
* - skipped (null): missing dependencies (no account, unknown pipeline/stage).
* This is acceptable — the deal cannot be imported until those exist.
*/
private function importOpportunityBatch(array $deals): array
{
$syncedOpportunities = [
'success' => [],
'failed_ids' => [],
];
$dealIds = array_column($deals, 'id');
// Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the
// queue job retries the whole batch and eventually requeues all deal IDs back to Redis.
try {
$companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');
$contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');
$associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);
$existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(
$this->config,
array_map('strval', $dealIds)
);
$existingCrmIdSet = array_flip($existingCrmIds);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [
'teamId' => $this->team->getId(),
'dealCount' => count($dealIds),
'error' => $e->getMessage(),
]);
throw $e;
}
foreach ($deals as $deal) {
try {
$deal['associations'] = $this->prepareAssociationsForOpportunity(
$deal['id'],
$companyAssociations,
$contactAssociations,
$associationsData
);
$syncedOpportunity = $this->importOrUpdateOpportunity(
$deal,
isset($existingCrmIdSet[(string) $deal['id']])
);
if ($syncedOpportunity) {
$syncedOpportunities['success'][] = $syncedOpportunity;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [
'teamId' => $this->team->getId(),
'crmId' => $deal['id'],
'error' => $e->getMessage(),
]);
$syncedOpportunities['failed_ids'][] = $deal['id'];
$syncedOpportunities['errors'][$deal['id']] = $e->getMessage();
}
}
return $syncedOpportunities;
}
/**
* Prepare associated entities for opportunities with optimized batch processing
* Returns structured data with CRM ID to DB ID mappings for each opportunity
*/
private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array
{
// Step 1: Collect all unique company and contact IDs from associations
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
// Step 2: Batch sync missing entities and get CRM ID to DB ID mappings
$companyIdMappings = [];
$contactIdMappings = [];
if (! empty($allCompanyIds)) {
$companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);
}
if (! empty($allContactIds)) {
$contactIdMappings = $this->prepareAssociatedContacts($allContactIds);
}
return [
'company_id_mappings' => $companyIdMappings,
'contact_id_mappings' => $contactIdMappings,
];
}
/**
* Flatten association data to get unique IDs
*/
private function flattenAssociationIds(array $associations): array
{
$ids = [];
foreach ($associations as $dealAssociations) {
if (is_array($dealAssociations)) {
foreach ($dealAssociations as $id) {
$ids[$id] = true;
}
}
}
return array_keys($ids);
}
/**
* Batch sync missing accounts
*/
private function prepareAssociatedAccounts(array $companyIds): array
{
// Find which accounts already exist
$existingAccounts = $this->crmEntityRepository
->findAccountsByExternalIds($this->config, $companyIds);
$existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();
$existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {
return [$account->getCrmProviderId() => $account->getId()];
})->toArray();
$missingCompanyIds = array_diff($companyIds, $existingCompanyIds);
if (empty($missingCompanyIds)) {
return $existingAccountsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [
'teamId' => $this->team->getUuid(),
'total_companies' => count($companyIds),
'existing_companies' => count($existingCompanyIds),
'missing_companies' => count($missingCompanyIds),
]);
// we already have limit on opportunity ids count
// Initialize variable before try block
$syncedAccountsData = [];
try {
$syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [
'size' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
$syncedAccountsData = [];
}
return $existingAccountsData + $syncedAccountsData;
}
/**
* Prepare associated contacts - find existing and sync missing ones
* Returns mapping of CRM ID to DB ID
*/
private function prepareAssociatedContacts(array $contactIds): array
{
// Find which contacts already exist
$existingContacts = $this->crmEntityRepository
->findContactsByExternalIds($this->config, $contactIds);
$existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();
// Create mapping for existing contacts
$existingContactsData = $existingContacts->mapWithKeys(function ($contact) {
return [$contact->getCrmProviderId() => $contact->getId()];
})->toArray();
$missingContactIds = array_diff($contactIds, $existingContactIds);
if (empty($missingContactIds)) {
return $existingContactsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [
'teamId' => $this->team->getUuid(),
'total_contacts' => count($contactIds),
'existing_contacts' => count($existingContactIds),
'missing_contacts' => count($missingContactIds),
]);
// Sync missing contacts using batch API
try {
$syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [
'size' => count($missingContactIds),
'error' => $e->getMessage(),
]);
$syncedContactsData = [];
}
return $existingContactsData + $syncedContactsData;
}
private function batchSyncCrmObjects(string $objectType, array $crmIds): array
{
$syncObjects = [];
$crmObjectIds = array_values($crmIds);
foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {
try {
$objects = $objectType === 'companies' ?
$this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :
$this->client->getContactsByIds($chunk, $this->getContactFields());
foreach ($objects as $objectId => $objectData) {
$this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [
'requested_count' => count($chunk),
'synced_count' => count($objects),
]);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [
'ids' => $chunk,
'error' => $e->getMessage(),
]);
}
}
return $syncObjects;
}
private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void
{
try {
$object = $objectType === 'companies' ?
$this->importAccount($objectData) :
$this->importContact($objectData);
if ($object) {
$syncObjects[$object->getCrmProviderId()] = $object->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [
'id' => $objectId,
'error' => $e->getMessage(),
]);
}
}
/**
* Prepare associations for a single opportunity
*
* The return value is an array with the following structure:
* [
* 'companies' => [
* $companyCrmId => $companyId,
* ...
* ],
* 'contacts' => [
* $contactCrmId => $contactId,
* ...
* ],
* 'account_id' => $accountId,
* ]
*/
private function prepareAssociationsForOpportunity(
string $oppCrmId,
array $companyAssociations,
array $contactAssociations,
array $associationsData
): array {
$associations = [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
$oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];
foreach ($oppCompanyIds as $companyCrmId) {
if (isset($associationsData['company_id_mappings'][$companyCrmId])) {
$associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];
// Set primary account (first company becomes primary account)
if ($associations['account_id'] === null) {
$associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];
}
}
}
$oppContactIds = $contactAssociations[$oppCrmId] ?? [];
foreach ($oppContactIds as $contactCrmId) {
if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {
$associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];
}
}
return $associations;
}
/**
* Update only associations for an opportunity
*/
private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void
{
// Update contact associations
$this->importOpportunityContacts($opportunity, $associations['contacts']);
// Update company (account) associations
$this->updateOpportunityAccount($opportunity, $associations['account_id']);
}
/**
* Remove all contact associations from an opportunity
*/
private function removeAllOpportunityContacts(Opportunity $opportunity): void
{
$currentCount = (int) $opportunity->contacts()->count();
if ($currentCount > 0) {
$opportunity->contacts()->detach();
$this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_count' => $currentCount,
]);
}
}
private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void
{
if ($accountId === null) {
// No account ID provided - keep current account
return;
}
$currentAccountId = $opportunity->getAccountId();
// Only update if account has changed
if ($currentAccountId !== $accountId) {
$opportunity->account_id = $accountId;
$opportunity->save();
$this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [
'opportunity_id' => $opportunity->getId(),
'old_account_id' => $currentAccountId,
'new_account_id' => $accountId,
]);
}
}
/**
* Find existing opportunities by external IDs (OPTIMIZED VERSION)
* Uses batch query for better performance
*/
private function findExistingOpportunities(array $crmIds): Collection
{
return $this->crmEntityRepository
->findOpportunitiesByExternalIds($this->config, $crmIds);
}
private function processOpportunityBatch(array $opportunities): int
{
$syncedOpportunities = $this->importOpportunityBatch($opportunities);
return count($syncedOpportunities['success'] ?? []);
}
/**
* Convert single deal associations from HubSpot format to internal format
* Handles both HubSpot SDK objects and array formats
*
* @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed
*
* @return array Processed associations with DB IDs
*/
private function convertDealAssociations(array $opportunityAssociations): array
{
$associations = $this->initializeAssociationsStructure();
if (empty($opportunityAssociations)) {
return $associations;
}
$associationIds = $this->extractAssociationIds($opportunityAssociations);
$this->processCompanyAssociations($associationIds, $associations);
$this->processContactAssociations($associationIds, $associations);
return $associations;
}
private function initializeAssociationsStructure(): array
{
return [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
}
private function extractAssociationIds(array $opportunityAssociations): array
{
$associationIds = [];
foreach ($opportunityAssociations as $type => $associationData) {
if (! empty($associationData)) {
$associationIds[$type] = $this->convertSingleDealAssociations($associationData);
}
}
return $associationIds;
}
private function processCompanyAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['companies'])) {
return;
}
$companyId = $associationIds['companies'][0];
$account = $this->findOrSyncAccount($companyId);
if ($account instanceof Account) {
$associations['companies'][$companyId] = $account->getId();
$associations['account_id'] = $account->getId();
}
}
private function processContactAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['contacts'])) {
return;
}
foreach ($associationIds['contacts'] as $contactId) {
$contact = $this->findOrSyncContact($contactId);
if ($contact instanceof Contact) {
$associations['contacts'][$contactId] = $contact->getId();
}
}
}
private function findOrSyncAccount(string $companyId): ?Account
{
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);
if (! $account instanceof Account) {
$account = $this->syncAccount($companyId);
}
return $account;
}
private function findOrSyncContact(string $contactId): ?Contact
{
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);
if (! $contact instanceof Contact) {
$contact = $this->syncContact($contactId);
}
return $contact;
}
private function convertSingleDealAssociations($opportunityAssociations = null): array
{
$associationData = [];
if ($opportunityAssociations === null) {
return $associationData;
}
// Handle array input (from extractAssociationIds)
if (is_array($opportunityAssociations)) {
return $opportunityAssociations;
}
// Handle CollectionResponseAssociatedId object
if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {
foreach ($opportunityAssociations->getResults() as $association) {
$associationData[] = $association->getId();
}
}
return $associationData;
}
private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity
{
if (empty($crmData['properties'])) {
return null;
}
$crmId = (string) $crmData['id'];
$properties = $crmData['properties'];
$associations = $crmData['associations'] ?? [];
$opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(
$this->config,
$crmId
);
if ($opportunityExists) {
return $this->updateOpportunity($crmId, $properties, $associations);
} else {
return $this->createOpportunity($crmId, $properties, $associations);
}
}
/**
* Create new opportunity
*/
private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity
{
$accountId = $this->resolveAccountId($associations);
if (! $accountId) {
return null;
}
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
if (! $businessProcess) {
return null;
}
$stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);
if (! $stage) {
return null;
}
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->importOpportunityContacts($opportunity, $associations['contacts']);
if ($opportunity->wasRecentlyCreated) {
MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());
}
return $opportunity;
}
/**
* Update existing opportunity
*/
private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity
{
$accountId = $this->resolveAccountId($associations);
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
$stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->updateOpportunityAssociations($opportunity, $associations);
return $opportunity;
}
private function resolveAccountId(array $associations): ?int
{
if (! empty($associations['accountId'])) {
return $associations['accountId'];
}
if (empty($associations)) {
return null;
}
// we can't resolve multiple account ids (currently SDK returns one company)
foreach ($associations['companies'] as $accountId) {
return $accountId;
}
return null;
}
private function buildOpportunityData(
array $properties,
?int $accountId,
?BusinessProcess $businessProcess,
?Stage $stage
): array {
$ownerId = null;
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$name = 'Unknown';
if (isset($properties['dealname'])) {
$name = mb_strimwidth($properties['dealname'], 0, 128);
}
$amount = $this->resolveAmount($properties);
$currency = $properties['deal_currency_code'] ?? null;
$closeDate = null;
if (! empty($properties['closedate'])) {
$closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');
}
$remotelyCreatedAt = null;
if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {
$date = $this->parseCleanDatetime($properties['createdate']);
$remotelyCreatedAt = $date?->format('Y-m-d H:i:s');
}
$closedStages = $this->getClosedDealStages();
$isWon = in_array($properties['dealstage'], $closedStages['won']);
$isLost = in_array($properties['dealstage'], $closedStages['lost']);
$data = [
'team_id' => $this->team->getId(),
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => $name,
'value' => ! empty($amount) ? $amount : null,
'currency_code' => CurrencyFormatter::formatCode($currency),
'close_date' => $closeDate,
'is_closed' => $isWon || $isLost,
'is_won' => $isWon,
'remotely_created_at' => $remotelyCreatedAt,
'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),
'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),
];
if ($accountId) {
$data['account_id'] = $accountId;
}
if ($stage) {
$data['stage_id'] = $stage->id;
}
if ($businessProcess) {
$recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);
if ($recordType) {
$data['record_type_id'] = $recordType->id;
}
}
return $data;
}
private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess
{
if ($pipelineId === null) {
return null;
}
if (isset($this->cachedBusinessProcesses[$pipelineId])) {
return $this->cachedBusinessProcesses[$pipelineId];
}
$businessProcess = $this->getBusinessProcess($pipelineId);
if (! $businessProcess instanceof BusinessProcess) {
$this->importStages();
$businessProcess = $this->getBusinessProcess($pipelineId);
}
if (! $businessProcess instanceof BusinessProcess) {
$this->logger->info(
'[HubSpot] Deal is not attached to a pipeline',
[
'pipeline' => $pipelineId]
);
}
$this->cachedBusinessProcesses[$pipelineId] = $businessProcess;
return $businessProcess;
}
private function getBusinessProcess(string $pipelineId): ?BusinessProcess
{
return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);
}
private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage
{
if (empty($stageId)) {
return null;
}
$cacheKey = $businessProcess->getId() . ':' . $stageId;
if (isset($this->cachedStages[$cacheKey])) {
return $this->cachedStages[$cacheKey];
}
$stage = $this->crmEntityRepository->getPipelineStageByConditions(
$businessProcess,
[
'crm_provider_id' => $stageId,
'type' => Stage::TYPE_OPPORTUNITY,
]
);
if ($stage === null) {
$this->importStages(null, $stageId);
}
if ($stage === null) {
$this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);
}
$this->cachedStages[$cacheKey] = $stage;
return $stage;
}
private function resolveAmount(array $properties): ?string
{
$amount = null;
if (! empty($properties['amount'])) {
$amount = str_replace(',', '', $properties['amount']);
}
if ($this->config->hasDefaultCurrencyFieldSet()) {
$valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();
$amount = $properties[$valueFieldName] ?? $amount;
}
return $amount;
}
private function parseCleanDatetime(string $datetime): ?Carbon
{
// Treat pre-1980 values as invalid
$minValidDate = Carbon::parse('1980-01-01 00:00:00');
try {
$date = Carbon::parse($datetime);
if ($minValidDate->gt($date)) {
return null;
}
return $date;
} catch (Exception) {
return null; // On parse error, treat as null
}
}
private function resolveDealProbability(?string $stageProbability): int
{
if ($stageProbability === null) {
return 0;
}
$probability = (float) $stageProbability;
return $probability > 1 ? 0 : (int) ($probability * 100);
}
private function resolveForecastCategory(?string $forecastCategory): string
{
if (! $forecastCategory) {
return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;
}
$forecastCategory = str_replace('_', ' ', $forecastCategory);
return ucwords(strtolower($forecastCategory));
}
private function importExternalFieldData(array $properties, int $opportunityId): void
{
$crmFields = $this->getOpportunitySyncableFields();
$this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);
}
private function importOpportunityContacts(Opportunity $opportunity, array $associations): void
{
// Handle empty or missing contact associations
if (empty($associations)) {
// Remove all existing contact associations if none provided
$this->removeAllOpportunityContacts($opportunity);
return;
}
// Use differential sync approach for better performance and accuracy
$this->syncOpportunityContactsDifferential($opportunity, $associations);
}
/**
* Sync opportunity contacts using differential approach
* This compares current vs new associations and only makes necessary changes
*/
private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void
{
$currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);
$contactAssociationIds = array_keys($contactAssociations);
$contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);
$contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);
if (empty($contactsToAdd) && empty($contactsToRemove)) {
return;
}
$this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);
$this->removeContactAssociations($opportunity, $contactsToRemove);
$this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);
}
private function getCurrentContactCrmIds(Opportunity $opportunity): array
{
return $opportunity->contacts()
->pluck('contacts.crm_provider_id')
->toArray();
}
private function logContactAssociationChanges(
Opportunity $opportunity,
array $currentContactCrmIds,
array $contactAssociations,
array $contactsToAdd,
array $contactsToRemove
): void {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [
'opportunity_id' => $opportunity->getId(),
'current_contacts' => $currentContactCrmIds,
'new_contacts' => $contactAssociations,
'contacts_to_add' => $contactsToAdd,
'contacts_to_remove' => $contactsToRemove,
]);
}
private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void
{
if (empty($contactsToRemove)) {
return;
}
$contactsToDetach = $opportunity->contacts()
->whereIn('contacts.crm_provider_id', $contactsToRemove)
->pluck('contacts.id')
->toArray();
if (! empty($contactsToDetach)) {
$opportunity->contacts()->detach($contactsToDetach);
$this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_contact_crm_ids' => $contactsToRemove,
'removed_contact_count' => count($contactsToDetach),
]);
}
}
private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void
{
if (empty($contactsToAdd)) {
return;
}
$contactsAdded = [];
foreach ($contactsToAdd as $crmId) {
$id = $contactAssociations[$crmId];
if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {
$contactsAdded[] = $crmId;
}
}
$this->logAddedContacts($opportunity, $contactsAdded);
}
private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool
{
try {
$contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);
if (! $contact) {
return false;
}
return $this->performContactAttachment($opportunity, $contact, $crmId);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [
'opportunity_id' => $opportunity->getId(),
'contact_crm_id' => $crmId,
'error' => $e->getMessage(),
]);
return false;
}
}
private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool
{
try {
$opportunity->contacts()->attach($contact->getId(), [
'crm_provider_id' => $crmId,
]);
return true;
} catch (\Illuminate\Database\QueryException $e) {
if (str_contains($e->getMessage(), 'Duplicate entry')) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [
'contact_id' => $contact->getId(),
'contact_crm_id' => $crmId,
'opportunity_id' => $opportunity->getId(),
]);
return false;
}
throw $e;
}
}
private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void
{
if (! empty($contactsAdded)) {
$this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [
'opportunity_id' => $opportunity->getId(),
'contacts_to_add_count' => count($contactsAdded),
'added_contact_crm_ids' => $contactsAdded,
'added_contacts_count' => count($contactsAdded),
]);
}
}
}...
|
48259
|
|
48161
|
NULL
|
0
|
2026-04-17T12:26:58.705848+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776428818705_m1.jpg...
|
NULL
|
NULL
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
iTerm2ShellEditViewSessionScriptsProfilesWindowHel iTerm2ShellEditViewSessionScriptsProfilesWindowHelplihol• Tech Day Review - in 1h 34 m100% <478DEV (docker)X4DOCKER• ₴1DEV (docker)APP (-zsh)• ·3-zshconfigcachecompiledeventsroutesviewsjiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00:stoppedjiminny-worker-processing-2:jiminny-worker-processing-2_00: stoppedjiminny-worker-processing-3:jiminny-worker-processing-3_00: stoppedjiminny-worker-processing-4:jiminny-worker-processing-4_00:stoppedjiminny-worker-processing-5:jiminny-worker-processing-5_00: stoppedworker-analytics:worker-analytics_00: stoppedworker-crm-update:worker-crm-update_00: stoppedworker-download:worker-download_00: stoppedworker-nudges:worker-nudges_00: stoppedworker:worker_00: stoppedworker-audio:worker-audio_00: stoppedworker-calendar:worker-calendar_00: stoppedworker-emails:worker-emails_00: stoppedjiminny-worker-processing-1:jiminny-worker-processing-1_00: stoppedworker-conferences:worker-conferences_00: stoppedworker-crm-sync:worker-crm-sync_00: stoppedworker-es-update:worker-es-update_00: stoppedartisan-schedule:artisan-schedule_00: stoppedartisan-schedule:artisan-schedule_00: startedjiminny-worker-processing-1:jiminny-worker-processing-1_00: startedjiminny-worker-processing-2:jiminny-worker-processing-2_00: startedjiminny-worker-processing-3:jiminny-worker-processing-3_00: startedjiminny-worker-processing-4:jiminny-worker-processing-4_00: startedjiminny-worker-processing-5:jiminny-worker-processing-5_00: startedjiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00:startedworker:worker_00: startedworker-analytics:worker-analytics_00: startedworker-audio:worker-audio_00: startedworker-calendar:worker-calendar_00: startedworker-conferences:worker-conferences_00: startedworker-crm-sync:worker-crm-sync_00: startedworker-crm-update:worker-crm-update_00:startedworker-download:worker-download_00: startedworker-emails:worker-emails_00: startedworker-es-update:worker-es-update_00: startedworker-nudges:worker-nudges_00: startedroot@docker_lamp_1:/home/jiminny#•95* Review screenp...• 286ec2-user@ip-10-...19.97ms DONE88.71ms DONE5.81ms DONE4.39ms DONE4.85ms DONE26.89ms DONEO x7Fri 17 Apr 15:26:581₴81ec2-user@ip-10-...• 88DEV...
|
NULL
|
-5114380931547802001
|
NULL
|
click
|
ocr
|
NULL
|
iTerm2ShellEditViewSessionScriptsProfilesWindowHel iTerm2ShellEditViewSessionScriptsProfilesWindowHelplihol• Tech Day Review - in 1h 34 m100% <478DEV (docker)X4DOCKER• ₴1DEV (docker)APP (-zsh)• ·3-zshconfigcachecompiledeventsroutesviewsjiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00:stoppedjiminny-worker-processing-2:jiminny-worker-processing-2_00: stoppedjiminny-worker-processing-3:jiminny-worker-processing-3_00: stoppedjiminny-worker-processing-4:jiminny-worker-processing-4_00:stoppedjiminny-worker-processing-5:jiminny-worker-processing-5_00: stoppedworker-analytics:worker-analytics_00: stoppedworker-crm-update:worker-crm-update_00: stoppedworker-download:worker-download_00: stoppedworker-nudges:worker-nudges_00: stoppedworker:worker_00: stoppedworker-audio:worker-audio_00: stoppedworker-calendar:worker-calendar_00: stoppedworker-emails:worker-emails_00: stoppedjiminny-worker-processing-1:jiminny-worker-processing-1_00: stoppedworker-conferences:worker-conferences_00: stoppedworker-crm-sync:worker-crm-sync_00: stoppedworker-es-update:worker-es-update_00: stoppedartisan-schedule:artisan-schedule_00: stoppedartisan-schedule:artisan-schedule_00: startedjiminny-worker-processing-1:jiminny-worker-processing-1_00: startedjiminny-worker-processing-2:jiminny-worker-processing-2_00: startedjiminny-worker-processing-3:jiminny-worker-processing-3_00: startedjiminny-worker-processing-4:jiminny-worker-processing-4_00: startedjiminny-worker-processing-5:jiminny-worker-processing-5_00: startedjiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00:startedworker:worker_00: startedworker-analytics:worker-analytics_00: startedworker-audio:worker-audio_00: startedworker-calendar:worker-calendar_00: startedworker-conferences:worker-conferences_00: startedworker-crm-sync:worker-crm-sync_00: startedworker-crm-update:worker-crm-update_00:startedworker-download:worker-download_00: startedworker-emails:worker-emails_00: startedworker-es-update:worker-es-update_00: startedworker-nudges:worker-nudges_00: startedroot@docker_lamp_1:/home/jiminny#•95* Review screenp...• 286ec2-user@ip-10-...19.97ms DONE88.71ms DONE5.81ms DONE4.39ms DONE4.85ms DONE26.89ms DONEO x7Fri 17 Apr 15:26:581₴81ec2-user@ip-10-...• 88DEV...
|
NULL
|
|
48160
|
NULL
|
0
|
2026-04-17T12:26:57.463536+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776428817463_m2.jpg...
|
NULL
|
NULL
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorRun PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsWindowHelp, 0Tech Day Review - in 1h 34 mFV faVsco.s vT° JY-20692-fix-integration-app-[API_KEY]© AutomatedReportsService.php© SendReportJob.php© SendReportMailJob.php© ReportController.php© TokenBuilder.php= custom.log= laravel.log4 SF [jiminny@localhost]A HS_local [iminny@localhost]A console (EU]4 console [PROD] XA console [STAGING]=.prettierignoreC TeamSetupController.phppnp apl.onp• Filesystem.phpC Team.php© CreateHeldActivityEvent.phpTrackProviderInstalledEvent.phpTx: AutoPlayground=.windsurfrulesphp_ide_helper.php© RequestGenerateReportJob.php& OpportunitySyncTrait.php x© Opportunity.php€ InteractsWithPivotTable.php© OpportunityUpdated.php556SELECT * FROM teams WHERE name LIKE "%Integrum ESG%'; # 1126, 1065,php_ide_helper_models.phpQ-cacheastadesCc W .*php artisanT.8 :557select * fromopportunities where team_id = 1126;SELECT * FROMteams WHERE name LIKE'%Base%'; # 1125, 1063,composer.jsontrait OpportunitySyncTraitA33 X2 X 19select * fromopportunities where team_id = 1125;# composer.lock1017select * from contacts cI dependency-checker.isonU dev.ison1018private function resolveForecastCategory(?string $forecastCategory): stringf...}561Wnere C.ream 10 = 8021= ids.txt11O28=562SELECT * FROMactivities WHERE id = 76822967;E infection.json.dist2 usages-564SELECT * FROM crm_profiles WHERE user_id = 15440;M+ INSTALL.md1022private function importExternalFieldDataarray $properties, int SopportunityId): void565SELECT * FROM crm_profiles WHERE crm_configuration_id = 555;M+ INTERNAL_WEBHOOK _SETUP1050jiminny_storage1001=566SELECT * FROM crm_configurations WHERE id = 555;$crmFields = $this->get0pportunitySyncableFields();567SELECT * FROM users WHERE id = 15440; # team.581, gr. 15440, pl. 3911, act. field 162182M+ licenses.md1032$this->import0pportunityCrmFieldData($properties, $crmFields, $opportunityId);-568M Makerlle1033SELECTCONCAT(u.id, CASE WHEN U.id = t.owner_id THEN ' (owner)' ELSE "' END) AS user_id,1034© package-lock.json=570u.email,E phostan.neon.dist2 usages= phostan-baseline.neor1035571sa.*,private function importOpportunityContacts(Opportunity $opportunity, array $associations): voidf….<> phpunit.xml1048572t.owner_id FROM social_accounts sa573Julr users u on u.l = sa.sociaote 1ole raw sal query.sal1049**M+ README.md* Sync opportunity contacts using differential approach—574JOIN teams t1..n<->1: on t.id = u.team id075ê sonar-project.properties* This comoares current vs new associations and onlu makes necessaru changes= test.py576WHERE U. team_id = 581 and sa.provider = 'salesferce':1052‹> Untitled Diagram.xml1 usage577SELECT * FROM automated_report_results order by ia desc;578Is vetur.config.js1000private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): voidf...3579select * from features;M.WEBHOOK FILIERING IMPUE1070580select * from team_features where feature_id = 40;› ih External LibrariesTusaye581= Scratches and Consolesprivate function getCurrentContactCrmIds(Opportunity $opportunity): arrayf...}v M Database Consoles1077582select * from teams where id = 556;58.4V LEU1 usage584select * fromautomated_reports where id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , ["pdf", "podcast"]A console [EU]1078private function logContactAssociationChanges(583SELECT * FROMautomated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uvid:4 DEAL RISKS [EU]Opportunity Sopportunity,580select * fromautomated_report_results order by id desc;A DI [EU]array ncurrent contaccurillos.J0/SELECT * FROMA EU [EU]1081array scontaccassoclations588automated_report_results WHERE id = 1919;v A jiminny@localhost1082aildy scullaceslonuu,589select * from automated_report_results WHERE ceRontaid = 54;A console [jiminny@localrarray $contactsToRemove-590DI [jiminny@localhost]1084): void {...}591select * from opportunities where id = 7594349;A HS_local [jiminny@local1093A SF [jiminny@localhost]Tusdge—592c zono cevmnnvolocc1094private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void{...}-593SELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067B - jiminnyintegration@lesmills.com594 %,select * from playbooks where team_id = |11;s 080 ms.V A PRODA console [PRODI1 usageA console_1 [PROD]1116private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations):A DI [PROD]1133100% C•Fri 17 Apr 15:26:57servicesGajiminny v34 4133 Y.61Outputf # 711, 692, 16067B -...[EMAIL] x Tx,v _ Database0 rows v of 0+Tx: Auto vDDLvdtuDid Yuuid (UUID) TE team_ id T4 console 1 s 59 msv Al jiminny@localhost4142 [CREDIT_CARD]-0200-270C5Cт0574143 45408e94-cdf0-ce24-8e17-864ad78632984 SF4 HS_local4148 4bc487f9-1897-440d-b983-8ac740b3e55fV A PROD4149 4669091a-576d-61fe-b459-7ae3ec1229d5console sosu ms4248 4[PHONE]-8184-8241-14520t07CZaV A STAGINGA consoley Docker4575 [CREDIT_CARD]-2024-4220610+90954374 4d3e95d7-ab6b-f89b-a2a9-002ff12f85a712 rows retrieved starting from 1 in 731 ms (execution: 159 ms, fetching: 572 ms)QJ activity_type YBactivity_field_id Y711 event711 event711 task711 task711 event711 task711 task! name 7226147 LMUS CX Playbook226147 Zone 2 Playbook226139 Enablement Team226139 Outbound BDS226147 Zone 1 KA Playbook126159 lest226139 Testtestis_selectable YWai_activity_type_detection_enabled YI created at Y :0 2023-05-18 14:09:57012025-05-18 14:171110 2023-05-22 15:35:110 2023-05-22 16:09:530 2023-07-06 21:59:170 2023-09-11 12:33:100 2023-09-11 12:47:50CSVvupdated_at Y2025-03-31 17:39:562075-05-18 141171112025-07-24 02:33:272023-05-22 16:09:532025-07-00 22.001572025-09-11 121551142023-09-11 12:48:01+, 0, E,I deleted_at<hULl<null><null><null>2023-07-06 222025-09-11 122023-09-11 12W Windsurf Teams 594:41uir-d4 spaces...
|
NULL
|
8456147500609988541
|
NULL
|
visual_change
|
ocr
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorRun PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsWindowHelp, 0Tech Day Review - in 1h 34 mFV faVsco.s vT° JY-20692-fix-integration-app-[API_KEY]© AutomatedReportsService.php© SendReportJob.php© SendReportMailJob.php© ReportController.php© TokenBuilder.php= custom.log= laravel.log4 SF [jiminny@localhost]A HS_local [iminny@localhost]A console (EU]4 console [PROD] XA console [STAGING]=.prettierignoreC TeamSetupController.phppnp apl.onp• Filesystem.phpC Team.php© CreateHeldActivityEvent.phpTrackProviderInstalledEvent.phpTx: AutoPlayground=.windsurfrulesphp_ide_helper.php© RequestGenerateReportJob.php& OpportunitySyncTrait.php x© Opportunity.php€ InteractsWithPivotTable.php© OpportunityUpdated.php556SELECT * FROM teams WHERE name LIKE "%Integrum ESG%'; # 1126, 1065,php_ide_helper_models.phpQ-cacheastadesCc W .*php artisanT.8 :557select * fromopportunities where team_id = 1126;SELECT * FROMteams WHERE name LIKE'%Base%'; # 1125, 1063,composer.jsontrait OpportunitySyncTraitA33 X2 X 19select * fromopportunities where team_id = 1125;# composer.lock1017select * from contacts cI dependency-checker.isonU dev.ison1018private function resolveForecastCategory(?string $forecastCategory): stringf...}561Wnere C.ream 10 = 8021= ids.txt11O28=562SELECT * FROMactivities WHERE id = 76822967;E infection.json.dist2 usages-564SELECT * FROM crm_profiles WHERE user_id = 15440;M+ INSTALL.md1022private function importExternalFieldDataarray $properties, int SopportunityId): void565SELECT * FROM crm_profiles WHERE crm_configuration_id = 555;M+ INTERNAL_WEBHOOK _SETUP1050jiminny_storage1001=566SELECT * FROM crm_configurations WHERE id = 555;$crmFields = $this->get0pportunitySyncableFields();567SELECT * FROM users WHERE id = 15440; # team.581, gr. 15440, pl. 3911, act. field 162182M+ licenses.md1032$this->import0pportunityCrmFieldData($properties, $crmFields, $opportunityId);-568M Makerlle1033SELECTCONCAT(u.id, CASE WHEN U.id = t.owner_id THEN ' (owner)' ELSE "' END) AS user_id,1034© package-lock.json=570u.email,E phostan.neon.dist2 usages= phostan-baseline.neor1035571sa.*,private function importOpportunityContacts(Opportunity $opportunity, array $associations): voidf….<> phpunit.xml1048572t.owner_id FROM social_accounts sa573Julr users u on u.l = sa.sociaote 1ole raw sal query.sal1049**M+ README.md* Sync opportunity contacts using differential approach—574JOIN teams t1..n<->1: on t.id = u.team id075ê sonar-project.properties* This comoares current vs new associations and onlu makes necessaru changes= test.py576WHERE U. team_id = 581 and sa.provider = 'salesferce':1052‹> Untitled Diagram.xml1 usage577SELECT * FROM automated_report_results order by ia desc;578Is vetur.config.js1000private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): voidf...3579select * from features;M.WEBHOOK FILIERING IMPUE1070580select * from team_features where feature_id = 40;› ih External LibrariesTusaye581= Scratches and Consolesprivate function getCurrentContactCrmIds(Opportunity $opportunity): arrayf...}v M Database Consoles1077582select * from teams where id = 556;58.4V LEU1 usage584select * fromautomated_reports where id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , ["pdf", "podcast"]A console [EU]1078private function logContactAssociationChanges(583SELECT * FROMautomated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uvid:4 DEAL RISKS [EU]Opportunity Sopportunity,580select * fromautomated_report_results order by id desc;A DI [EU]array ncurrent contaccurillos.J0/SELECT * FROMA EU [EU]1081array scontaccassoclations588automated_report_results WHERE id = 1919;v A jiminny@localhost1082aildy scullaceslonuu,589select * from automated_report_results WHERE ceRontaid = 54;A console [jiminny@localrarray $contactsToRemove-590DI [jiminny@localhost]1084): void {...}591select * from opportunities where id = 7594349;A HS_local [jiminny@local1093A SF [jiminny@localhost]Tusdge—592c zono cevmnnvolocc1094private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void{...}-593SELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067B - jiminnyintegration@lesmills.com594 %,select * from playbooks where team_id = |11;s 080 ms.V A PRODA console [PRODI1 usageA console_1 [PROD]1116private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations):A DI [PROD]1133100% C•Fri 17 Apr 15:26:57servicesGajiminny v34 4133 Y.61Outputf # 711, 692, 16067B -...[EMAIL] x Tx,v _ Database0 rows v of 0+Tx: Auto vDDLvdtuDid Yuuid (UUID) TE team_ id T4 console 1 s 59 msv Al jiminny@localhost4142 [CREDIT_CARD]-0200-270C5Cт0574143 45408e94-cdf0-ce24-8e17-864ad78632984 SF4 HS_local4148 4bc487f9-1897-440d-b983-8ac740b3e55fV A PROD4149 4669091a-576d-61fe-b459-7ae3ec1229d5console sosu ms4248 4[PHONE]-8184-8241-14520t07CZaV A STAGINGA consoley Docker4575 [CREDIT_CARD]-2024-4220610+90954374 4d3e95d7-ab6b-f89b-a2a9-002ff12f85a712 rows retrieved starting from 1 in 731 ms (execution: 159 ms, fetching: 572 ms)QJ activity_type YBactivity_field_id Y711 event711 event711 task711 task711 event711 task711 task! name 7226147 LMUS CX Playbook226147 Zone 2 Playbook226139 Enablement Team226139 Outbound BDS226147 Zone 1 KA Playbook126159 lest226139 Testtestis_selectable YWai_activity_type_detection_enabled YI created at Y :0 2023-05-18 14:09:57012025-05-18 14:171110 2023-05-22 15:35:110 2023-05-22 16:09:530 2023-07-06 21:59:170 2023-09-11 12:33:100 2023-09-11 12:47:50CSVvupdated_at Y2025-03-31 17:39:562075-05-18 141171112025-07-24 02:33:272023-05-22 16:09:532025-07-00 22.001572025-09-11 121551142023-09-11 12:48:01+, 0, E,I deleted_at<hULl<null><null><null>2023-07-06 222025-09-11 122023-09-11 12W Windsurf Teams 594:41uir-d4 spaces...
|
48159
|
|
48109
|
NULL
|
0
|
2026-04-17T12:21:59.009415+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776428519009_m2.jpg...
|
NULL
|
NULL
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileFditViewNavigateCodelaravelRefactonRun PhpStormFileFditViewNavigateCodelaravelRefactonRunToolsWindowHelnFV faVsco.s vT° JY-20692-fix-integration-app-[API_KEY] vphp.php-cs-fixer.dist.phpphp.phpstorm.meta.php=.phpunit.result.cache=.prettierignoreE windsurfrulespip _lde_nelper.pnppnpce neloermoces.onepnp aruisani composer.isonI composer.locku dependency-checker.jsonu dev.json= ids.txt_Intection.son.distM-INSALL.mdM+ INTERNAL WEBHOOK SETUPliminny storageM+ licenses.mdM MakefileU package-lock.ison= phostan.neon.dist= phpstan-baseline.neon<» phpunit.xmlTaraw_sqL_query.salM+ README.mdes sonar-project.properties= test.py<> Untitled Diagram.xmlus vetur.contig.jsM-+ WEbnOOK HILIEKING_IMPLE› Ih External Librariesv = Scratches and Consolesv _ Database Consoles¿ console EUlL DEAL RISKS EUIdDIlEUdEuleUvd jiminny@localhost& console yiminny @localr4 DI liiminnv@localhosti4 HS local [iiminny@local4 SF Timinny@localhostl¿ zoho dev fiminny@locaV L PRODd console PRODd console_1 PROD« DI [PRODI›dQA7&OAI> 4 OAI PRODYESAGING& console SlAGiNg& console 1 [STAGING]L uranus (STAGINGI› _ Extensionsv _ Scratches= phpstorm_shortcuts.txt= scratch.txtfscratch 1.isonPusnea Jy-20092-rix-intecrationC AutomatedReportsService.phpC) SendReportJob.phpC SendReportMailJob.php(C) ReportController.phpG TokenBuilder.phpC TeamSetupController.phppnp apl.onp© Filesystem.phpC Team.phpC CreateHeldActivityEvent.phpC TrackProviderInstalledEvent.php= custom.log= laravel.logL SF (iminny@localhostHS_local fiminny@localhost)Tx: AutovPlaygroundv© RequestGenerateReportJob.phpwOpportunitySynctrait.php© Opportunity.php(T) InteractsWithPivotTable.ohr© OpportunityUpdated.phpCc Wcacneastaoestrait OpportunitySyncTrait433 M2 M 19select * from automated_reports where 1d = 36:select ar. freguency,r.*ar.* from automated_report_results rjoin automated_reports ar on r.report 1d = ar.1dwhere ar.trequency"one orte1101811O28102710301031103210331934103510491052100010761107810811082108410741116113311541154115511/8private function resolveForecastCategory(?string $forecastCategory): string{...}2 usagesprivate function importExternalFieldDataarray $properties, int SopportunityId): void$crmFields = Sthis->get0pportunitySyncableFieldsO:sthis->import0pportunityCrmFieldData(Sproperties, $crmFields, Sopportunityld):2 usages**private function importOpportunityContacts(Opportunity $opportunity, array $associations): void{...}* Sync opportunity contacts using differential approach* This comoares current vs new associations and onlu makes necessaru changeslusaeeprivate function syncOpportunityContactsDifferential(Opportunity Sopportunity, array $contactAssociations): void{...-562Tusaueprivate function getcurrentcontactcrmids(Opportunity sopportunity): array....h1 usageonivate tuncrion LoccontactassociamonunanoesuUoporTun so0oorunlarrav ncurrentcontaccurillos.array scontaccassoclations.aildy scullaceslonuu,array ScontactsToRemove): void i...rTusdge1 usage1 usagelusaeeprivate function removecontactAssoclations(Opportunity sopportunity, array $contactsToRemove): vold...sprivate function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations):v 586private function attachSingLeContact(Opportunity $opportunity, string $crmld, int $id): booli...s546541542154.4544545=546—541548549550_552553554556-557558559560561=563564m506507568507571314573574575576570578579581582583584585private function performcontactAttachment(Opportunity sopportunity, Contact scontact, string scrmid: booli...a—589Tusaueprivate function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void{...}select s.* from activity_searches s join users u 1..n<->1: ON s.user_id = u.id where u.team_id = 882;select x troi muoces n wnere macmury search 1oiselect * from teams where created at > "2026-03-09'•SELECT * FROM crm_ layouts WHERE crm confiquration 1d = 1065: # 1065SELECT * FROMcrm layout entities WHERE crm_ layout id = 3617:select * from users where team_1d = 1 and name Like "%LUKaS* # 7160SELEC * FROM Teams VHERE 10 E 551se lect * tron oooorunes wherTeamdo 9799oEL*-KUMteams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,select * tron0pportunlmes where tean 10 = 10.IOELE *-KUMITealil'sWHERE name LIKE '%Base%': # 1125. 1063select x TOIopportunities where team id = 1125:select * from contacts cwhere c.team_ id = 882;SELECT * FROM activities WHERE 1d = 76822967:SFF *EROMcrm_profiles WHERE User_1d = 15440;ELE * FROMcrm_profiles WHERE crm_configuration_1d = 555*AELE*-KUIMcrn_contiguratlons WHERE 10 = 5551AELE*-KUIMUsers WHERE 10 = 15440; # team. So1, gr. 15440, pL. 5711, act. TIELd 102182SELECTLUNLATU.LOrCASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,U,ellan tasa.xt.owner id FROM social accounts saJOIN users u on u.id = sa.sociable_idJOIN teams t1.n<->1: on t.id = u.team_idWHERE U.team_1d = 581 and sa.provider = "savestence":SELECT * FROM automated_report_results order by 1d desc)- 591591V574selet * tromrearures.seLect *tii opportunities (Jiminny)dib teams (jiminny)seleet xii users (J1m1nny)ii soclal accounts (J1m1nny)select *SELECT *select *SFHE*uib business_process_stages (J1m1nny)u acuivity_search_tilters (J1minny)u acuivity_searches (J1minny)uw ceam_reatures (Jiminny)l accounirs a00vsellert *iii activities jiminnyini activity availability notifications (Jiminny)select *НН ЛАФЧИЧФИпАЛІЛАРА (44тгPress - to insert, to replace Next TipSELECT * FROM erm field data WHERE object_type = "contact:PRODPRODPRODPROIDIPROIDIPKUIDIPKUUPKUDPKUUPRODPROD)44 , L"pdf","podcast".36b0e36f3131') = wwid:change // Create pull request (10 minutes ago)Tech Day Review • in 1h 39 m100% [45Fri 17 Apr 15:21:58AutomatedReportsCommandTestv& console (EU]L console PROD] X& console [STAGINGSo jiminny v34 41133 Y6lAW Windsurf Teams 593:15 0 UTF-84 spaces...
|
NULL
|
-2794968720426266944
|
NULL
|
visual_change
|
ocr
|
NULL
|
PhpStormFileFditViewNavigateCodelaravelRefactonRun PhpStormFileFditViewNavigateCodelaravelRefactonRunToolsWindowHelnFV faVsco.s vT° JY-20692-fix-integration-app-[API_KEY] vphp.php-cs-fixer.dist.phpphp.phpstorm.meta.php=.phpunit.result.cache=.prettierignoreE windsurfrulespip _lde_nelper.pnppnpce neloermoces.onepnp aruisani composer.isonI composer.locku dependency-checker.jsonu dev.json= ids.txt_Intection.son.distM-INSALL.mdM+ INTERNAL WEBHOOK SETUPliminny storageM+ licenses.mdM MakefileU package-lock.ison= phostan.neon.dist= phpstan-baseline.neon<» phpunit.xmlTaraw_sqL_query.salM+ README.mdes sonar-project.properties= test.py<> Untitled Diagram.xmlus vetur.contig.jsM-+ WEbnOOK HILIEKING_IMPLE› Ih External Librariesv = Scratches and Consolesv _ Database Consoles¿ console EUlL DEAL RISKS EUIdDIlEUdEuleUvd jiminny@localhost& console yiminny @localr4 DI liiminnv@localhosti4 HS local [iiminny@local4 SF Timinny@localhostl¿ zoho dev fiminny@locaV L PRODd console PRODd console_1 PROD« DI [PRODI›dQA7&OAI> 4 OAI PRODYESAGING& console SlAGiNg& console 1 [STAGING]L uranus (STAGINGI› _ Extensionsv _ Scratches= phpstorm_shortcuts.txt= scratch.txtfscratch 1.isonPusnea Jy-20092-rix-intecrationC AutomatedReportsService.phpC) SendReportJob.phpC SendReportMailJob.php(C) ReportController.phpG TokenBuilder.phpC TeamSetupController.phppnp apl.onp© Filesystem.phpC Team.phpC CreateHeldActivityEvent.phpC TrackProviderInstalledEvent.php= custom.log= laravel.logL SF (iminny@localhostHS_local fiminny@localhost)Tx: AutovPlaygroundv© RequestGenerateReportJob.phpwOpportunitySynctrait.php© Opportunity.php(T) InteractsWithPivotTable.ohr© OpportunityUpdated.phpCc Wcacneastaoestrait OpportunitySyncTrait433 M2 M 19select * from automated_reports where 1d = 36:select ar. freguency,r.*ar.* from automated_report_results rjoin automated_reports ar on r.report 1d = ar.1dwhere ar.trequency"one orte1101811O28102710301031103210331934103510491052100010761107810811082108410741116113311541154115511/8private function resolveForecastCategory(?string $forecastCategory): string{...}2 usagesprivate function importExternalFieldDataarray $properties, int SopportunityId): void$crmFields = Sthis->get0pportunitySyncableFieldsO:sthis->import0pportunityCrmFieldData(Sproperties, $crmFields, Sopportunityld):2 usages**private function importOpportunityContacts(Opportunity $opportunity, array $associations): void{...}* Sync opportunity contacts using differential approach* This comoares current vs new associations and onlu makes necessaru changeslusaeeprivate function syncOpportunityContactsDifferential(Opportunity Sopportunity, array $contactAssociations): void{...-562Tusaueprivate function getcurrentcontactcrmids(Opportunity sopportunity): array....h1 usageonivate tuncrion LoccontactassociamonunanoesuUoporTun so0oorunlarrav ncurrentcontaccurillos.array scontaccassoclations.aildy scullaceslonuu,array ScontactsToRemove): void i...rTusdge1 usage1 usagelusaeeprivate function removecontactAssoclations(Opportunity sopportunity, array $contactsToRemove): vold...sprivate function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations):v 586private function attachSingLeContact(Opportunity $opportunity, string $crmld, int $id): booli...s546541542154.4544545=546—541548549550_552553554556-557558559560561=563564m506507568507571314573574575576570578579581582583584585private function performcontactAttachment(Opportunity sopportunity, Contact scontact, string scrmid: booli...a—589Tusaueprivate function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void{...}select s.* from activity_searches s join users u 1..n<->1: ON s.user_id = u.id where u.team_id = 882;select x troi muoces n wnere macmury search 1oiselect * from teams where created at > "2026-03-09'•SELECT * FROM crm_ layouts WHERE crm confiquration 1d = 1065: # 1065SELECT * FROMcrm layout entities WHERE crm_ layout id = 3617:select * from users where team_1d = 1 and name Like "%LUKaS* # 7160SELEC * FROM Teams VHERE 10 E 551se lect * tron oooorunes wherTeamdo 9799oEL*-KUMteams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,select * tron0pportunlmes where tean 10 = 10.IOELE *-KUMITealil'sWHERE name LIKE '%Base%': # 1125. 1063select x TOIopportunities where team id = 1125:select * from contacts cwhere c.team_ id = 882;SELECT * FROM activities WHERE 1d = 76822967:SFF *EROMcrm_profiles WHERE User_1d = 15440;ELE * FROMcrm_profiles WHERE crm_configuration_1d = 555*AELE*-KUIMcrn_contiguratlons WHERE 10 = 5551AELE*-KUIMUsers WHERE 10 = 15440; # team. So1, gr. 15440, pL. 5711, act. TIELd 102182SELECTLUNLATU.LOrCASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,U,ellan tasa.xt.owner id FROM social accounts saJOIN users u on u.id = sa.sociable_idJOIN teams t1.n<->1: on t.id = u.team_idWHERE U.team_1d = 581 and sa.provider = "savestence":SELECT * FROM automated_report_results order by 1d desc)- 591591V574selet * tromrearures.seLect *tii opportunities (Jiminny)dib teams (jiminny)seleet xii users (J1m1nny)ii soclal accounts (J1m1nny)select *SELECT *select *SFHE*uib business_process_stages (J1m1nny)u acuivity_search_tilters (J1minny)u acuivity_searches (J1minny)uw ceam_reatures (Jiminny)l accounirs a00vsellert *iii activities jiminnyini activity availability notifications (Jiminny)select *НН ЛАФЧИЧФИпАЛІЛАРА (44тгPress - to insert, to replace Next TipSELECT * FROM erm field data WHERE object_type = "contact:PRODPRODPRODPROIDIPROIDIPKUIDIPKUUPKUDPKUUPRODPROD)44 , L"pdf","podcast".36b0e36f3131') = wwid:change // Create pull request (10 minutes ago)Tech Day Review • in 1h 39 m100% [45Fri 17 Apr 15:21:58AutomatedReportsCommandTestv& console (EU]L console PROD] X& console [STAGINGSo jiminny v34 41133 Y6lAW Windsurf Teams 593:15 0 UTF-84 spaces...
|
48107
|
|
48108
|
NULL
|
0
|
2026-04-17T12:21:37.111583+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776428497111_m1.jpg...
|
NULL
|
NULL
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
iTerm2ShellEditViewSessionScriptsProfilesWindowHel iTerm2ShellEditViewSessionScriptsProfilesWindowHelplihol• Tech Day Review - in 1 h 39 m100% <478DEV (docker)X4DOCKER• ₴1DEV (docker)APP (-zsh)• ·3-zshconfigcachecompiledeventsroutesviewsjiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00:stoppedjiminny-worker-processing-2:jiminny-worker-processing-2_00: stoppedjiminny-worker-processing-3:jiminny-worker-processing-3_00: stoppedjiminny-worker-processing-4:jiminny-worker-processing-4_00:stoppedjiminny-worker-processing-5:jiminny-worker-processing-5_00: stoppedworker-analytics:worker-analytics_00: stoppedworker-crm-update:worker-crm-update_00: stoppedworker-download:worker-download_00: stoppedworker-nudges:worker-nudges_00: stoppedworker:worker_00: stoppedworker-audio:worker-audio_00: stoppedworker-calendar:worker-calendar_00: stoppedworker-emails:worker-emails_00: stoppedjiminny-worker-processing-1:jiminny-worker-processing-1_00: stoppedworker-conferences:worker-conferences_00: stoppedworker-crm-sync:worker-crm-sync_00: stoppedworker-es-update:worker-es-update_00: stoppedartisan-schedule:artisan-schedule_00: stoppedartisan-schedule:artisan-schedule_00: startedjiminny-worker-processing-1:jiminny-worker-processing-1_00: startedjiminny-worker-processing-2:jiminny-worker-processing-2_00: startedjiminny-worker-processing-3:jiminny-worker-processing-3_00: startedjiminny-worker-processing-4:jiminny-worker-processing-4_00: startedjiminny-worker-processing-5:jiminny-worker-processing-5_00: startedjiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00:startedworker:worker_00: startedworker-analytics:worker-analytics_00: startedworker-audio:worker-audio_00: startedworker-calendar:worker-calendar_00: startedworker-conferences:worker-conferences_00: startedworker-crm-sync:worker-crm-sync_00: startedworker-crm-update:worker-crm-update_00:startedworker-download:worker-download_00: startedworker-emails:worker-emails_00: startedworker-es-update:worker-es-update_00: startedworker-nudges:worker-nudges_00: startedroot@docker_lamp_1:/home/jiminny#•95* Review screenp...• 286ec2-user@ip-10-...19.97ms DONE88.71ms DONE5.81ms DONE4.39ms DONE4.85ms DONE26.89ms DONEO x7Fri 17 Apr 15:21:361₴81ec2-user@ip-10-...• 88DEV...
|
NULL
|
8007503840566108152
|
NULL
|
click
|
ocr
|
NULL
|
iTerm2ShellEditViewSessionScriptsProfilesWindowHel iTerm2ShellEditViewSessionScriptsProfilesWindowHelplihol• Tech Day Review - in 1 h 39 m100% <478DEV (docker)X4DOCKER• ₴1DEV (docker)APP (-zsh)• ·3-zshconfigcachecompiledeventsroutesviewsjiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00:stoppedjiminny-worker-processing-2:jiminny-worker-processing-2_00: stoppedjiminny-worker-processing-3:jiminny-worker-processing-3_00: stoppedjiminny-worker-processing-4:jiminny-worker-processing-4_00:stoppedjiminny-worker-processing-5:jiminny-worker-processing-5_00: stoppedworker-analytics:worker-analytics_00: stoppedworker-crm-update:worker-crm-update_00: stoppedworker-download:worker-download_00: stoppedworker-nudges:worker-nudges_00: stoppedworker:worker_00: stoppedworker-audio:worker-audio_00: stoppedworker-calendar:worker-calendar_00: stoppedworker-emails:worker-emails_00: stoppedjiminny-worker-processing-1:jiminny-worker-processing-1_00: stoppedworker-conferences:worker-conferences_00: stoppedworker-crm-sync:worker-crm-sync_00: stoppedworker-es-update:worker-es-update_00: stoppedartisan-schedule:artisan-schedule_00: stoppedartisan-schedule:artisan-schedule_00: startedjiminny-worker-processing-1:jiminny-worker-processing-1_00: startedjiminny-worker-processing-2:jiminny-worker-processing-2_00: startedjiminny-worker-processing-3:jiminny-worker-processing-3_00: startedjiminny-worker-processing-4:jiminny-worker-processing-4_00: startedjiminny-worker-processing-5:jiminny-worker-processing-5_00: startedjiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00:startedworker:worker_00: startedworker-analytics:worker-analytics_00: startedworker-audio:worker-audio_00: startedworker-calendar:worker-calendar_00: startedworker-conferences:worker-conferences_00: startedworker-crm-sync:worker-crm-sync_00: startedworker-crm-update:worker-crm-update_00:startedworker-download:worker-download_00: startedworker-emails:worker-emails_00: startedworker-es-update:worker-es-update_00: startedworker-nudges:worker-nudges_00: startedroot@docker_lamp_1:/home/jiminny#•95* Review screenp...• 286ec2-user@ip-10-...19.97ms DONE88.71ms DONE5.81ms DONE4.39ms DONE4.85ms DONE26.89ms DONEO x7Fri 17 Apr 15:21:361₴81ec2-user@ip-10-...• 88DEV...
|
48106
|
|
48010
|
NULL
|
0
|
2026-04-17T12:16:46.636928+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776428206636_m2.jpg...
|
NULL
|
NULL
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
FirefoxFile...EoitViewHistoryBookmarksProfilesTool FirefoxFile...EoitViewHistoryBookmarksProfilesToolsWindowHelpgithub.com/jiminny/app/pull/11986bookmhlejiminny / app 8Developers | HubSpotQ Search bookmarks‹> Code82 Pull requests 30G Agents © Actions D Wiki ® Security and quality 27 Insights ộs SettingsM'inbox (1,576) - lukas.kovalik@jiminM 120216 is your HubSpot Log In CodCa CloudWatch | eu-west-1New Tabv 3o0knarks 100 oa1Sprint BoardSRD QueueGithubv E Bookmarks Menu> C Mozilla Firefox• Other Bookmarks® On April 24 we'll start using GitHub Copilot interaction data for Al model training unless you opt out. Review this update and manage your preferences in your GitHub account settings.Z Configure SSH access to multiple. fix-cache-for-business-processesAuth ProxyDashboard • Jiminny • MembraneApo "4oho CkM" • Jiminny • MemcJY-20692 change confirmatior X( JY-20543 add AJ reports User pilc+ New TabJY-20692 change confirmation parameter #11986 •87 OpenLakyLak wants to merge 1 commit into master from JY-20692-fix-integration-app-[API_KEY] 0• Commits 1 |E Checks 2# Files changed 2LakyLak commented nowwritePrevlewH B#H# JIKA. JT-LUOSL#### Changes:- Addresses breaking change, disconnected is replaced by connected.- Keeping disconnected for scenarios it is still used in some cases (support claims both are used)vancelUpdate comment• ad47aa8C < 40 libl • I Tech Day Review in 1h 44 mA100% C•8 Fri 17 Apr 15:16:46Q Type to search• Checks pendingCode-+2-2 -0280203a @JY-20692 change confirmation parameter) This branch has not been deployedNo deploymentsReview requiredAt least 1 approving review is required by reviewers with write access.Some checks haven't completed yet3 pending, 1 in progress, 1 expected, 5 successful checks4 pending checks• O ci/circleci: setup Waiting for status to be reported - CircleCl is running your tests• O ci/circleci: test-backend-lint Waiting for status to be reported - CircleCl is running your tests• O ci/circleci: test-frontend Waiting for status to be reported - CircleCl is running your tests• & SonarCloud Code Analysis Expected - Waiting for status to be reportediin orodress checkv• O build_accept_deploy Started 4 minutes ago — Workflow: build_ accept_deploy5 successfull checksvMal AilniralAni. huiid haAkAnAA This branch is out-of-date with the base branchMerge the latest changes from master into this branch. This merge commit will beassociated with LakyLak....RequiredReviewersSuggestions€ Vasil-JiminnyRequestAt least 1 approving review is required to merge this pullrequest.Still in progress? Convert to draftAssigneesNo one-assign yourselfLabelsNone yetProjectsNone yetMilestoneNo milestoneDevelopmentSuccessfully merging this pull request may close theseissues.None yetNotificationsCustomizeUnsubscribeYou're receiving notifications because you're watchingthis repository.1 participant& Lock conversationUpdate branch...
|
NULL
|
4111515534464520827
|
NULL
|
click
|
ocr
|
NULL
|
FirefoxFile...EoitViewHistoryBookmarksProfilesTool FirefoxFile...EoitViewHistoryBookmarksProfilesToolsWindowHelpgithub.com/jiminny/app/pull/11986bookmhlejiminny / app 8Developers | HubSpotQ Search bookmarks‹> Code82 Pull requests 30G Agents © Actions D Wiki ® Security and quality 27 Insights ộs SettingsM'inbox (1,576) - lukas.kovalik@jiminM 120216 is your HubSpot Log In CodCa CloudWatch | eu-west-1New Tabv 3o0knarks 100 oa1Sprint BoardSRD QueueGithubv E Bookmarks Menu> C Mozilla Firefox• Other Bookmarks® On April 24 we'll start using GitHub Copilot interaction data for Al model training unless you opt out. Review this update and manage your preferences in your GitHub account settings.Z Configure SSH access to multiple. fix-cache-for-business-processesAuth ProxyDashboard • Jiminny • MembraneApo "4oho CkM" • Jiminny • MemcJY-20692 change confirmatior X( JY-20543 add AJ reports User pilc+ New TabJY-20692 change confirmation parameter #11986 •87 OpenLakyLak wants to merge 1 commit into master from JY-20692-fix-integration-app-[API_KEY] 0• Commits 1 |E Checks 2# Files changed 2LakyLak commented nowwritePrevlewH B#H# JIKA. JT-LUOSL#### Changes:- Addresses breaking change, disconnected is replaced by connected.- Keeping disconnected for scenarios it is still used in some cases (support claims both are used)vancelUpdate comment• ad47aa8C < 40 libl • I Tech Day Review in 1h 44 mA100% C•8 Fri 17 Apr 15:16:46Q Type to search• Checks pendingCode-+2-2 -0280203a @JY-20692 change confirmation parameter) This branch has not been deployedNo deploymentsReview requiredAt least 1 approving review is required by reviewers with write access.Some checks haven't completed yet3 pending, 1 in progress, 1 expected, 5 successful checks4 pending checks• O ci/circleci: setup Waiting for status to be reported - CircleCl is running your tests• O ci/circleci: test-backend-lint Waiting for status to be reported - CircleCl is running your tests• O ci/circleci: test-frontend Waiting for status to be reported - CircleCl is running your tests• & SonarCloud Code Analysis Expected - Waiting for status to be reportediin orodress checkv• O build_accept_deploy Started 4 minutes ago — Workflow: build_ accept_deploy5 successfull checksvMal AilniralAni. huiid haAkAnAA This branch is out-of-date with the base branchMerge the latest changes from master into this branch. This merge commit will beassociated with LakyLak....RequiredReviewersSuggestions€ Vasil-JiminnyRequestAt least 1 approving review is required to merge this pullrequest.Still in progress? Convert to draftAssigneesNo one-assign yourselfLabelsNone yetProjectsNone yetMilestoneNo milestoneDevelopmentSuccessfully merging this pull request may close theseissues.None yetNotificationsCustomizeUnsubscribeYou're receiving notifications because you're watchingthis repository.1 participant& Lock conversationUpdate branch...
|
NULL
|
|
48009
|
NULL
|
0
|
2026-04-17T12:16:46.596747+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776428206596_m1.jpg...
|
NULL
|
NULL
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
FirefoxFileEditViewHistoryBookmarksProfilesToolsWi FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelplihol& Tech Day Review - in 1h 44 m100% <478DEV (docker)X4DOCKER• ₴1DEV (docker)APP (-zsh)• *3-zshconfigcachecompiledeventsroutesviewsjiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00:stoppedjiminny-worker-processing-2:jiminny-worker-processing-2_00: stoppedjiminny-worker-processing-3:jiminny-worker-processing-3_00: stoppedjiminny-worker-processing-4:jiminny-worker-processing-4_00:stoppedjiminny-worker-processing-5:jiminny-worker-processing-5_00: stoppedworker-analytics:worker-analytics_00: stoppedworker-crm-update:worker-crm-update_00: stoppedworker-download:worker-download_00: stoppedworker-nudges:worker-nudges_00: stoppedworker:worker_00: stoppedworker-audio:worker-audio_00: stoppedworker-calendar:worker-calendar_00: stoppedworker-emails:worker-emails_00: stoppedjiminny-worker-processing-1:jiminny-worker-processing-1_00: stoppedworker-conferences:worker-conferences_00: stoppedworker-crm-sync:worker-crm-sync_00: stoppedworker-es-update:worker-es-update_00: stoppedartisan-schedule:artisan-schedule_00: stoppedartisan-schedule:artisan-schedule_00: startedjiminny-worker-processing-1:jiminny-worker-processing-1_00: startedjiminny-worker-processing-2:jiminny-worker-processing-2_00: startedjiminny-worker-processing-3:jiminny-worker-processing-3_00: startedjiminny-worker-processing-4:jiminny-worker-processing-4_00: startedjiminny-worker-processing-5:jiminny-worker-processing-5_00: startedjiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00:startedworker:worker_00: startedworker-analytics:worker-analytics_00: startedworker-audio:worker-audio_00: startedworker-calendar:worker-calendar_00: startedworker-conferences:worker-conferences_00: startedworker-crm-sync:worker-crm-sync_00: startedworker-crm-update:worker-crm-update_00:startedworker-download:worker-download_00: startedworker-emails:worker-emails_00: startedworker-es-update:worker-es-update_00: startedworker-nudges:worker-nudges_00: startedroot@docker_lamp_1:/home/jiminny#•₴85* Review screenp...• 286ec2-user@ip-10-...19.97ms DONE88.71ms DONE5.81ms DONE4.39ms DONE4.85ms DONE26.89ms DONEO x7Fri 17 Apr 15:16:461₴81ec2-user@ip-10-...• 88DEV...
|
NULL
|
-4835493523388054914
|
NULL
|
click
|
ocr
|
NULL
|
FirefoxFileEditViewHistoryBookmarksProfilesToolsWi FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelplihol& Tech Day Review - in 1h 44 m100% <478DEV (docker)X4DOCKER• ₴1DEV (docker)APP (-zsh)• *3-zshconfigcachecompiledeventsroutesviewsjiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00:stoppedjiminny-worker-processing-2:jiminny-worker-processing-2_00: stoppedjiminny-worker-processing-3:jiminny-worker-processing-3_00: stoppedjiminny-worker-processing-4:jiminny-worker-processing-4_00:stoppedjiminny-worker-processing-5:jiminny-worker-processing-5_00: stoppedworker-analytics:worker-analytics_00: stoppedworker-crm-update:worker-crm-update_00: stoppedworker-download:worker-download_00: stoppedworker-nudges:worker-nudges_00: stoppedworker:worker_00: stoppedworker-audio:worker-audio_00: stoppedworker-calendar:worker-calendar_00: stoppedworker-emails:worker-emails_00: stoppedjiminny-worker-processing-1:jiminny-worker-processing-1_00: stoppedworker-conferences:worker-conferences_00: stoppedworker-crm-sync:worker-crm-sync_00: stoppedworker-es-update:worker-es-update_00: stoppedartisan-schedule:artisan-schedule_00: stoppedartisan-schedule:artisan-schedule_00: startedjiminny-worker-processing-1:jiminny-worker-processing-1_00: startedjiminny-worker-processing-2:jiminny-worker-processing-2_00: startedjiminny-worker-processing-3:jiminny-worker-processing-3_00: startedjiminny-worker-processing-4:jiminny-worker-processing-4_00: startedjiminny-worker-processing-5:jiminny-worker-processing-5_00: startedjiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00:startedworker:worker_00: startedworker-analytics:worker-analytics_00: startedworker-audio:worker-audio_00: startedworker-calendar:worker-calendar_00: startedworker-conferences:worker-conferences_00: startedworker-crm-sync:worker-crm-sync_00: startedworker-crm-update:worker-crm-update_00:startedworker-download:worker-download_00: startedworker-emails:worker-emails_00: startedworker-es-update:worker-es-update_00: startedworker-nudges:worker-nudges_00: startedroot@docker_lamp_1:/home/jiminny#•₴85* Review screenp...• 286ec2-user@ip-10-...19.97ms DONE88.71ms DONE5.81ms DONE4.39ms DONE4.85ms DONE26.89ms DONEO x7Fri 17 Apr 15:16:461₴81ec2-user@ip-10-...• 88DEV...
|
NULL
|
|
47895
|
NULL
|
0
|
2026-04-17T12:11:33.359940+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776427893359_m2.jpg...
|
Firefox
|
Jiminny — Work
|
True
|
app.dev.jiminny.com/dashboard
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Developers | HubSpot
Developers Workers | Datadog
Developers | HubSpot
Developers | HubSpot
Inbox (1,576) - [EMAIL] - Jiminny Mail
Inbox (1,576) - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
CloudWatch | eu-west-1
CloudWatch | eu-west-1
New Tab
New Tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
[JY-20692] Issue with reconnecting Zoho - Jira
[JY-20692] Issue with reconnecting Zoho - Jira
Jiminny
Jiminny
Close tab
Auth Proxy
Auth Proxy
Dashboard · Jiminny · Membrane
Dashboard · Jiminny · Membrane
App "Zoho CRM" · Jiminny · Membrane
App "Zoho CRM" · Jiminny · Membrane
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
My Recordings
My Recordings
Everyone's Recordings
Everyone's Recordings
No Recordings
Schedule
Schedule
Invite Notetaker
This Week
This Week
My Schedule
My Schedule
No Meetings
Trending this month
Trending this month
Sort by Sort by: Most played
Sort by
Sort by:
Most played
No Recordings
Live Feed
Live Feed
No Activity...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"bounds":{"left":0.00234375,"top":0.045138888,"width":0.0890625,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Developers | HubSpot","depth":4,"bounds":{"left":0.0,"top":0.08263889,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Developers | HubSpot","depth":5,"bounds":{"left":0.015625,"top":0.09236111,"width":0.04453125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Inbox (1,576) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.11111111,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox (1,576) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"bounds":{"left":0.015625,"top":0.12083333,"width":0.11445312,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"120216 is your HubSpot Log In Code - integration-account@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.13958333,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"120216 is your HubSpot Log In Code - integration-account@jiminny.com - Jiminny Mail","depth":5,"bounds":{"left":0.015625,"top":0.14930555,"width":0.17734376,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | eu-west-1","depth":4,"bounds":{"left":0.0,"top":0.16805555,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | eu-west-1","depth":5,"bounds":{"left":0.015625,"top":0.17777778,"width":0.048828125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.19652778,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.015625,"top":0.20625,"width":0.017578125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.225,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":5,"bounds":{"left":0.015625,"top":0.23472223,"width":0.1515625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.2534722,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app","depth":5,"bounds":{"left":0.015625,"top":0.26319444,"width":0.17421874,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20692] Issue with reconnecting Zoho - Jira","depth":4,"bounds":{"left":0.0,"top":0.28194445,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20692] Issue with reconnecting Zoho - Jira","depth":5,"bounds":{"left":0.015625,"top":0.29166666,"width":0.09726562,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.31041667,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.015625,"top":0.3201389,"width":0.015625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.07890625,"top":0.31666666,"width":0.009375,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Auth Proxy","depth":4,"bounds":{"left":0.0,"top":0.33888888,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Auth Proxy","depth":5,"bounds":{"left":0.015625,"top":0.34861112,"width":0.021875,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Dashboard · Jiminny · Membrane","depth":4,"bounds":{"left":0.0,"top":0.3673611,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Dashboard · Jiminny · Membrane","depth":5,"bounds":{"left":0.015625,"top":0.37708333,"width":0.06679688,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"App \"Zoho CRM\" · Jiminny · Membrane","depth":4,"bounds":{"left":0.0,"top":0.39583334,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"App \"Zoho CRM\" · Jiminny · Membrane","depth":5,"bounds":{"left":0.015625,"top":0.40555555,"width":0.0796875,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.003125,"top":0.42569444,"width":0.08710937,"height":0.022222223},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.003125,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.01640625,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.029296875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.0421875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.05546875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"My Recordings","depth":10,"bounds":{"left":0.18867187,"top":0.0625,"width":0.071875,"height":0.045833334},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"My Recordings","depth":11,"bounds":{"left":0.20429687,"top":0.07847222,"width":0.040625,"height":0.013888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Everyone's Recordings","depth":10,"bounds":{"left":0.26054686,"top":0.0625,"width":0.09257813,"height":0.045833334},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Everyone's Recordings","depth":11,"bounds":{"left":0.27617186,"top":0.07847222,"width":0.061328124,"height":0.013888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No Recordings","depth":13,"bounds":{"left":0.253125,"top":0.25694445,"width":0.035546876,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Schedule","depth":9,"bounds":{"left":0.41914064,"top":0.24513888,"width":0.034375,"height":0.022222223},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Schedule","depth":10,"bounds":{"left":0.41914064,"top":0.24652778,"width":0.034375,"height":0.01875},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Invite Notetaker","depth":10,"bounds":{"left":0.64804685,"top":0.24305555,"width":0.051953126,"height":0.025},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXComboBox","text":"This Week","depth":10,"bounds":{"left":0.425,"top":0.2847222,"width":0.1328125,"height":0.025694445},"value":"This Week","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"This Week","depth":12,"bounds":{"left":0.42929688,"top":0.29166666,"width":0.02578125,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"My Schedule","depth":10,"bounds":{"left":0.56171876,"top":0.2847222,"width":0.13242188,"height":0.025694445},"value":"My Schedule","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"My Schedule","depth":12,"bounds":{"left":0.5660156,"top":0.29166666,"width":0.030859375,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No Meetings","depth":12,"bounds":{"left":0.54414064,"top":0.67083335,"width":0.030859375,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Trending this month","depth":9,"bounds":{"left":0.425,"top":0.077083334,"width":0.054296874,"height":0.016666668},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Trending this month","depth":10,"bounds":{"left":0.425,"top":0.07847222,"width":0.054296874,"height":0.013888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Sort by Sort by: Most played","depth":9,"bounds":{"left":0.6136719,"top":0.072916664,"width":0.08046875,"height":0.025694445},"value":"Sort by Sort by: Most played","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Sort by","depth":10,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sort by:","depth":11,"bounds":{"left":0.61796874,"top":0.07986111,"width":0.019921875,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Most played","depth":11,"bounds":{"left":0.63789064,"top":0.07986111,"width":0.03046875,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No Recordings","depth":12,"bounds":{"left":0.54179686,"top":0.19166666,"width":0.035546876,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Live Feed","depth":9,"bounds":{"left":0.71367186,"top":0.077083334,"width":0.02578125,"height":0.016666668},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Live Feed","depth":10,"bounds":{"left":0.71367186,"top":0.07847222,"width":0.02578125,"height":0.013888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No Activity","depth":12,"bounds":{"left":0.834375,"top":0.25694445,"width":0.027734375,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-4962200028860693347
|
-2792973627911173944
|
visual_change
|
accessibility
|
NULL
|
Workers | Datadog
Developers | HubSpot
Developers Workers | Datadog
Developers | HubSpot
Developers | HubSpot
Inbox (1,576) - [EMAIL] - Jiminny Mail
Inbox (1,576) - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
CloudWatch | eu-west-1
CloudWatch | eu-west-1
New Tab
New Tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
[JY-20692] Issue with reconnecting Zoho - Jira
[JY-20692] Issue with reconnecting Zoho - Jira
Jiminny
Jiminny
Close tab
Auth Proxy
Auth Proxy
Dashboard · Jiminny · Membrane
Dashboard · Jiminny · Membrane
App "Zoho CRM" · Jiminny · Membrane
App "Zoho CRM" · Jiminny · Membrane
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
My Recordings
My Recordings
Everyone's Recordings
Everyone's Recordings
No Recordings
Schedule
Schedule
Invite Notetaker
This Week
This Week
My Schedule
My Schedule
No Meetings
Trending this month
Trending this month
Sort by Sort by: Most played
Sort by
Sort by:
Most played
No Recordings
Live Feed
Live Feed
No Activity...
|
NULL
|
|
47893
|
NULL
|
0
|
2026-04-17T12:11:16.117171+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776427876117_m1.jpg...
|
Slack
|
jiminny-x-integration-app (Channel) - Jiminny Inc jiminny-x-integration-app (Channel) - Jiminny Inc - Slack...
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
Directories
jiminny-x-integration-app
platform-inner-team
ai-chapter
alerts
backend
confusion-clinic
curiosity_lab
engineering
frontend
general
infra-changes
jiminny-bg
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Nikolay Nikolov
Aneliya Angelova
,
Nikolay Yankov
,
Steliyan Georgiev
Galya Dimitrova
Stoyan Tanev
Vasil Vasilev
Nikolay Ivanov
Aneliya Angelova
Ves
Steliyan Georgiev
Jira Cloud
Toast
Google Calendar
Messages
Messages
More
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
Membrane
APP
Nov 11th, 2025 at 12:38:55 AM...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Jiminny Inc","depth":12,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXRadioButton","text":"Jiminny (Staging)","depth":12,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Add workspaces","depth":12,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":16,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":16,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":16,"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":16,"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":16,"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":16,"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"jiminny-x-integration-app","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"curiosity_lab","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"frontend","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"general","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"infra-changes","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"platform-tickets","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"product_launches","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"random","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"releases","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"support","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"thank-yous","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"the_people_of_jiminny","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Nikolov","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Aneliya Angelova","depth":22,"role_description":"text"},{"role":"AXStaticText","text":",","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":22,"role_description":"text"},{"role":"AXStaticText","text":",","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Stoyan Tanev","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Ivanov","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Aneliya Angelova","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Ves","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Jira Cloud","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Toast","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Google Calendar","depth":22,"role_description":"text"},{"role":"AXRadioButton","text":"Messages","depth":18,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Messages","depth":20,"role_description":"text"},{"role":"AXRadioButton","text":"More","depth":19,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Add and Edit Channel Tabs","depth":18,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Canvas","depth":18,"role_description":"text"},{"role":"AXStaticText","text":"List","depth":18,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":18,"role_description":"text"},{"role":"AXPopUpButton","text":"Jump to date","depth":23,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Membrane","depth":24,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"APP","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"","depth":24,"role_description":"text"},{"role":"AXLink","text":"Nov 11th, 2025 at 12:38:55 AM","depth":24,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
1393408694651708376
|
-4149935279600396149
|
click
|
hybrid
|
NULL
|
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
Directories
jiminny-x-integration-app
platform-inner-team
ai-chapter
alerts
backend
confusion-clinic
curiosity_lab
engineering
frontend
general
infra-changes
jiminny-bg
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Nikolay Nikolov
Aneliya Angelova
,
Nikolay Yankov
,
Steliyan Georgiev
Galya Dimitrova
Stoyan Tanev
Vasil Vasilev
Nikolay Ivanov
Aneliya Angelova
Ves
Steliyan Georgiev
Jira Cloud
Toast
Google Calendar
Messages
Messages
More
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
Membrane
APP
Nov 11th, 2025 at 12:38:55 AM
FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelpSupport Daily • 4 m left100% <478DEV (docker)84DOCKER881DEV (docker)APP (-zsh)• *3-zshconfigcachecompiledeventsroutesviewsjiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00:stoppedjiminny-worker-processing-2:jiminny-worker-processing-2_00: stoppedjiminny-worker-processing-3:jiminny-worker-processing-3_00:stoppedjiminny-worker-processing-4:jiminny-worker-processing-4_00:stoppedjiminny-worker-processing-5:jiminny-worker-processing-5_00: stoppedworker-analytics:worker-analytics_00: stoppedworker-crm-update:worker-crm-update_00: stoppedworker-download:worker-download_00: stoppedworker-nudges:worker-nudges_00: stoppedworker:worker_00: stoppedworker-audio:worker-audio_00: stoppedworker-calendar:worker-calendar_00: stoppedworker-emails:worker-emails_00: stoppedjiminny-worker-processing-1:jiminny-worker-processing-1_00: stoppedworker-conferences:worker-conferences_00: stoppedworker-crm-sync:worker-crm-sync_00: stoppedworker-es-update:worker-es-update_00: stoppedartisan-schedule:artisan-schedule_00: stoppedartisan-schedule:artisan-schedule_00: startedjiminny-worker-processing-1:jiminny-worker-processing-1_00: startedjiminny-worker-processing-2:jiminny-worker-processing-2_00: startedjiminny-worker-processing-3:jiminny-worker-processing-3_00: startedjiminny-worker-processing-4:jiminny-worker-processing-4_00: startedjiminny-worker-processing-5:jiminny-worker-processing-5_00: startedjiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00:startedworker:worker_00: startedworker-analytics:worker-analytics_00: startedworker-audio:worker-audio_00: startedworker-calendar:worker-calendar_00: startedworker-conferences:worker-conferences_00: startedworker-crm-sync:worker-crm-sync_00: startedworker-crm-update:worker-crm-update_00:startedworker-download:worker-download_00: startedworker-emails:worker-emails_00: startedworker-es-update:worker-es-update_00: startedworker-nudges:worker-nudges_00: startedroot@docker_lamp_1:/home/jiminny#-zsh•₴85* Review screenp...• ₴6ec2-user@ip-10-...19.97ms DONE88.71ms DONE5.81ms DONE4.39ms DONE4.85ms DONE26.89ms DONEO x7Fri 17 Apr 15:11:151₴81ec2-user@ip-10-...• 88DEV...
|
47888
|
|
47858
|
NULL
|
0
|
2026-04-17T12:06:22.463212+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776427582463_m2.jpg...
|
PhpStorm
|
Commit Changes
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Diff
Changelist:
Changelist:
Changes
Show Diff
Rol Diff
Changelist:
Changelist:
Changes
Show Diff
Rollback...
Refresh
Group By
Expand All
Collapse All
2 modified
JY-20692
Commit Message
Commit Message History
Git
Author:
Author:
Amend commit
Sign-off commit
Create extra commit with file movements
Commit Checks
Update copyright
Reformat code
Rearrange code
Optimize imports
Cleanup
Choose profile
Check malicious dependencies
Run external formatter
Configure
Perform SonarQube for IDE analysis
Find unused view files
Run Git hooks
Advanced Commit Checks
Analyze code
Choose profile
Check TODO
Configure
After Commit
Upload files to:
Upload files to:
<None>
Always use selected server or group of servers
Previous Difference
Next Difference
Jump to Source
Compare Previous File
Compare Next File
Go to Changed File…
Side-by-side viewer
Do not ignore
Highlight words
Collapse Unchanged Fragments
Synchronize Scrolling
Disable Editing
Settings
Help
1 difference
5bb8fefc
<template>
<WelcomeLayout
title="Account disconnected"
textPosition="center"
:icon="faUnlink"
:class="$style.layout"
>
<div :class="$style.container" v-if="providersLoaded">
<p>
<strong>
It looks like your {{ localProvider.displayName }} account has become
disconnected
</strong>
</p>
<p :class="$style.small">Please re-connect to continue</p>
<p v-if="isInIframe">
We'll open the {{ localProvider.displayName }} authentication in a new
tab. Please return here and refresh the page once complete
</p>
<GoogleLikeButton
v-if="localProvider.viaIntegrationApp && crmTokenLoaded"
as="a"
:key="localProvider.name"
:brand-logo="localProvider.name"
:class="$style.connectButton"
@click="integrationAppOnClick"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
<GoogleLikeButton
v-if="!localProvider.viaIntegrationApp"
as="a"
:key="localProvider.name"
:href="`/auth/redirect/${localProvider.name}`"
:target="target"
:brand-logo="localProvider.name"
:class="$style.connectButton"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
</div>
<BuildInfo />
<KioskBanner />
</WelcomeLayout>
</template>
<script>
import window from "window";
import axios from "axios";
import { faUnlink } from "@fortawesome/pro-regular-svg-icons";
import isInIframe from "@/utils/isInIframe";
import BuildInfo from "@/components/layout/BuildInfo/BuildInfo.vue";
import KioskBanner from "@/components/shared/KioskBanner/KioskBanner.vue";
import WelcomeLayout from "@/components/layout/WelcomeLayout/WelcomeLayout.vue";
import GoogleLikeButton from "@/components/shared/Buttons/GoogleLikeButton.vue";
import { showSnackbarError, normalizeError } from "@/utils/index";
import { IntegrationAppClient } from "@integration-app/sdk";
export default {
name: "ConnectPage",
components: {
BuildInfo,
KioskBanner,
WelcomeLayout,
GoogleLikeButton,
},
data() {
return {
...window.connectData,
crmToken: null,
faUnlink,
isInIframe,
providers: [],
providersLoaded: false,
crmTokenLoaded: false,
};
},
computed: {
localProvider() {
return this.providers.find((e) => e.name === this.provider);
},
target() {
return this.isInIframe ? "_blank" : null;
},
},
created() {
this.getProviders();
},
mounted() {
this.showErrors();
},
watch: {
providersLoaded() {
if (this.providersLoaded) {
this.prepareIntegrationAppConnection();
}
},
},
methods: {
showErrors() {
if (!this.error) return;
showSnackbarError(this.error, undefined, undefined, false);
},
unwrapEntityResponse({ data }) {
return data.map(({ icon, name, displayName, viaIntegrationApp }) => {
return { icon, name, displayName, viaIntegrationApp };
});
},
async getProviders() {
try {
const response = await axios.get("/api/v1/connect-providers");
this.providers = this.unwrapEntityResponse(response);
this.providersLoaded = true;
} catch {
showSnackbarError(
"An error occurred, while loading form data (connect providers).",
);
}
},
async prepareIntegrationAppConnection() {
if (this.localProvider.viaIntegrationApp) {
try {
const response = await axios.get("/api/v1/integration-app-token");
this.crmToken = response.data.token;
this.crmTokenLoaded = true;
} catch (error) {
console.log(error);
showSnackbarError(
`An error occurred while preparing the page.
Try refreshing, if the error persists get in touch with the Jiminny team.`,
);
}
}
},
async integrationAppOnClick() {
const integrationApp = new IntegrationAppClient({
token: this.crmToken,
});
const connection = await integrationApp
.integration(this.localProvider.name)
.openNewConnection({
showPoweredBy: false,
allowMultipleConnections: false,
});
if (connection && connection.disconnected === false) {
try {
const saveRequest = await axios.post(
"/api/v1/integration-app-connect",
);
if (saveRequest.data && saveRequest.data.success === true) {
/** If all is good refresh the page here */
window.location = "/dashboard";
return;
}
throw new Error(saveRequest.data.message);
} catch (error) {
console.log(error);
showSnackbarError(normalizeError(error));
}
}
},
},
};
</script>
<style module lang="less" src="./connect.less"></style>
Current version
<template>
<WelcomeLayout
title="Account disconnected"
textPosition="center"
:icon="faUnlink"
:class="$style.layout"
>
<div :class="$style.container" v-if="providersLoaded">
<p>
<strong>
It looks like your {{ localProvider.displayName }} account has become
disconnected
</strong>
</p>
<p :class="$style.small">Please re-connect to continue</p>
<p v-if="isInIframe">
We'll open the {{ localProvider.displayName }} authentication in a new
tab. Please return here and refresh the page once complete
</p>
<GoogleLikeButton
v-if="localProvider.viaIntegrationApp && crmTokenLoaded"
as="a"
:key="localProvider.name"
:brand-logo="localProvider.name"
:class="$style.connectButton"
@click="integrationAppOnClick"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
<GoogleLikeButton
v-if="!localProvider.viaIntegrationApp"
as="a"
:key="localProvider.name"
:href="`/auth/redirect/${localProvider.name}`"
:target="target"
:brand-logo="localProvider.name"
:class="$style.connectButton"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
</div>
<BuildInfo />
<KioskBanner />
</WelcomeLayout>
</template>
<script>
import window from "window";
import axios from "axios";
import { faUnlink } from "@fortawesome/pro-regular-svg-icons";
import isInIframe from "@/utils/isInIframe";
import BuildInfo from "@/components/layout/BuildInfo/BuildInfo.vue";
import KioskBanner from "@/components/shared/KioskBanner/KioskBanner.vue";
import WelcomeLayout from "@/components/layout/WelcomeLayout/WelcomeLayout.vue";
import GoogleLikeButton from "@/components/shared/Buttons/GoogleLikeButton.vue";
import { showSnackbarError, normalizeError } from "@/utils/index";
import { IntegrationAppClient } from "@integration-app/sdk";
export default {
name: "ConnectPage",
components: {
BuildInfo,
KioskBanner,
WelcomeLayout,
GoogleLikeButton,
},
data() {
return {
...window.connectData,
crmToken: null,
faUnlink,
isInIframe,
providers: [],
providersLoaded: false,
crmTokenLoaded: false,
};
},
computed: {
localProvider() {
return this.providers.find((e) => e.name === this.provider);
},
target() {
return this.isInIframe ? "_blank" : null;
},
},
created() {
this.getProviders();
},
mounted() {
this.showErrors();
},
watch: {
providersLoaded() {
if (this.providersLoaded) {
this.prepareIntegrationAppConnection();
}
},
},
methods: {
showErrors() {
if (!this.error) return;
showSnackbarError(this.error, undefined, undefined, false);
},
unwrapEntityResponse({ data }) {
return data.map(({ icon, name, displayName, viaIntegrationApp }) => {
return { icon, name, displayName, viaIntegrationApp };
});
},
async getProviders() {
try {
const response = await axios.get("/api/v1/connect-providers");
this.providers = this.unwrapEntityResponse(response);
this.providersLoaded = true;
} catch {
showSnackbarError(
"An error occurred, while loading form data (connect providers).",
);
}
},
async prepareIntegrationAppConnection() {
if (this.localProvider.viaIntegrationApp) {
try {
const response = await axios.get("/api/v1/integration-app-token");
this.crmToken = response.data.token;
this.crmTokenLoaded = true;
} catch (error) {
console.log(error);
showSnackbarError(
`An error occurred while preparing the page.
Try refreshing, if the error persists get in touch with the Jiminny team.`,
);
}
}
},
async integrationAppOnClick() {
const integrationApp = new IntegrationAppClient({
token: this.crmToken,
});
const connection = await integrationApp
.integration(this.localProvider.name)
.openNewConnection({
showPoweredBy: false,
allowMultipleConnections: false,
});
if (connection && connection.disconnected !== true && connection.connected !== false) {
try {
const saveRequest = await axios.post(
"/api/v1/integration-app-connect",
);
if (saveRequest.data && saveRequest.data.success === true) {
/** If all is good refresh the page here */
window.location = "/dashboard";
return;
}
throw new Error(saveRequest.data.message);
} catch (error) {
console.log(error);
showSnackbarError(normalizeError(error));
}
}
},
},
};
</script>
<style module lang="less" src="./connect.less"></style>
Help
Cancel
Commit
Commit...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"Diff","depth":1,"bounds":{"left":0.25585938,"top":0.35555556,"width":0.01640625,"height":0.011805556},"role_description":"text"},{"role":"AXStaticText","text":"Changelist:","depth":1,"bounds":{"left":0.4050781,"top":0.15208334,"width":0.026953125,"height":0.023611112},"role_description":"text"},{"role":"AXPopUpButton","text":"Changelist:","depth":1,"bounds":{"left":0.43359375,"top":0.15208334,"width":0.049609374,"height":0.023611112},"role_description":"pop up button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Changes","depth":6,"role_description":"text"},{"role":"AXButton","text":"Show Diff","depth":2,"bounds":{"left":0.25742188,"top":0.15555556,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Rollback...","depth":2,"bounds":{"left":0.26757812,"top":0.15555556,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Refresh","depth":2,"bounds":{"left":0.27773437,"top":0.15555556,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Group By","depth":2,"bounds":{"left":0.290625,"top":0.15555556,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand All","depth":2,"bounds":{"left":0.38085938,"top":0.15555556,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":2,"bounds":{"left":0.39101562,"top":0.15555556,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2 modified","depth":1,"bounds":{"left":0.25585938,"top":0.2361111,"width":0.22734375,"height":0.014583333},"role_description":"text"},{"role":"AXTextArea","text":"JY-20692","depth":2,"bounds":{"left":0.25625,"top":0.27638888,"width":0.2265625,"height":0.07638889},"value":"JY-20692","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Commit Message","depth":1,"bounds":{"left":0.25585938,"top":0.26041666,"width":0.041796874,"height":0.011805556},"role_description":"text"},{"role":"AXButton","text":"Commit Message History","depth":2,"bounds":{"left":0.47304687,"top":0.25694445,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Git","depth":2,"bounds":{"left":0.48710936,"top":0.15625,"width":0.006640625,"height":0.011805556},"role_description":"text"},{"role":"AXStaticText","text":"Author:","depth":2,"bounds":{"left":0.49648437,"top":0.17847222,"width":0.017578125,"height":0.011805556},"role_description":"text"},{"role":"AXTextArea","text":"Author:","depth":3,"bounds":{"left":0.51835936,"top":0.17847222,"width":0.11015625,"height":0.011805556},"role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Amend commit","depth":2,"bounds":{"left":0.49492186,"top":0.19791667,"width":0.046875,"height":0.017361112},"help_text":"Merge this commit with the previous one","role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Sign-off commit","depth":2,"bounds":{"left":0.49492186,"top":0.21805556,"width":0.049609374,"height":0.017361112},"help_text":"<html>Adds the following line at the end of the commit message:<br/>Signed-off by: Lukas Kovalik <kovaliklukas@gmail.com></html>","role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Create extra commit with file movements","depth":2,"role_description":"checkbox","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Commit Checks","depth":2,"bounds":{"left":0.48710936,"top":0.25486112,"width":0.037890624,"height":0.011805556},"role_description":"text"},{"role":"AXCheckBox","text":"Update copyright","depth":2,"bounds":{"left":0.49492186,"top":0.27222222,"width":0.052734375,"height":0.017361112},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Reformat code","depth":2,"bounds":{"left":0.49492186,"top":0.2923611,"width":0.046484374,"height":0.017361112},"help_text":"","role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Rearrange code","depth":2,"bounds":{"left":0.49492186,"top":0.3125,"width":0.04921875,"height":0.017361112},"help_text":"","role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Optimize imports","depth":2,"bounds":{"left":0.49492186,"top":0.3326389,"width":0.051953126,"height":0.017361112},"help_text":"","role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Cleanup","depth":2,"bounds":{"left":0.49492186,"top":0.35277778,"width":0.030859375,"height":0.017361112},"help_text":"","role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Choose profile","depth":2,"bounds":{"left":0.53046876,"top":0.35555556,"width":0.03515625,"height":0.011805556},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Check malicious dependencies","depth":2,"bounds":{"left":0.49492186,"top":0.37291667,"width":0.08515625,"height":0.017361112},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Run external formatter","depth":2,"bounds":{"left":0.49492186,"top":0.39305556,"width":0.06484375,"height":0.017361112},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Configure","depth":2,"bounds":{"left":0.5644531,"top":0.39583334,"width":0.0234375,"height":0.011805556},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Perform SonarQube for IDE analysis","depth":2,"bounds":{"left":0.49492186,"top":0.41319445,"width":0.13554688,"height":0.017361112},"help_text":"","role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Find unused view files","depth":2,"bounds":{"left":0.49492186,"top":0.43333334,"width":0.0640625,"height":0.017361112},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Run Git hooks","depth":2,"help_text":"If unchecked, Git hooks will be skipped with the '--no-verify' option for the upcoming commit","role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Advanced Commit Checks","depth":2,"bounds":{"left":0.48710936,"top":0.47013888,"width":0.06367187,"height":0.011805556},"role_description":"text"},{"role":"AXCheckBox","text":"Analyze code","depth":2,"bounds":{"left":0.49492186,"top":0.4875,"width":0.043359376,"height":0.017361112},"help_text":"","role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Choose profile","depth":2,"bounds":{"left":0.54296875,"top":0.49027777,"width":0.03515625,"height":0.011805556},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Check TODO","depth":2,"bounds":{"left":0.49492186,"top":0.5076389,"width":0.0421875,"height":0.017361112},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Configure","depth":2,"bounds":{"left":0.54179686,"top":0.5104167,"width":0.0234375,"height":0.011805556},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"After Commit","depth":2,"bounds":{"left":0.48710936,"top":0.54444444,"width":0.03203125,"height":0.011805556},"role_description":"text"},{"role":"AXStaticText","text":"Upload files to:","depth":2,"bounds":{"left":0.49648437,"top":0.56805557,"width":0.0359375,"height":0.011805556},"role_description":"text"},{"role":"AXPopUpButton","text":"Upload files to:","depth":2,"bounds":{"left":0.5335938,"top":0.5625,"width":0.08476563,"height":0.023611112},"role_description":"pop up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"<None>","depth":7,"role_description":"text"},{"role":"AXCheckBox","text":"Always use selected server or group of servers","depth":2,"bounds":{"left":0.5042969,"top":0.58958334,"width":0.12382813,"height":0.017361112},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Previous Difference","depth":2,"bounds":{"left":0.25742188,"top":0.37430555,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Difference","depth":2,"bounds":{"left":0.26757812,"top":0.37430555,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Jump to Source","depth":2,"bounds":{"left":0.27773437,"top":0.37430555,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Compare Previous File","depth":2,"bounds":{"left":0.290625,"top":0.37430555,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Compare Next File","depth":2,"bounds":{"left":0.30078125,"top":0.37430555,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Go to Changed File…","depth":2,"bounds":{"left":0.3109375,"top":0.37430555,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Side-by-side viewer","depth":2,"bounds":{"left":0.32226562,"top":0.37430555,"width":0.05546875,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Do not ignore","depth":2,"bounds":{"left":0.3828125,"top":0.37430555,"width":0.041796874,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Highlight words","depth":2,"bounds":{"left":0.42695314,"top":0.37430555,"width":0.046484374,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse Unchanged Fragments","depth":2,"bounds":{"left":0.47460938,"top":0.37430555,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Synchronize Scrolling","depth":2,"bounds":{"left":0.48476562,"top":0.37430555,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Disable Editing","depth":2,"bounds":{"left":0.49492186,"top":0.37430555,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Settings","depth":2,"bounds":{"left":0.50507814,"top":0.37430555,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Help","depth":2,"bounds":{"left":0.5179688,"top":0.37430555,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1 difference","depth":1,"bounds":{"left":0.6070312,"top":0.37291667,"width":0.028515626,"height":0.019444445},"role_description":"text"},{"role":"AXTextField","text":"5bb8fefc","depth":1,"bounds":{"left":0.26367188,"top":0.3923611,"width":0.025390625,"height":0.011805556},"value":"5bb8fefc","help_text":"text/plain","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<template>\n <WelcomeLayout\n title=\"Account disconnected\"\n textPosition=\"center\"\n :icon=\"faUnlink\"\n :class=\"$style.layout\"\n >\n <div :class=\"$style.container\" v-if=\"providersLoaded\">\n <p>\n <strong>\n It looks like your {{ localProvider.displayName }} account has become\n disconnected\n </strong>\n </p>\n <p :class=\"$style.small\">Please re-connect to continue</p>\n <p v-if=\"isInIframe\">\n We'll open the {{ localProvider.displayName }} authentication in a new\n tab. Please return here and refresh the page once complete\n </p>\n\n <GoogleLikeButton\n v-if=\"localProvider.viaIntegrationApp && crmTokenLoaded\"\n as=\"a\"\n :key=\"localProvider.name\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n @click=\"integrationAppOnClick\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n <GoogleLikeButton\n v-if=\"!localProvider.viaIntegrationApp\"\n as=\"a\"\n :key=\"localProvider.name\"\n :href=\"`/auth/redirect/${localProvider.name}`\"\n :target=\"target\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n </div>\n <BuildInfo />\n\n <KioskBanner />\n </WelcomeLayout>\n</template>\n\n<script>\nimport window from \"window\";\nimport axios from \"axios\";\nimport { faUnlink } from \"@fortawesome/pro-regular-svg-icons\";\nimport isInIframe from \"@/utils/isInIframe\";\nimport BuildInfo from \"@/components/layout/BuildInfo/BuildInfo.vue\";\nimport KioskBanner from \"@/components/shared/KioskBanner/KioskBanner.vue\";\nimport WelcomeLayout from \"@/components/layout/WelcomeLayout/WelcomeLayout.vue\";\nimport GoogleLikeButton from \"@/components/shared/Buttons/GoogleLikeButton.vue\";\nimport { showSnackbarError, normalizeError } from \"@/utils/index\";\nimport { IntegrationAppClient } from \"@integration-app/sdk\";\n\nexport default {\n name: \"ConnectPage\",\n components: {\n BuildInfo,\n KioskBanner,\n WelcomeLayout,\n GoogleLikeButton,\n },\n data() {\n return {\n ...window.connectData,\n crmToken: null,\n faUnlink,\n isInIframe,\n providers: [],\n providersLoaded: false,\n crmTokenLoaded: false,\n };\n },\n computed: {\n localProvider() {\n return this.providers.find((e) => e.name === this.provider);\n },\n target() {\n return this.isInIframe ? \"_blank\" : null;\n },\n },\n created() {\n this.getProviders();\n },\n mounted() {\n this.showErrors();\n },\n watch: {\n providersLoaded() {\n if (this.providersLoaded) {\n this.prepareIntegrationAppConnection();\n }\n },\n },\n methods: {\n showErrors() {\n if (!this.error) return;\n\n showSnackbarError(this.error, undefined, undefined, false);\n },\n unwrapEntityResponse({ data }) {\n return data.map(({ icon, name, displayName, viaIntegrationApp }) => {\n return { icon, name, displayName, viaIntegrationApp };\n });\n },\n async getProviders() {\n try {\n const response = await axios.get(\"/api/v1/connect-providers\");\n this.providers = this.unwrapEntityResponse(response);\n this.providersLoaded = true;\n } catch {\n showSnackbarError(\n \"An error occurred, while loading form data (connect providers).\",\n );\n }\n },\n async prepareIntegrationAppConnection() {\n if (this.localProvider.viaIntegrationApp) {\n try {\n const response = await axios.get(\"/api/v1/integration-app-token\");\n this.crmToken = response.data.token;\n this.crmTokenLoaded = true;\n } catch (error) {\n console.log(error);\n showSnackbarError(\n `An error occurred while preparing the page.\n Try refreshing, if the error persists get in touch with the Jiminny team.`,\n );\n }\n }\n },\n async integrationAppOnClick() {\n const integrationApp = new IntegrationAppClient({\n token: this.crmToken,\n });\n\n const connection = await integrationApp\n .integration(this.localProvider.name)\n .openNewConnection({\n showPoweredBy: false,\n allowMultipleConnections: false,\n });\n\n if (connection && connection.disconnected === false) {\n try {\n const saveRequest = await axios.post(\n \"/api/v1/integration-app-connect\",\n );\n if (saveRequest.data && saveRequest.data.success === true) {\n /** If all is good refresh the page here */\n window.location = \"/dashboard\";\n return;\n }\n\n throw new Error(saveRequest.data.message);\n } catch (error) {\n console.log(error);\n showSnackbarError(normalizeError(error));\n }\n }\n },\n },\n};\n</script>\n\n<style module lang=\"less\" src=\"./connect.less\"></style>","depth":2,"bounds":{"left":0.25585938,"top":0.38333333,"width":0.1984375,"height":0.24444444},"value":"<template>\n <WelcomeLayout\n title=\"Account disconnected\"\n textPosition=\"center\"\n :icon=\"faUnlink\"\n :class=\"$style.layout\"\n >\n <div :class=\"$style.container\" v-if=\"providersLoaded\">\n <p>\n <strong>\n It looks like your {{ localProvider.displayName }} account has become\n disconnected\n </strong>\n </p>\n <p :class=\"$style.small\">Please re-connect to continue</p>\n <p v-if=\"isInIframe\">\n We'll open the {{ localProvider.displayName }} authentication in a new\n tab. Please return here and refresh the page once complete\n </p>\n\n <GoogleLikeButton\n v-if=\"localProvider.viaIntegrationApp && crmTokenLoaded\"\n as=\"a\"\n :key=\"localProvider.name\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n @click=\"integrationAppOnClick\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n <GoogleLikeButton\n v-if=\"!localProvider.viaIntegrationApp\"\n as=\"a\"\n :key=\"localProvider.name\"\n :href=\"`/auth/redirect/${localProvider.name}`\"\n :target=\"target\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n </div>\n <BuildInfo />\n\n <KioskBanner />\n </WelcomeLayout>\n</template>\n\n<script>\nimport window from \"window\";\nimport axios from \"axios\";\nimport { faUnlink } from \"@fortawesome/pro-regular-svg-icons\";\nimport isInIframe from \"@/utils/isInIframe\";\nimport BuildInfo from \"@/components/layout/BuildInfo/BuildInfo.vue\";\nimport KioskBanner from \"@/components/shared/KioskBanner/KioskBanner.vue\";\nimport WelcomeLayout from \"@/components/layout/WelcomeLayout/WelcomeLayout.vue\";\nimport GoogleLikeButton from \"@/components/shared/Buttons/GoogleLikeButton.vue\";\nimport { showSnackbarError, normalizeError } from \"@/utils/index\";\nimport { IntegrationAppClient } from \"@integration-app/sdk\";\n\nexport default {\n name: \"ConnectPage\",\n components: {\n BuildInfo,\n KioskBanner,\n WelcomeLayout,\n GoogleLikeButton,\n },\n data() {\n return {\n ...window.connectData,\n crmToken: null,\n faUnlink,\n isInIframe,\n providers: [],\n providersLoaded: false,\n crmTokenLoaded: false,\n };\n },\n computed: {\n localProvider() {\n return this.providers.find((e) => e.name === this.provider);\n },\n target() {\n return this.isInIframe ? \"_blank\" : null;\n },\n },\n created() {\n this.getProviders();\n },\n mounted() {\n this.showErrors();\n },\n watch: {\n providersLoaded() {\n if (this.providersLoaded) {\n this.prepareIntegrationAppConnection();\n }\n },\n },\n methods: {\n showErrors() {\n if (!this.error) return;\n\n showSnackbarError(this.error, undefined, undefined, false);\n },\n unwrapEntityResponse({ data }) {\n return data.map(({ icon, name, displayName, viaIntegrationApp }) => {\n return { icon, name, displayName, viaIntegrationApp };\n });\n },\n async getProviders() {\n try {\n const response = await axios.get(\"/api/v1/connect-providers\");\n this.providers = this.unwrapEntityResponse(response);\n this.providersLoaded = true;\n } catch {\n showSnackbarError(\n \"An error occurred, while loading form data (connect providers).\",\n );\n }\n },\n async prepareIntegrationAppConnection() {\n if (this.localProvider.viaIntegrationApp) {\n try {\n const response = await axios.get(\"/api/v1/integration-app-token\");\n this.crmToken = response.data.token;\n this.crmTokenLoaded = true;\n } catch (error) {\n console.log(error);\n showSnackbarError(\n `An error occurred while preparing the page.\n Try refreshing, if the error persists get in touch with the Jiminny team.`,\n );\n }\n }\n },\n async integrationAppOnClick() {\n const integrationApp = new IntegrationAppClient({\n token: this.crmToken,\n });\n\n const connection = await integrationApp\n .integration(this.localProvider.name)\n .openNewConnection({\n showPoweredBy: false,\n allowMultipleConnections: false,\n });\n\n if (connection && connection.disconnected === false) {\n try {\n const saveRequest = await axios.post(\n \"/api/v1/integration-app-connect\",\n );\n if (saveRequest.data && saveRequest.data.success === true) {\n /** If all is good refresh the page here */\n window.location = \"/dashboard\";\n return;\n }\n\n throw new Error(saveRequest.data.message);\n } catch (error) {\n console.log(error);\n showSnackbarError(normalizeError(error));\n }\n }\n },\n },\n};\n</script>\n\n<style module lang=\"less\" src=\"./connect.less\"></style>","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Current version","depth":1,"bounds":{"left":0.4699219,"top":0.3923611,"width":0.165625,"height":0.011805556},"value":"Current version","help_text":"text/plain","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<template>\n <WelcomeLayout\n title=\"Account disconnected\"\n textPosition=\"center\"\n :icon=\"faUnlink\"\n :class=\"$style.layout\"\n >\n <div :class=\"$style.container\" v-if=\"providersLoaded\">\n <p>\n <strong>\n It looks like your {{ localProvider.displayName }} account has become\n disconnected\n </strong>\n </p>\n <p :class=\"$style.small\">Please re-connect to continue</p>\n <p v-if=\"isInIframe\">\n We'll open the {{ localProvider.displayName }} authentication in a new\n tab. Please return here and refresh the page once complete\n </p>\n\n <GoogleLikeButton\n v-if=\"localProvider.viaIntegrationApp && crmTokenLoaded\"\n as=\"a\"\n :key=\"localProvider.name\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n @click=\"integrationAppOnClick\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n <GoogleLikeButton\n v-if=\"!localProvider.viaIntegrationApp\"\n as=\"a\"\n :key=\"localProvider.name\"\n :href=\"`/auth/redirect/${localProvider.name}`\"\n :target=\"target\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n </div>\n <BuildInfo />\n\n <KioskBanner />\n </WelcomeLayout>\n</template>\n\n<script>\nimport window from \"window\";\nimport axios from \"axios\";\nimport { faUnlink } from \"@fortawesome/pro-regular-svg-icons\";\nimport isInIframe from \"@/utils/isInIframe\";\nimport BuildInfo from \"@/components/layout/BuildInfo/BuildInfo.vue\";\nimport KioskBanner from \"@/components/shared/KioskBanner/KioskBanner.vue\";\nimport WelcomeLayout from \"@/components/layout/WelcomeLayout/WelcomeLayout.vue\";\nimport GoogleLikeButton from \"@/components/shared/Buttons/GoogleLikeButton.vue\";\nimport { showSnackbarError, normalizeError } from \"@/utils/index\";\nimport { IntegrationAppClient } from \"@integration-app/sdk\";\n\nexport default {\n name: \"ConnectPage\",\n components: {\n BuildInfo,\n KioskBanner,\n WelcomeLayout,\n GoogleLikeButton,\n },\n data() {\n return {\n ...window.connectData,\n crmToken: null,\n faUnlink,\n isInIframe,\n providers: [],\n providersLoaded: false,\n crmTokenLoaded: false,\n };\n },\n computed: {\n localProvider() {\n return this.providers.find((e) => e.name === this.provider);\n },\n target() {\n return this.isInIframe ? \"_blank\" : null;\n },\n },\n created() {\n this.getProviders();\n },\n mounted() {\n this.showErrors();\n },\n watch: {\n providersLoaded() {\n if (this.providersLoaded) {\n this.prepareIntegrationAppConnection();\n }\n },\n },\n methods: {\n showErrors() {\n if (!this.error) return;\n\n showSnackbarError(this.error, undefined, undefined, false);\n },\n unwrapEntityResponse({ data }) {\n return data.map(({ icon, name, displayName, viaIntegrationApp }) => {\n return { icon, name, displayName, viaIntegrationApp };\n });\n },\n async getProviders() {\n try {\n const response = await axios.get(\"/api/v1/connect-providers\");\n this.providers = this.unwrapEntityResponse(response);\n this.providersLoaded = true;\n } catch {\n showSnackbarError(\n \"An error occurred, while loading form data (connect providers).\",\n );\n }\n },\n async prepareIntegrationAppConnection() {\n if (this.localProvider.viaIntegrationApp) {\n try {\n const response = await axios.get(\"/api/v1/integration-app-token\");\n this.crmToken = response.data.token;\n this.crmTokenLoaded = true;\n } catch (error) {\n console.log(error);\n showSnackbarError(\n `An error occurred while preparing the page.\n Try refreshing, if the error persists get in touch with the Jiminny team.`,\n );\n }\n }\n },\n async integrationAppOnClick() {\n const integrationApp = new IntegrationAppClient({\n token: this.crmToken,\n });\n\n const connection = await integrationApp\n .integration(this.localProvider.name)\n .openNewConnection({\n showPoweredBy: false,\n allowMultipleConnections: false,\n });\n\n if (connection && connection.disconnected !== true && connection.connected !== false) {\n try {\n const saveRequest = await axios.post(\n \"/api/v1/integration-app-connect\",\n );\n if (saveRequest.data && saveRequest.data.success === true) {\n /** If all is good refresh the page here */\n window.location = \"/dashboard\";\n return;\n }\n\n throw new Error(saveRequest.data.message);\n } catch (error) {\n console.log(error);\n showSnackbarError(normalizeError(error));\n }\n }\n },\n },\n};\n</script>\n\n<style module lang=\"less\" src=\"./connect.less\"></style>","depth":2,"bounds":{"left":0.4753906,"top":0.38333333,"width":0.2921875,"height":0.24444444},"value":"<template>\n <WelcomeLayout\n title=\"Account disconnected\"\n textPosition=\"center\"\n :icon=\"faUnlink\"\n :class=\"$style.layout\"\n >\n <div :class=\"$style.container\" v-if=\"providersLoaded\">\n <p>\n <strong>\n It looks like your {{ localProvider.displayName }} account has become\n disconnected\n </strong>\n </p>\n <p :class=\"$style.small\">Please re-connect to continue</p>\n <p v-if=\"isInIframe\">\n We'll open the {{ localProvider.displayName }} authentication in a new\n tab. Please return here and refresh the page once complete\n </p>\n\n <GoogleLikeButton\n v-if=\"localProvider.viaIntegrationApp && crmTokenLoaded\"\n as=\"a\"\n :key=\"localProvider.name\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n @click=\"integrationAppOnClick\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n <GoogleLikeButton\n v-if=\"!localProvider.viaIntegrationApp\"\n as=\"a\"\n :key=\"localProvider.name\"\n :href=\"`/auth/redirect/${localProvider.name}`\"\n :target=\"target\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n </div>\n <BuildInfo />\n\n <KioskBanner />\n </WelcomeLayout>\n</template>\n\n<script>\nimport window from \"window\";\nimport axios from \"axios\";\nimport { faUnlink } from \"@fortawesome/pro-regular-svg-icons\";\nimport isInIframe from \"@/utils/isInIframe\";\nimport BuildInfo from \"@/components/layout/BuildInfo/BuildInfo.vue\";\nimport KioskBanner from \"@/components/shared/KioskBanner/KioskBanner.vue\";\nimport WelcomeLayout from \"@/components/layout/WelcomeLayout/WelcomeLayout.vue\";\nimport GoogleLikeButton from \"@/components/shared/Buttons/GoogleLikeButton.vue\";\nimport { showSnackbarError, normalizeError } from \"@/utils/index\";\nimport { IntegrationAppClient } from \"@integration-app/sdk\";\n\nexport default {\n name: \"ConnectPage\",\n components: {\n BuildInfo,\n KioskBanner,\n WelcomeLayout,\n GoogleLikeButton,\n },\n data() {\n return {\n ...window.connectData,\n crmToken: null,\n faUnlink,\n isInIframe,\n providers: [],\n providersLoaded: false,\n crmTokenLoaded: false,\n };\n },\n computed: {\n localProvider() {\n return this.providers.find((e) => e.name === this.provider);\n },\n target() {\n return this.isInIframe ? \"_blank\" : null;\n },\n },\n created() {\n this.getProviders();\n },\n mounted() {\n this.showErrors();\n },\n watch: {\n providersLoaded() {\n if (this.providersLoaded) {\n this.prepareIntegrationAppConnection();\n }\n },\n },\n methods: {\n showErrors() {\n if (!this.error) return;\n\n showSnackbarError(this.error, undefined, undefined, false);\n },\n unwrapEntityResponse({ data }) {\n return data.map(({ icon, name, displayName, viaIntegrationApp }) => {\n return { icon, name, displayName, viaIntegrationApp };\n });\n },\n async getProviders() {\n try {\n const response = await axios.get(\"/api/v1/connect-providers\");\n this.providers = this.unwrapEntityResponse(response);\n this.providersLoaded = true;\n } catch {\n showSnackbarError(\n \"An error occurred, while loading form data (connect providers).\",\n );\n }\n },\n async prepareIntegrationAppConnection() {\n if (this.localProvider.viaIntegrationApp) {\n try {\n const response = await axios.get(\"/api/v1/integration-app-token\");\n this.crmToken = response.data.token;\n this.crmTokenLoaded = true;\n } catch (error) {\n console.log(error);\n showSnackbarError(\n `An error occurred while preparing the page.\n Try refreshing, if the error persists get in touch with the Jiminny team.`,\n );\n }\n }\n },\n async integrationAppOnClick() {\n const integrationApp = new IntegrationAppClient({\n token: this.crmToken,\n });\n\n const connection = await integrationApp\n .integration(this.localProvider.name)\n .openNewConnection({\n showPoweredBy: false,\n allowMultipleConnections: false,\n });\n\n if (connection && connection.disconnected !== true && connection.connected !== false) {\n try {\n const saveRequest = await axios.post(\n \"/api/v1/integration-app-connect\",\n );\n if (saveRequest.data && saveRequest.data.success === true) {\n /** If all is good refresh the page here */\n window.location = \"/dashboard\";\n return;\n }\n\n throw new Error(saveRequest.data.message);\n } catch (error) {\n console.log(error);\n showSnackbarError(normalizeError(error));\n }\n }\n },\n },\n};\n</script>\n\n<style module lang=\"less\" src=\"./connect.less\"></style>","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Help","depth":1,"bounds":{"left":0.25585938,"top":0.5833333,"width":0.0109375,"height":0.023611112},"help_text":"Show help contents","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel","depth":1,"bounds":{"left":0.5601562,"top":0.5833333,"width":0.03046875,"height":0.023611112},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Commit","depth":1,"bounds":{"left":0.59296876,"top":0.5833333,"width":0.044140626,"height":0.023611112},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Commit","depth":2,"bounds":{"left":0.59296876,"top":0.5833333,"width":0.03203125,"height":0.023611112},"help_text":"Show drop-down menu (⌥⇧⏎)","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-1891754827552789574
|
-8000317044650091579
|
idle
|
accessibility
|
NULL
|
Diff
Changelist:
Changelist:
Changes
Show Diff
Rol Diff
Changelist:
Changelist:
Changes
Show Diff
Rollback...
Refresh
Group By
Expand All
Collapse All
2 modified
JY-20692
Commit Message
Commit Message History
Git
Author:
Author:
Amend commit
Sign-off commit
Create extra commit with file movements
Commit Checks
Update copyright
Reformat code
Rearrange code
Optimize imports
Cleanup
Choose profile
Check malicious dependencies
Run external formatter
Configure
Perform SonarQube for IDE analysis
Find unused view files
Run Git hooks
Advanced Commit Checks
Analyze code
Choose profile
Check TODO
Configure
After Commit
Upload files to:
Upload files to:
<None>
Always use selected server or group of servers
Previous Difference
Next Difference
Jump to Source
Compare Previous File
Compare Next File
Go to Changed File…
Side-by-side viewer
Do not ignore
Highlight words
Collapse Unchanged Fragments
Synchronize Scrolling
Disable Editing
Settings
Help
1 difference
5bb8fefc
<template>
<WelcomeLayout
title="Account disconnected"
textPosition="center"
:icon="faUnlink"
:class="$style.layout"
>
<div :class="$style.container" v-if="providersLoaded">
<p>
<strong>
It looks like your {{ localProvider.displayName }} account has become
disconnected
</strong>
</p>
<p :class="$style.small">Please re-connect to continue</p>
<p v-if="isInIframe">
We'll open the {{ localProvider.displayName }} authentication in a new
tab. Please return here and refresh the page once complete
</p>
<GoogleLikeButton
v-if="localProvider.viaIntegrationApp && crmTokenLoaded"
as="a"
:key="localProvider.name"
:brand-logo="localProvider.name"
:class="$style.connectButton"
@click="integrationAppOnClick"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
<GoogleLikeButton
v-if="!localProvider.viaIntegrationApp"
as="a"
:key="localProvider.name"
:href="`/auth/redirect/${localProvider.name}`"
:target="target"
:brand-logo="localProvider.name"
:class="$style.connectButton"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
</div>
<BuildInfo />
<KioskBanner />
</WelcomeLayout>
</template>
<script>
import window from "window";
import axios from "axios";
import { faUnlink } from "@fortawesome/pro-regular-svg-icons";
import isInIframe from "@/utils/isInIframe";
import BuildInfo from "@/components/layout/BuildInfo/BuildInfo.vue";
import KioskBanner from "@/components/shared/KioskBanner/KioskBanner.vue";
import WelcomeLayout from "@/components/layout/WelcomeLayout/WelcomeLayout.vue";
import GoogleLikeButton from "@/components/shared/Buttons/GoogleLikeButton.vue";
import { showSnackbarError, normalizeError } from "@/utils/index";
import { IntegrationAppClient } from "@integration-app/sdk";
export default {
name: "ConnectPage",
components: {
BuildInfo,
KioskBanner,
WelcomeLayout,
GoogleLikeButton,
},
data() {
return {
...window.connectData,
crmToken: null,
faUnlink,
isInIframe,
providers: [],
providersLoaded: false,
crmTokenLoaded: false,
};
},
computed: {
localProvider() {
return this.providers.find((e) => e.name === this.provider);
},
target() {
return this.isInIframe ? "_blank" : null;
},
},
created() {
this.getProviders();
},
mounted() {
this.showErrors();
},
watch: {
providersLoaded() {
if (this.providersLoaded) {
this.prepareIntegrationAppConnection();
}
},
},
methods: {
showErrors() {
if (!this.error) return;
showSnackbarError(this.error, undefined, undefined, false);
},
unwrapEntityResponse({ data }) {
return data.map(({ icon, name, displayName, viaIntegrationApp }) => {
return { icon, name, displayName, viaIntegrationApp };
});
},
async getProviders() {
try {
const response = await axios.get("/api/v1/connect-providers");
this.providers = this.unwrapEntityResponse(response);
this.providersLoaded = true;
} catch {
showSnackbarError(
"An error occurred, while loading form data (connect providers).",
);
}
},
async prepareIntegrationAppConnection() {
if (this.localProvider.viaIntegrationApp) {
try {
const response = await axios.get("/api/v1/integration-app-token");
this.crmToken = response.data.token;
this.crmTokenLoaded = true;
} catch (error) {
console.log(error);
showSnackbarError(
`An error occurred while preparing the page.
Try refreshing, if the error persists get in touch with the Jiminny team.`,
);
}
}
},
async integrationAppOnClick() {
const integrationApp = new IntegrationAppClient({
token: this.crmToken,
});
const connection = await integrationApp
.integration(this.localProvider.name)
.openNewConnection({
showPoweredBy: false,
allowMultipleConnections: false,
});
if (connection && connection.disconnected === false) {
try {
const saveRequest = await axios.post(
"/api/v1/integration-app-connect",
);
if (saveRequest.data && saveRequest.data.success === true) {
/** If all is good refresh the page here */
window.location = "/dashboard";
return;
}
throw new Error(saveRequest.data.message);
} catch (error) {
console.log(error);
showSnackbarError(normalizeError(error));
}
}
},
},
};
</script>
<style module lang="less" src="./connect.less"></style>
Current version
<template>
<WelcomeLayout
title="Account disconnected"
textPosition="center"
:icon="faUnlink"
:class="$style.layout"
>
<div :class="$style.container" v-if="providersLoaded">
<p>
<strong>
It looks like your {{ localProvider.displayName }} account has become
disconnected
</strong>
</p>
<p :class="$style.small">Please re-connect to continue</p>
<p v-if="isInIframe">
We'll open the {{ localProvider.displayName }} authentication in a new
tab. Please return here and refresh the page once complete
</p>
<GoogleLikeButton
v-if="localProvider.viaIntegrationApp && crmTokenLoaded"
as="a"
:key="localProvider.name"
:brand-logo="localProvider.name"
:class="$style.connectButton"
@click="integrationAppOnClick"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
<GoogleLikeButton
v-if="!localProvider.viaIntegrationApp"
as="a"
:key="localProvider.name"
:href="`/auth/redirect/${localProvider.name}`"
:target="target"
:brand-logo="localProvider.name"
:class="$style.connectButton"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
</div>
<BuildInfo />
<KioskBanner />
</WelcomeLayout>
</template>
<script>
import window from "window";
import axios from "axios";
import { faUnlink } from "@fortawesome/pro-regular-svg-icons";
import isInIframe from "@/utils/isInIframe";
import BuildInfo from "@/components/layout/BuildInfo/BuildInfo.vue";
import KioskBanner from "@/components/shared/KioskBanner/KioskBanner.vue";
import WelcomeLayout from "@/components/layout/WelcomeLayout/WelcomeLayout.vue";
import GoogleLikeButton from "@/components/shared/Buttons/GoogleLikeButton.vue";
import { showSnackbarError, normalizeError } from "@/utils/index";
import { IntegrationAppClient } from "@integration-app/sdk";
export default {
name: "ConnectPage",
components: {
BuildInfo,
KioskBanner,
WelcomeLayout,
GoogleLikeButton,
},
data() {
return {
...window.connectData,
crmToken: null,
faUnlink,
isInIframe,
providers: [],
providersLoaded: false,
crmTokenLoaded: false,
};
},
computed: {
localProvider() {
return this.providers.find((e) => e.name === this.provider);
},
target() {
return this.isInIframe ? "_blank" : null;
},
},
created() {
this.getProviders();
},
mounted() {
this.showErrors();
},
watch: {
providersLoaded() {
if (this.providersLoaded) {
this.prepareIntegrationAppConnection();
}
},
},
methods: {
showErrors() {
if (!this.error) return;
showSnackbarError(this.error, undefined, undefined, false);
},
unwrapEntityResponse({ data }) {
return data.map(({ icon, name, displayName, viaIntegrationApp }) => {
return { icon, name, displayName, viaIntegrationApp };
});
},
async getProviders() {
try {
const response = await axios.get("/api/v1/connect-providers");
this.providers = this.unwrapEntityResponse(response);
this.providersLoaded = true;
} catch {
showSnackbarError(
"An error occurred, while loading form data (connect providers).",
);
}
},
async prepareIntegrationAppConnection() {
if (this.localProvider.viaIntegrationApp) {
try {
const response = await axios.get("/api/v1/integration-app-token");
this.crmToken = response.data.token;
this.crmTokenLoaded = true;
} catch (error) {
console.log(error);
showSnackbarError(
`An error occurred while preparing the page.
Try refreshing, if the error persists get in touch with the Jiminny team.`,
);
}
}
},
async integrationAppOnClick() {
const integrationApp = new IntegrationAppClient({
token: this.crmToken,
});
const connection = await integrationApp
.integration(this.localProvider.name)
.openNewConnection({
showPoweredBy: false,
allowMultipleConnections: false,
});
if (connection && connection.disconnected !== true && connection.connected !== false) {
try {
const saveRequest = await axios.post(
"/api/v1/integration-app-connect",
);
if (saveRequest.data && saveRequest.data.success === true) {
/** If all is good refresh the page here */
window.location = "/dashboard";
return;
}
throw new Error(saveRequest.data.message);
} catch (error) {
console.log(error);
showSnackbarError(normalizeError(error));
}
}
},
},
};
</script>
<style module lang="less" src="./connect.less"></style>
Help
Cancel
Commit
Commit...
|
47856
|
|
47857
|
NULL
|
0
|
2026-04-17T12:06:19.731503+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776427579731_m1.jpg...
|
PhpStorm
|
Commit Changes
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Diff
Changelist:
Changelist:
Changes
Show Diff
Rol Diff
Changelist:
Changelist:
Changes
Show Diff
Rollback...
Refresh
Group By
Expand All
Collapse All
2 modified
JY-20692
Commit Message
Commit Message History
Git
Author:
Author:
Amend commit
Sign-off commit
Create extra commit with file movements
Commit Checks
Update copyright
Reformat code
Rearrange code
Optimize imports
Cleanup
Choose profile
Check malicious dependencies
Run external formatter
Configure
Perform SonarQube for IDE analysis
Find unused view files
Run Git hooks
Advanced Commit Checks
Analyze code
Choose profile
Check TODO
Configure
After Commit
Upload files to:
Upload files to:
<None>
Always use selected server or group of servers
Previous Difference
Next Difference
Jump to Source
Compare Previous File
Compare Next File
Go to Changed File…
Side-by-side viewer
Do not ignore
Highlight words
Collapse Unchanged Fragments
Synchronize Scrolling
Disable Editing
Settings
Help
1 difference
5bb8fefc
<template>
<WelcomeLayout
title="Account disconnected"
textPosition="center"
:icon="faUnlink"
:class="$style.layout"
>
<div :class="$style.container" v-if="providersLoaded">
<p>
<strong>
It looks like your {{ localProvider.displayName }} account has become
disconnected
</strong>
</p>
<p :class="$style.small">Please re-connect to continue</p>
<p v-if="isInIframe">
We'll open the {{ localProvider.displayName }} authentication in a new
tab. Please return here and refresh the page once complete
</p>
<GoogleLikeButton
v-if="localProvider.viaIntegrationApp && crmTokenLoaded"
as="a"
:key="localProvider.name"
:brand-logo="localProvider.name"
:class="$style.connectButton"
@click="integrationAppOnClick"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
<GoogleLikeButton
v-if="!localProvider.viaIntegrationApp"
as="a"
:key="localProvider.name"
:href="`/auth/redirect/${localProvider.name}`"
:target="target"
:brand-logo="localProvider.name"
:class="$style.connectButton"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
</div>
<BuildInfo />
<KioskBanner />
</WelcomeLayout>
</template>
<script>
import window from "window";
import axios from "axios";
import { faUnlink } from "@fortawesome/pro-regular-svg-icons";
import isInIframe from "@/utils/isInIframe";
import BuildInfo from "@/components/layout/BuildInfo/BuildInfo.vue";
import KioskBanner from "@/components/shared/KioskBanner/KioskBanner.vue";
import WelcomeLayout from "@/components/layout/WelcomeLayout/WelcomeLayout.vue";
import GoogleLikeButton from "@/components/shared/Buttons/GoogleLikeButton.vue";
import { showSnackbarError, normalizeError } from "@/utils/index";
import { IntegrationAppClient } from "@integration-app/sdk";
export default {
name: "ConnectPage",
components: {
BuildInfo,
KioskBanner,
WelcomeLayout,
GoogleLikeButton,
},
data() {
return {
...window.connectData,
crmToken: null,
faUnlink,
isInIframe,
providers: [],
providersLoaded: false,
crmTokenLoaded: false,
};
},
computed: {
localProvider() {
return this.providers.find((e) => e.name === this.provider);
},
target() {
return this.isInIframe ? "_blank" : null;
},
},
created() {
this.getProviders();
},
mounted() {
this.showErrors();
},
watch: {
providersLoaded() {
if (this.providersLoaded) {
this.prepareIntegrationAppConnection();
}
},
},
methods: {
showErrors() {
if (!this.error) return;
showSnackbarError(this.error, undefined, undefined, false);
},
unwrapEntityResponse({ data }) {
return data.map(({ icon, name, displayName, viaIntegrationApp }) => {
return { icon, name, displayName, viaIntegrationApp };
});
},
async getProviders() {
try {
const response = await axios.get("/api/v1/connect-providers");
this.providers = this.unwrapEntityResponse(response);
this.providersLoaded = true;
} catch {
showSnackbarError(
"An error occurred, while loading form data (connect providers).",
);
}
},
async prepareIntegrationAppConnection() {
if (this.localProvider.viaIntegrationApp) {
try {
const response = await axios.get("/api/v1/integration-app-token");
this.crmToken = response.data.token;
this.crmTokenLoaded = true;
} catch (error) {
console.log(error);
showSnackbarError(
`An error occurred while preparing the page.
Try refreshing, if the error persists get in touch with the Jiminny team.`,
);
}
}
},
async integrationAppOnClick() {
const integrationApp = new IntegrationAppClient({
token: this.crmToken,
});
const connection = await integrationApp
.integration(this.localProvider.name)
.openNewConnection({
showPoweredBy: false,
allowMultipleConnections: false,
});
if (connection && connection.disconnected === false) {
try {
const saveRequest = await axios.post(
"/api/v1/integration-app-connect",
);
if (saveRequest.data && saveRequest.data.success === true) {
/** If all is good refresh the page here */
window.location = "/dashboard";
return;
}
throw new Error(saveRequest.data.message);
} catch (error) {
console.log(error);
showSnackbarError(normalizeError(error));
}
}
},
},
};
</script>
<style module lang="less" src="./connect.less"></style>
Current version...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"Diff","depth":1,"role_description":"text"},{"role":"AXStaticText","text":"Changelist:","depth":1,"role_description":"text"},{"role":"AXPopUpButton","text":"Changelist:","depth":1,"role_description":"pop up button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Changes","depth":6,"role_description":"text"},{"role":"AXButton","text":"Show Diff","depth":2,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Rollback...","depth":2,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Refresh","depth":2,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Group By","depth":2,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand All","depth":2,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":2,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2 modified","depth":1,"role_description":"text"},{"role":"AXTextArea","text":"JY-20692","depth":2,"value":"JY-20692","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Commit Message","depth":1,"role_description":"text"},{"role":"AXButton","text":"Commit Message History","depth":2,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Git","depth":2,"role_description":"text"},{"role":"AXStaticText","text":"Author:","depth":2,"role_description":"text"},{"role":"AXTextArea","text":"Author:","depth":3,"role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Amend commit","depth":2,"help_text":"Merge this commit with the previous one","role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Sign-off commit","depth":2,"help_text":"<html>Adds the following line at the end of the commit message:<br/>Signed-off by: Lukas Kovalik <kovaliklukas@gmail.com></html>","role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Create extra commit with file movements","depth":2,"role_description":"checkbox","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Commit Checks","depth":2,"role_description":"text"},{"role":"AXCheckBox","text":"Update copyright","depth":2,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Reformat code","depth":2,"help_text":"","role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Rearrange code","depth":2,"help_text":"","role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Optimize imports","depth":2,"help_text":"","role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Cleanup","depth":2,"help_text":"","role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Choose profile","depth":2,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Check malicious dependencies","depth":2,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Run external formatter","depth":2,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Configure","depth":2,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Perform SonarQube for IDE analysis","depth":2,"help_text":"","role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Find unused view files","depth":2,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Run Git hooks","depth":2,"help_text":"If unchecked, Git hooks will be skipped with the '--no-verify' option for the upcoming commit","role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Advanced Commit Checks","depth":2,"role_description":"text"},{"role":"AXCheckBox","text":"Analyze code","depth":2,"help_text":"","role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Choose profile","depth":2,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Check TODO","depth":2,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Configure","depth":2,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"After Commit","depth":2,"role_description":"text"},{"role":"AXStaticText","text":"Upload files to:","depth":2,"role_description":"text"},{"role":"AXPopUpButton","text":"Upload files to:","depth":2,"role_description":"pop up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"<None>","depth":7,"role_description":"text"},{"role":"AXCheckBox","text":"Always use selected server or group of servers","depth":2,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Previous Difference","depth":2,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Difference","depth":2,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Jump to Source","depth":2,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Compare Previous File","depth":2,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Compare Next File","depth":2,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Go to Changed File…","depth":2,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Side-by-side viewer","depth":2,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Do not ignore","depth":2,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Highlight words","depth":2,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse Unchanged Fragments","depth":2,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Synchronize Scrolling","depth":2,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Disable Editing","depth":2,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Settings","depth":2,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Help","depth":2,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1 difference","depth":1,"role_description":"text"},{"role":"AXTextField","text":"5bb8fefc","depth":1,"value":"5bb8fefc","help_text":"text/plain","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<template>\n <WelcomeLayout\n title=\"Account disconnected\"\n textPosition=\"center\"\n :icon=\"faUnlink\"\n :class=\"$style.layout\"\n >\n <div :class=\"$style.container\" v-if=\"providersLoaded\">\n <p>\n <strong>\n It looks like your {{ localProvider.displayName }} account has become\n disconnected\n </strong>\n </p>\n <p :class=\"$style.small\">Please re-connect to continue</p>\n <p v-if=\"isInIframe\">\n We'll open the {{ localProvider.displayName }} authentication in a new\n tab. Please return here and refresh the page once complete\n </p>\n\n <GoogleLikeButton\n v-if=\"localProvider.viaIntegrationApp && crmTokenLoaded\"\n as=\"a\"\n :key=\"localProvider.name\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n @click=\"integrationAppOnClick\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n <GoogleLikeButton\n v-if=\"!localProvider.viaIntegrationApp\"\n as=\"a\"\n :key=\"localProvider.name\"\n :href=\"`/auth/redirect/${localProvider.name}`\"\n :target=\"target\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n </div>\n <BuildInfo />\n\n <KioskBanner />\n </WelcomeLayout>\n</template>\n\n<script>\nimport window from \"window\";\nimport axios from \"axios\";\nimport { faUnlink } from \"@fortawesome/pro-regular-svg-icons\";\nimport isInIframe from \"@/utils/isInIframe\";\nimport BuildInfo from \"@/components/layout/BuildInfo/BuildInfo.vue\";\nimport KioskBanner from \"@/components/shared/KioskBanner/KioskBanner.vue\";\nimport WelcomeLayout from \"@/components/layout/WelcomeLayout/WelcomeLayout.vue\";\nimport GoogleLikeButton from \"@/components/shared/Buttons/GoogleLikeButton.vue\";\nimport { showSnackbarError, normalizeError } from \"@/utils/index\";\nimport { IntegrationAppClient } from \"@integration-app/sdk\";\n\nexport default {\n name: \"ConnectPage\",\n components: {\n BuildInfo,\n KioskBanner,\n WelcomeLayout,\n GoogleLikeButton,\n },\n data() {\n return {\n ...window.connectData,\n crmToken: null,\n faUnlink,\n isInIframe,\n providers: [],\n providersLoaded: false,\n crmTokenLoaded: false,\n };\n },\n computed: {\n localProvider() {\n return this.providers.find((e) => e.name === this.provider);\n },\n target() {\n return this.isInIframe ? \"_blank\" : null;\n },\n },\n created() {\n this.getProviders();\n },\n mounted() {\n this.showErrors();\n },\n watch: {\n providersLoaded() {\n if (this.providersLoaded) {\n this.prepareIntegrationAppConnection();\n }\n },\n },\n methods: {\n showErrors() {\n if (!this.error) return;\n\n showSnackbarError(this.error, undefined, undefined, false);\n },\n unwrapEntityResponse({ data }) {\n return data.map(({ icon, name, displayName, viaIntegrationApp }) => {\n return { icon, name, displayName, viaIntegrationApp };\n });\n },\n async getProviders() {\n try {\n const response = await axios.get(\"/api/v1/connect-providers\");\n this.providers = this.unwrapEntityResponse(response);\n this.providersLoaded = true;\n } catch {\n showSnackbarError(\n \"An error occurred, while loading form data (connect providers).\",\n );\n }\n },\n async prepareIntegrationAppConnection() {\n if (this.localProvider.viaIntegrationApp) {\n try {\n const response = await axios.get(\"/api/v1/integration-app-token\");\n this.crmToken = response.data.token;\n this.crmTokenLoaded = true;\n } catch (error) {\n console.log(error);\n showSnackbarError(\n `An error occurred while preparing the page.\n Try refreshing, if the error persists get in touch with the Jiminny team.`,\n );\n }\n }\n },\n async integrationAppOnClick() {\n const integrationApp = new IntegrationAppClient({\n token: this.crmToken,\n });\n\n const connection = await integrationApp\n .integration(this.localProvider.name)\n .openNewConnection({\n showPoweredBy: false,\n allowMultipleConnections: false,\n });\n\n if (connection && connection.disconnected === false) {\n try {\n const saveRequest = await axios.post(\n \"/api/v1/integration-app-connect\",\n );\n if (saveRequest.data && saveRequest.data.success === true) {\n /** If all is good refresh the page here */\n window.location = \"/dashboard\";\n return;\n }\n\n throw new Error(saveRequest.data.message);\n } catch (error) {\n console.log(error);\n showSnackbarError(normalizeError(error));\n }\n }\n },\n },\n};\n</script>\n\n<style module lang=\"less\" src=\"./connect.less\"></style>","depth":2,"value":"<template>\n <WelcomeLayout\n title=\"Account disconnected\"\n textPosition=\"center\"\n :icon=\"faUnlink\"\n :class=\"$style.layout\"\n >\n <div :class=\"$style.container\" v-if=\"providersLoaded\">\n <p>\n <strong>\n It looks like your {{ localProvider.displayName }} account has become\n disconnected\n </strong>\n </p>\n <p :class=\"$style.small\">Please re-connect to continue</p>\n <p v-if=\"isInIframe\">\n We'll open the {{ localProvider.displayName }} authentication in a new\n tab. Please return here and refresh the page once complete\n </p>\n\n <GoogleLikeButton\n v-if=\"localProvider.viaIntegrationApp && crmTokenLoaded\"\n as=\"a\"\n :key=\"localProvider.name\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n @click=\"integrationAppOnClick\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n <GoogleLikeButton\n v-if=\"!localProvider.viaIntegrationApp\"\n as=\"a\"\n :key=\"localProvider.name\"\n :href=\"`/auth/redirect/${localProvider.name}`\"\n :target=\"target\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n </div>\n <BuildInfo />\n\n <KioskBanner />\n </WelcomeLayout>\n</template>\n\n<script>\nimport window from \"window\";\nimport axios from \"axios\";\nimport { faUnlink } from \"@fortawesome/pro-regular-svg-icons\";\nimport isInIframe from \"@/utils/isInIframe\";\nimport BuildInfo from \"@/components/layout/BuildInfo/BuildInfo.vue\";\nimport KioskBanner from \"@/components/shared/KioskBanner/KioskBanner.vue\";\nimport WelcomeLayout from \"@/components/layout/WelcomeLayout/WelcomeLayout.vue\";\nimport GoogleLikeButton from \"@/components/shared/Buttons/GoogleLikeButton.vue\";\nimport { showSnackbarError, normalizeError } from \"@/utils/index\";\nimport { IntegrationAppClient } from \"@integration-app/sdk\";\n\nexport default {\n name: \"ConnectPage\",\n components: {\n BuildInfo,\n KioskBanner,\n WelcomeLayout,\n GoogleLikeButton,\n },\n data() {\n return {\n ...window.connectData,\n crmToken: null,\n faUnlink,\n isInIframe,\n providers: [],\n providersLoaded: false,\n crmTokenLoaded: false,\n };\n },\n computed: {\n localProvider() {\n return this.providers.find((e) => e.name === this.provider);\n },\n target() {\n return this.isInIframe ? \"_blank\" : null;\n },\n },\n created() {\n this.getProviders();\n },\n mounted() {\n this.showErrors();\n },\n watch: {\n providersLoaded() {\n if (this.providersLoaded) {\n this.prepareIntegrationAppConnection();\n }\n },\n },\n methods: {\n showErrors() {\n if (!this.error) return;\n\n showSnackbarError(this.error, undefined, undefined, false);\n },\n unwrapEntityResponse({ data }) {\n return data.map(({ icon, name, displayName, viaIntegrationApp }) => {\n return { icon, name, displayName, viaIntegrationApp };\n });\n },\n async getProviders() {\n try {\n const response = await axios.get(\"/api/v1/connect-providers\");\n this.providers = this.unwrapEntityResponse(response);\n this.providersLoaded = true;\n } catch {\n showSnackbarError(\n \"An error occurred, while loading form data (connect providers).\",\n );\n }\n },\n async prepareIntegrationAppConnection() {\n if (this.localProvider.viaIntegrationApp) {\n try {\n const response = await axios.get(\"/api/v1/integration-app-token\");\n this.crmToken = response.data.token;\n this.crmTokenLoaded = true;\n } catch (error) {\n console.log(error);\n showSnackbarError(\n `An error occurred while preparing the page.\n Try refreshing, if the error persists get in touch with the Jiminny team.`,\n );\n }\n }\n },\n async integrationAppOnClick() {\n const integrationApp = new IntegrationAppClient({\n token: this.crmToken,\n });\n\n const connection = await integrationApp\n .integration(this.localProvider.name)\n .openNewConnection({\n showPoweredBy: false,\n allowMultipleConnections: false,\n });\n\n if (connection && connection.disconnected === false) {\n try {\n const saveRequest = await axios.post(\n \"/api/v1/integration-app-connect\",\n );\n if (saveRequest.data && saveRequest.data.success === true) {\n /** If all is good refresh the page here */\n window.location = \"/dashboard\";\n return;\n }\n\n throw new Error(saveRequest.data.message);\n } catch (error) {\n console.log(error);\n showSnackbarError(normalizeError(error));\n }\n }\n },\n },\n};\n</script>\n\n<style module lang=\"less\" src=\"./connect.less\"></style>","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Current version","depth":1,"value":"Current version","help_text":"text/plain","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
6104486290665866948
|
-8009324243367978171
|
idle
|
accessibility
|
NULL
|
Diff
Changelist:
Changelist:
Changes
Show Diff
Rol Diff
Changelist:
Changelist:
Changes
Show Diff
Rollback...
Refresh
Group By
Expand All
Collapse All
2 modified
JY-20692
Commit Message
Commit Message History
Git
Author:
Author:
Amend commit
Sign-off commit
Create extra commit with file movements
Commit Checks
Update copyright
Reformat code
Rearrange code
Optimize imports
Cleanup
Choose profile
Check malicious dependencies
Run external formatter
Configure
Perform SonarQube for IDE analysis
Find unused view files
Run Git hooks
Advanced Commit Checks
Analyze code
Choose profile
Check TODO
Configure
After Commit
Upload files to:
Upload files to:
<None>
Always use selected server or group of servers
Previous Difference
Next Difference
Jump to Source
Compare Previous File
Compare Next File
Go to Changed File…
Side-by-side viewer
Do not ignore
Highlight words
Collapse Unchanged Fragments
Synchronize Scrolling
Disable Editing
Settings
Help
1 difference
5bb8fefc
<template>
<WelcomeLayout
title="Account disconnected"
textPosition="center"
:icon="faUnlink"
:class="$style.layout"
>
<div :class="$style.container" v-if="providersLoaded">
<p>
<strong>
It looks like your {{ localProvider.displayName }} account has become
disconnected
</strong>
</p>
<p :class="$style.small">Please re-connect to continue</p>
<p v-if="isInIframe">
We'll open the {{ localProvider.displayName }} authentication in a new
tab. Please return here and refresh the page once complete
</p>
<GoogleLikeButton
v-if="localProvider.viaIntegrationApp && crmTokenLoaded"
as="a"
:key="localProvider.name"
:brand-logo="localProvider.name"
:class="$style.connectButton"
@click="integrationAppOnClick"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
<GoogleLikeButton
v-if="!localProvider.viaIntegrationApp"
as="a"
:key="localProvider.name"
:href="`/auth/redirect/${localProvider.name}`"
:target="target"
:brand-logo="localProvider.name"
:class="$style.connectButton"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
</div>
<BuildInfo />
<KioskBanner />
</WelcomeLayout>
</template>
<script>
import window from "window";
import axios from "axios";
import { faUnlink } from "@fortawesome/pro-regular-svg-icons";
import isInIframe from "@/utils/isInIframe";
import BuildInfo from "@/components/layout/BuildInfo/BuildInfo.vue";
import KioskBanner from "@/components/shared/KioskBanner/KioskBanner.vue";
import WelcomeLayout from "@/components/layout/WelcomeLayout/WelcomeLayout.vue";
import GoogleLikeButton from "@/components/shared/Buttons/GoogleLikeButton.vue";
import { showSnackbarError, normalizeError } from "@/utils/index";
import { IntegrationAppClient } from "@integration-app/sdk";
export default {
name: "ConnectPage",
components: {
BuildInfo,
KioskBanner,
WelcomeLayout,
GoogleLikeButton,
},
data() {
return {
...window.connectData,
crmToken: null,
faUnlink,
isInIframe,
providers: [],
providersLoaded: false,
crmTokenLoaded: false,
};
},
computed: {
localProvider() {
return this.providers.find((e) => e.name === this.provider);
},
target() {
return this.isInIframe ? "_blank" : null;
},
},
created() {
this.getProviders();
},
mounted() {
this.showErrors();
},
watch: {
providersLoaded() {
if (this.providersLoaded) {
this.prepareIntegrationAppConnection();
}
},
},
methods: {
showErrors() {
if (!this.error) return;
showSnackbarError(this.error, undefined, undefined, false);
},
unwrapEntityResponse({ data }) {
return data.map(({ icon, name, displayName, viaIntegrationApp }) => {
return { icon, name, displayName, viaIntegrationApp };
});
},
async getProviders() {
try {
const response = await axios.get("/api/v1/connect-providers");
this.providers = this.unwrapEntityResponse(response);
this.providersLoaded = true;
} catch {
showSnackbarError(
"An error occurred, while loading form data (connect providers).",
);
}
},
async prepareIntegrationAppConnection() {
if (this.localProvider.viaIntegrationApp) {
try {
const response = await axios.get("/api/v1/integration-app-token");
this.crmToken = response.data.token;
this.crmTokenLoaded = true;
} catch (error) {
console.log(error);
showSnackbarError(
`An error occurred while preparing the page.
Try refreshing, if the error persists get in touch with the Jiminny team.`,
);
}
}
},
async integrationAppOnClick() {
const integrationApp = new IntegrationAppClient({
token: this.crmToken,
});
const connection = await integrationApp
.integration(this.localProvider.name)
.openNewConnection({
showPoweredBy: false,
allowMultipleConnections: false,
});
if (connection && connection.disconnected === false) {
try {
const saveRequest = await axios.post(
"/api/v1/integration-app-connect",
);
if (saveRequest.data && saveRequest.data.success === true) {
/** If all is good refresh the page here */
window.location = "/dashboard";
return;
}
throw new Error(saveRequest.data.message);
} catch (error) {
console.log(error);
showSnackbarError(normalizeError(error));
}
}
},
},
};
</script>
<style module lang="less" src="./connect.less"></style>
Current version...
|
47855
|
|
47792
|
NULL
|
0
|
2026-04-17T12:00:54.529103+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776427254529_m2.jpg...
|
NULL
|
NULL
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileFditViewNavigateCodelaravelRefactonToo PhpStormFileFditViewNavigateCodelaravelRefactonToolsWindowHelnFV faVsco.s vT° JY-20692-fix-integration-app-[API_KEY] vphp.php-cs-fixer.dist.phpphp.phpstorm.meta.php=.phpunit.result.cacheE.prettierignoreE windsurfrulespip _lde_nelper.pnppnpce neloermoces.onepnp aruisani composer.isonI composer.locku dependency-checker.jsonu dev.json= ids.txt=infection.json.distM-INSALL.mdM+ INTERNAL WEBHOOK SETUPliminny storageM+ licenses.mdM MakefileU package-lock.ison= phostan.neon.dist= phpstan-baseline.neon<» phpunit.xmlTaraw_sqL_query.salM.README.moles sonar-project.properties= test.py<> Untitled Diagram.xmlus vetur.contig.jsM+ WEbnOOK HILICKING_IMPLE› Ih External Librariesv = Scratches and Consolesv _ Database Consoles¿ console EUlL DEAL RISKS EUIdDIlEUdEuleUvd jiminny@localhost& console yiminny @localr4 DI liiminnv@localhosti4 HS local [iiminny@local4 SF Timinny@localhostl¿ zoho dev fiminny@locaV & PRODC AutomatedReportsService.phpC) SendReportJob.phpC SendReportMailJob.php(C) ReportController.phpG TokenBuilder.phpC TeamSetupController.phppnp apl.onp© Filesystem.phpc leam.onoC CreateHeldActivityEvent.phpC TrackProviderInstalledEvent.php© RequestGenerateReportJob.phpwOpportunitySynctrait.php© Opportunity.php(T) InteractsWithPivotTable.ohr© OpportunityUpdated.phpC OpportunityStageUpdated.phpC OpportunityPendingAiAnalysisAfterStageChanged.phpC RunOpportunityAiAnalysis.phpC ProcessAiAutomationAnalysisResults.php© ImportOpportunityBatch.phpTImportBatchJobTrait.phpC Service.phpcachedStagesCc W.*2141TITtrait UpportunitysynctraltA33 V2 V19 ^private function resolverorecastcategory(?string sforecastcategory): stringi...я10282 usagesprivate runccion imporcexcernaurleldbacalarray spropercles, Int фopportunityla: vold1050norireros = vuns-roe.uoooruncvovncaouerersrSthis->inportOpportunitvCrmFieldDataSproperties. ScrmFields. SopportunityId:10551034Lusaees1035104810491050105111054private function importopportunityContacts(Opportunity Sopportunity, array Sassociations): voidi...h*** Sync opportunity contacts using differentzal approach* Inis compares current vs new associacions and only makes necessary changes*/1 usageprivate function sync0pportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): voidf...10/01 usageД0/11077private function getcurrentContactCrmlds(Opportunity $opportunity): arrayi...lusageJ0/C107910861081private function logcontactAssoclationchangesOpportunity sopportunity,array scurrentconcacucrmias,array scontaccassoclatlons,array scontacustoAddarray ncontacrs orellove0: void {...}lusaee1074private function removeContactAssociations(Opportunity Sopportunity, array ScontactsToRemove): void....rTusageprivate function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): vservices+,o,cv _ Databasevdtuc console 1s 59 msu crm_contigurations 1s 391 msv & minnvo ocanostA SF4 HS localV & PRODs console Is ovo msV & STAGINGd console(aJ OutputiD # 16952 rows y6G9odv181RLAL0AqwHvCfOD4i0kNaHLfsvzifiGrPwZVo0SCPiax.Tx: Autov7OшA9I expires Y1776430563I refresh token expires Yprovider Yu coodle2 ClsImtpzC16l7VmNwFhZGrkLTowzDKtNDLKNy04ZWL2LTAZNmNLM...129615615<nu> z0om-nhone1776513214<null> integration-appsnoricuis contlicis: Clone caret Above and 1 more shottings. #/ Modiry shortcuts // Don't show Again (today 12:22158101162163=164165-166=16816%-171173174-1751761781811184184100186—187188189"196—192-193= custom.log= laravel.logd SF (iminny@localhost Xu scratch_1.jsoncrmenutykeposilorv.ono(iii crm configurations [EUlL console (PROD1p oIx. AUto vMlaycroundvoEL*-KUIMautomaced_report_resulus where 10 = 215;oELE * FKUMSELECT * FROMselect * fromautomaced_reports order dy za desc;automated_report_results order by id desc;activity searches where user id = 143:select * fromask anyunine prompcs.SELECT * FROM groups WHERE id = 14391SELECT * FROM users WHERE group_id = 1439:select * from permissions; # 158select * from roles;seLect * rrom permission_roleselect * tronreans where 10 =1select x t'oIgroups g JOIN playbooks p 1..n<->1: on g.playbook_id = p.id where g.team_id = 1;select x TOIaroups where id = 28select * fronplaybooks where team_ id = 1:select * fromplaybooks where id = 179:select * from playbook categories where id = 1391:select * fromusers where 1d = 145;select * from crm_profiles where user_1d = 143:select * from activities where crm_configuration_1d = 39 and type = 'conference"and crm_provider_1d Is NUI NULL URDER Dy 10 desc;select * from activities where id = 422003; # 00U0400000pB6FpMACSELECT ar.id, ar.vuid, ar.media type, ar.status, a.typeFROM automated report results anJOIN automated_reports a ON a.id = ar.report idWHERE a.type = 'ask-liminnxLIMIT 10:select * from teams where id = 3143;select * from crm_configurations where 1d = 500;seLect * rrom users where name ="Integracion Account; # 1675SELECT * FROM social_accounts WHERE sociable_id = 1695;Support Daily • now100% zFri 17 Apr 15:00:54AutomatedReportsCommandTestvconnect.vueOnboara.vuec HS_local fiminny@localhost¿ console (EU]console SlAGiNGliminny09 412 X2 X4 ^state Yconnectedfull-refreshfull-refresh• :-esVyL→08I auth_scope YI retry after YI cresnttps://www.googleap1s.com/auch/user1nro.ema1l nutps://www.googleap1s.com/auch/user1nto.pror1le nuups.ShULD2020-0phone:read:admin user:read:admin<nul>2024-1SHULLe<null>2026-04SUM: 0 3:9 W Windsurf Teams 192:18 0 UTF-84 spaces...
|
NULL
|
-5843023489289675064
|
NULL
|
click
|
ocr
|
NULL
|
PhpStormFileFditViewNavigateCodelaravelRefactonToo PhpStormFileFditViewNavigateCodelaravelRefactonToolsWindowHelnFV faVsco.s vT° JY-20692-fix-integration-app-[API_KEY] vphp.php-cs-fixer.dist.phpphp.phpstorm.meta.php=.phpunit.result.cacheE.prettierignoreE windsurfrulespip _lde_nelper.pnppnpce neloermoces.onepnp aruisani composer.isonI composer.locku dependency-checker.jsonu dev.json= ids.txt=infection.json.distM-INSALL.mdM+ INTERNAL WEBHOOK SETUPliminny storageM+ licenses.mdM MakefileU package-lock.ison= phostan.neon.dist= phpstan-baseline.neon<» phpunit.xmlTaraw_sqL_query.salM.README.moles sonar-project.properties= test.py<> Untitled Diagram.xmlus vetur.contig.jsM+ WEbnOOK HILICKING_IMPLE› Ih External Librariesv = Scratches and Consolesv _ Database Consoles¿ console EUlL DEAL RISKS EUIdDIlEUdEuleUvd jiminny@localhost& console yiminny @localr4 DI liiminnv@localhosti4 HS local [iiminny@local4 SF Timinny@localhostl¿ zoho dev fiminny@locaV & PRODC AutomatedReportsService.phpC) SendReportJob.phpC SendReportMailJob.php(C) ReportController.phpG TokenBuilder.phpC TeamSetupController.phppnp apl.onp© Filesystem.phpc leam.onoC CreateHeldActivityEvent.phpC TrackProviderInstalledEvent.php© RequestGenerateReportJob.phpwOpportunitySynctrait.php© Opportunity.php(T) InteractsWithPivotTable.ohr© OpportunityUpdated.phpC OpportunityStageUpdated.phpC OpportunityPendingAiAnalysisAfterStageChanged.phpC RunOpportunityAiAnalysis.phpC ProcessAiAutomationAnalysisResults.php© ImportOpportunityBatch.phpTImportBatchJobTrait.phpC Service.phpcachedStagesCc W.*2141TITtrait UpportunitysynctraltA33 V2 V19 ^private function resolverorecastcategory(?string sforecastcategory): stringi...я10282 usagesprivate runccion imporcexcernaurleldbacalarray spropercles, Int фopportunityla: vold1050norireros = vuns-roe.uoooruncvovncaouerersrSthis->inportOpportunitvCrmFieldDataSproperties. ScrmFields. SopportunityId:10551034Lusaees1035104810491050105111054private function importopportunityContacts(Opportunity Sopportunity, array Sassociations): voidi...h*** Sync opportunity contacts using differentzal approach* Inis compares current vs new associacions and only makes necessary changes*/1 usageprivate function sync0pportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): voidf...10/01 usageД0/11077private function getcurrentContactCrmlds(Opportunity $opportunity): arrayi...lusageJ0/C107910861081private function logcontactAssoclationchangesOpportunity sopportunity,array scurrentconcacucrmias,array scontaccassoclatlons,array scontacustoAddarray ncontacrs orellove0: void {...}lusaee1074private function removeContactAssociations(Opportunity Sopportunity, array ScontactsToRemove): void....rTusageprivate function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): vservices+,o,cv _ Databasevdtuc console 1s 59 msu crm_contigurations 1s 391 msv & minnvo ocanostA SF4 HS localV & PRODs console Is ovo msV & STAGINGd console(aJ OutputiD # 16952 rows y6G9odv181RLAL0AqwHvCfOD4i0kNaHLfsvzifiGrPwZVo0SCPiax.Tx: Autov7OшA9I expires Y1776430563I refresh token expires Yprovider Yu coodle2 ClsImtpzC16l7VmNwFhZGrkLTowzDKtNDLKNy04ZWL2LTAZNmNLM...129615615<nu> z0om-nhone1776513214<null> integration-appsnoricuis contlicis: Clone caret Above and 1 more shottings. #/ Modiry shortcuts // Don't show Again (today 12:22158101162163=164165-166=16816%-171173174-1751761781811184184100186—187188189"196—192-193= custom.log= laravel.logd SF (iminny@localhost Xu scratch_1.jsoncrmenutykeposilorv.ono(iii crm configurations [EUlL console (PROD1p oIx. AUto vMlaycroundvoEL*-KUIMautomaced_report_resulus where 10 = 215;oELE * FKUMSELECT * FROMselect * fromautomaced_reports order dy za desc;automated_report_results order by id desc;activity searches where user id = 143:select * fromask anyunine prompcs.SELECT * FROM groups WHERE id = 14391SELECT * FROM users WHERE group_id = 1439:select * from permissions; # 158select * from roles;seLect * rrom permission_roleselect * tronreans where 10 =1select x t'oIgroups g JOIN playbooks p 1..n<->1: on g.playbook_id = p.id where g.team_id = 1;select x TOIaroups where id = 28select * fronplaybooks where team_ id = 1:select * fromplaybooks where id = 179:select * from playbook categories where id = 1391:select * fromusers where 1d = 145;select * from crm_profiles where user_1d = 143:select * from activities where crm_configuration_1d = 39 and type = 'conference"and crm_provider_1d Is NUI NULL URDER Dy 10 desc;select * from activities where id = 422003; # 00U0400000pB6FpMACSELECT ar.id, ar.vuid, ar.media type, ar.status, a.typeFROM automated report results anJOIN automated_reports a ON a.id = ar.report idWHERE a.type = 'ask-liminnxLIMIT 10:select * from teams where id = 3143;select * from crm_configurations where 1d = 500;seLect * rrom users where name ="Integracion Account; # 1675SELECT * FROM social_accounts WHERE sociable_id = 1695;Support Daily • now100% zFri 17 Apr 15:00:54AutomatedReportsCommandTestvconnect.vueOnboara.vuec HS_local fiminny@localhost¿ console (EU]console SlAGiNGliminny09 412 X2 X4 ^state Yconnectedfull-refreshfull-refresh• :-esVyL→08I auth_scope YI retry after YI cresnttps://www.googleap1s.com/auch/user1nro.ema1l nutps://www.googleap1s.com/auch/user1nto.pror1le nuups.ShULD2020-0phone:read:admin user:read:admin<nul>2024-1SHULLe<null>2026-04SUM: 0 3:9 W Windsurf Teams 192:18 0 UTF-84 spaces...
|
47790
|
|
47791
|
NULL
|
0
|
2026-04-17T12:00:54.494976+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776427254494_m1.jpg...
|
NULL
|
NULL
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
iTerm2ShellEditViewSessionScriptsProfilesWindowHel iTerm2ShellEditViewSessionScriptsProfilesWindowHelpSupport Daily - now100% <28Fri 17 Apr 15:00:54DEV (docker)84DOCKER- 881DEV (docker)APP (-zsh)• *3-zshconfigcachecompiledeventsroutesviewsjiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00:stoppedjiminny-worker-processing-2:jiminny-worker-processing-2_00: stoppedjiminny-worker-processing-3:jiminny-worker-processing-3_00:stoppedjiminny-worker-processing-4:jiminny-worker-processing-4_00:stoppedjiminny-worker-processing-5:jiminny-worker-processing-5_00: stoppedworker-analytics:worker-analytics_00: stoppedworker-crm-update:worker-crm-update_00: stoppedworker-download:worker-download_00: stoppedworker-nudges:worker-nudges_00: stoppedworker:worker_00: stoppedworker-audio:worker-audio_00: stoppedworker-calendar:worker-calendar_00: stoppedworker-emails:worker-emails_00: stoppedjiminny-worker-processing-1:jiminny-worker-processing-1_00: stoppedworker-conferences:worker-conferences_00: stoppedworker-crm-sync:worker-crm-sync_00: stoppedworker-es-update:worker-es-update_00: stoppedartisan-schedule:artisan-schedule_00: stoppedartisan-schedule:artisan-schedule_00: startedjiminny-worker-processing-1:jiminny-worker-processing-1_00: startedjiminny-worker-processing-2:jiminny-worker-processing-2_00: startedjiminny-worker-processing-3:jiminny-worker-processing-3_00: startedjiminny-worker-processing-4:jiminny-worker-processing-4_00: startedjiminny-worker-processing-5:jiminny-worker-processing-5_00: startedjiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00:startedworker:worker_00: startedworker-analytics:worker-analytics_00: startedworker-audio:worker-audio_00: startedworker-calendar:worker-calendar_00: startedworker-conferences:worker-conferences_00: startedworker-crm-sync:worker-crm-sync_00: startedworker-crm-update:worker-crm-update_00:startedworker-download:worker-download_00: startedworker-emails:worker-emails_00: startedworker-es-update:worker-es-update_00: startedworker-nudges:worker-nudges_00: startedroot@docker_lamp_1:/home/jiminny#-zsh•₴85* Review :Support Dailynow - 15:00-15:15= Notes - Support Daily.5.81ms DONE4.39ms DONE4.85ms DONE26.89ms DONEC Join Google MeetDEV...
|
NULL
|
-9018126091195121953
|
NULL
|
click
|
ocr
|
NULL
|
iTerm2ShellEditViewSessionScriptsProfilesWindowHel iTerm2ShellEditViewSessionScriptsProfilesWindowHelpSupport Daily - now100% <28Fri 17 Apr 15:00:54DEV (docker)84DOCKER- 881DEV (docker)APP (-zsh)• *3-zshconfigcachecompiledeventsroutesviewsjiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00:stoppedjiminny-worker-processing-2:jiminny-worker-processing-2_00: stoppedjiminny-worker-processing-3:jiminny-worker-processing-3_00:stoppedjiminny-worker-processing-4:jiminny-worker-processing-4_00:stoppedjiminny-worker-processing-5:jiminny-worker-processing-5_00: stoppedworker-analytics:worker-analytics_00: stoppedworker-crm-update:worker-crm-update_00: stoppedworker-download:worker-download_00: stoppedworker-nudges:worker-nudges_00: stoppedworker:worker_00: stoppedworker-audio:worker-audio_00: stoppedworker-calendar:worker-calendar_00: stoppedworker-emails:worker-emails_00: stoppedjiminny-worker-processing-1:jiminny-worker-processing-1_00: stoppedworker-conferences:worker-conferences_00: stoppedworker-crm-sync:worker-crm-sync_00: stoppedworker-es-update:worker-es-update_00: stoppedartisan-schedule:artisan-schedule_00: stoppedartisan-schedule:artisan-schedule_00: startedjiminny-worker-processing-1:jiminny-worker-processing-1_00: startedjiminny-worker-processing-2:jiminny-worker-processing-2_00: startedjiminny-worker-processing-3:jiminny-worker-processing-3_00: startedjiminny-worker-processing-4:jiminny-worker-processing-4_00: startedjiminny-worker-processing-5:jiminny-worker-processing-5_00: startedjiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00:startedworker:worker_00: startedworker-analytics:worker-analytics_00: startedworker-audio:worker-audio_00: startedworker-calendar:worker-calendar_00: startedworker-conferences:worker-conferences_00: startedworker-crm-sync:worker-crm-sync_00: startedworker-crm-update:worker-crm-update_00:startedworker-download:worker-download_00: startedworker-emails:worker-emails_00: startedworker-es-update:worker-es-update_00: startedworker-nudges:worker-nudges_00: startedroot@docker_lamp_1:/home/jiminny#-zsh•₴85* Review :Support Dailynow - 15:00-15:15= Notes - Support Daily.5.81ms DONE4.39ms DONE4.85ms DONE26.89ms DONEC Join Google MeetDEV...
|
47789
|
|
47701
|
NULL
|
0
|
2026-04-17T11:56:02.834541+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776426962834_m2.jpg...
|
NULL
|
NULL
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
FirefoxFileEoitViewHistoryBookmarksProfilesToolsWi FirefoxFileEoitViewHistoryBookmarksProfilesToolsWindow Help== accounts.google.com/signin/oauth/consent?as=S1242662876%3A1776426949273943&authuser=1&client_id=827025697740-iohrve9ot2mkt6fj56a44r4qo97m55de.apps.googleusercontent.com&flowName=GeneralOAuthFlow&part=AJi8hAPDgj1rf2K-HUymRj07wyv8Zb-5zpQxjYgTcInspector• ConsoleD DebuggerT Network {} Style EditorPerformance: MemoryStorageY Integij Support Daily • in 4mi Accessibility88: Application423 hidden XA100% CS•Fri 17 Apr 14:56:02Errors Warnings Info Logs DebugCSS XHR Requests *Developers | HubSpotM'inbox (1,576) - lukas.kovalik@jiminM 120216 is your HubSpot Log In ColCa CloudWatch | eu-west-1New TabZ Configure SSH access to multiple. fix-cache-for-business-processes4 [JY-20692] Issue with reconnectirSign in - Google accountsAuth ProxyDashooard : Jiminny • vemoraneApo "4oho CkM" • Jiminny • Memc+ New Tab301YF149hA0QpbuNnqx53gphOjFgeL6wDQfqvLBTSMVL5X0Poh9kN1hYXWjDjc4SIhSv1UapKMZD_0vzL1Nd-LNti0EdLWhNAP_zI2qnsdxtxt8pX_40_10I0-FtbrbCD2T2Lachycv6X sACyEw-DgsweVzFa2voxEmYA34176428-SatoWVfOoyDdMJUKtK20Ugr7_ redbOGrapt=AEJ HL403w:5H/2DnYSTđpTUVL* Navigated to https://app.dev.jiminny.com/auth/callback/google?stateeyanttas e5o1032k221mgu2i6k2o0tsT2C 14-90gTCmOe/YAC5 BN0T bGликnV: o0р ubCPebhPCy9302RрJCA22₽-ro-gNCb/u2mayT120pr12cy20pnas7201aHR:HM6XC9cL2Fvwww.googleapis.com/auth/userinfo.email%20https://www.googleapis.com/auth/userinfo.profile%20https://www.googleapis.com/auth/calendar%20https://www.googleapis.com/auth/gillatt. reddon tyoducnuser-tand-cnenny. comeprompL-conseneG Sign in with Googlejiminny.com wants toaccess your [EMAIL] will allow jiminny.com to:M View your email messages and settingsSee, edit, share, and permanently delete all thecalendars you can access using Google CalendarMake sure you trust jiminny.comReview jiminny.com's Privacy Policy and Terms of Service tounderstand how iminny.com will process and protect your data.To make changes at any time, go to your Google Account.eain now coocle nellos vou snare cata sarev.CanceAllow hEnglish (United States)HelpPrivacyTerms203...
|
NULL
|
9109564888349226639
|
NULL
|
visual_change
|
ocr
|
NULL
|
FirefoxFileEoitViewHistoryBookmarksProfilesToolsWi FirefoxFileEoitViewHistoryBookmarksProfilesToolsWindow Help== accounts.google.com/signin/oauth/consent?as=S1242662876%3A1776426949273943&authuser=1&client_id=827025697740-iohrve9ot2mkt6fj56a44r4qo97m55de.apps.googleusercontent.com&flowName=GeneralOAuthFlow&part=AJi8hAPDgj1rf2K-HUymRj07wyv8Zb-5zpQxjYgTcInspector• ConsoleD DebuggerT Network {} Style EditorPerformance: MemoryStorageY Integij Support Daily • in 4mi Accessibility88: Application423 hidden XA100% CS•Fri 17 Apr 14:56:02Errors Warnings Info Logs DebugCSS XHR Requests *Developers | HubSpotM'inbox (1,576) - lukas.kovalik@jiminM 120216 is your HubSpot Log In ColCa CloudWatch | eu-west-1New TabZ Configure SSH access to multiple. fix-cache-for-business-processes4 [JY-20692] Issue with reconnectirSign in - Google accountsAuth ProxyDashooard : Jiminny • vemoraneApo "4oho CkM" • Jiminny • Memc+ New Tab301YF149hA0QpbuNnqx53gphOjFgeL6wDQfqvLBTSMVL5X0Poh9kN1hYXWjDjc4SIhSv1UapKMZD_0vzL1Nd-LNti0EdLWhNAP_zI2qnsdxtxt8pX_40_10I0-FtbrbCD2T2Lachycv6X sACyEw-DgsweVzFa2voxEmYA34176428-SatoWVfOoyDdMJUKtK20Ugr7_ redbOGrapt=AEJ HL403w:5H/2DnYSTđpTUVL* Navigated to https://app.dev.jiminny.com/auth/callback/google?stateeyanttas e5o1032k221mgu2i6k2o0tsT2C 14-90gTCmOe/YAC5 BN0T bGликnV: o0р ubCPebhPCy9302RрJCA22₽-ro-gNCb/u2mayT120pr12cy20pnas7201aHR:HM6XC9cL2Fvwww.googleapis.com/auth/userinfo.email%20https://www.googleapis.com/auth/userinfo.profile%20https://www.googleapis.com/auth/calendar%20https://www.googleapis.com/auth/gillatt. reddon tyoducnuser-tand-cnenny. comeprompL-conseneG Sign in with Googlejiminny.com wants toaccess your [EMAIL] will allow jiminny.com to:M View your email messages and settingsSee, edit, share, and permanently delete all thecalendars you can access using Google CalendarMake sure you trust jiminny.comReview jiminny.com's Privacy Policy and Terms of Service tounderstand how iminny.com will process and protect your data.To make changes at any time, go to your Google Account.eain now coocle nellos vou snare cata sarev.CanceAllow hEnglish (United States)HelpPrivacyTerms203...
|
NULL
|
|
47700
|
NULL
|
0
|
2026-04-17T11:56:00.945424+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776426960945_m1.jpg...
|
NULL
|
NULL
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
FirefoxFileEditViewHistoryBookmarksProfilesToolsWi FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelplahl• Support Daily - in 5 mAPP (-zsh)X4-zsh100% <47• ₴78Fri 17 Apr 14:56:001₴81ec2-user@ip-10-...• 88DOCKER881DEV (docker)• *2APP (-zsh)X3-zsh./public/vue-assets/assets/ondemand-DutwAJ6x.js:./public/vue-assets/assets/CrmLink-DKYsnHnx.js./public/vue-assets/assets/liquor-tree-COUefof4.js./public/vue-assets/assets/DealRiskList-CjPoT6NF.js:./public/vue-assets/assets/AskAnything-a4BpUaF5.js:./public/vue-assets/assets/lib-CwM9toD2.js./public/vue-assets/assets/AppFormField-CtfldUq7.js./public/vue-assets/assets/deal-view-DhouIWLw.js:./public/vue-assets/assets/exports-D1lmea40.js../public/vue-assets/assets/playlists-BWns-By9.js../public/vue-assets/assets/callScoringTemplates-zeRn40ul.js../public/vue-assets/assets/_copy0bject-USkOnlaQ.js../public/vue-assets/assets/pusher-znYCfz7U.js./public/vue-assets/assets/onboard-BENebgvU.js./public/vue-assets/assets/StatusBadge-CmcY3nfX.js./public/vue-assets/assets/kiosk-1vhk4_89.js./public/vue-assets/assets/preload-helper-DCvhahzG.js../public/vue-assets/assets/deal-insights-DN-kVyxK.js../public/vue-assets/assets/ListView-POU6dSu7.js:./public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js./public/vue-assets/assets/WelcomeLayout-B6wd32HG.js../public/vue-assets/assets/dashboard-DYH5MiIH.js:./public/vue-assets/assets/emoji-input-CSq87OVy.js../public/vue-assets/assets/AppButton-D3qMd0Dr.js../public/vue-assets/assets/sentry-c0Rhilsu.js:./public/vue-assets/assets/OrgSettingsLayout-ByQjX4wG.js./public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js./public/vue-assets/assets/playback-BH9MB7YC.js./public/vue-assets/assets/index.module-Bjlhgfdl.js./public/vue-assets/assets/intl-tel-input-BW4mv40Q.js./public/vue-assets/assets/team-insights-BqCZQtVc.js../public/vue-assets/assets/popper-CQwVcrX4.js../public/vue-assets/assets/PhoneField-CwCIoAYm.js./public/vue-assets/assets/live-XL9ZmWsU.js./public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BN0485xV.js../public/vue-assets/assets/index-CPycMtZF.js../public/vue-assets/assets/logged-in-layout-B_JDhk5y.js• ₴526.88kB27.91kB30.75kB34.39kB39.50kB39.69kB41.91kB43.22kB47.84kB48.28kB55.13kB61.28kB62.98kB63.26kB64.66kB79.60kB82.59kB94.84kB115.71kB117.59 kВ120.67kB128.71 kB129.28 kB133.44 kB164.28kB176.33kB180.40kB198.79kB218.14kB264.94 kВ298.57kB307.13kB343.99kB367.43 kB689.63kB825.23 kB1,402.70 kB• built in 28.48s[plugin builtin:vite-reporter](!) Some chunksare larger than 500 kB after minification. Consider:- Using dynamic import() to code-split the application- Use build.rolldown0ptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/Output0ptions.codeSplitting- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-[API_KEY]) $U* Review screenp...gzip:9.38kBgz1p:10.18kBgzip:9.58kB9z1p:10.62kB9z1p:14.97kBgzip:12.70kBgzip:12.69kBgzip:14.34kBgz1p:16.46kBgzip:15.07kBgz1p:13.28kBgzip:20.09kBgzip:18.88kB9z1p:21.90kB921p:22.96kBgzip:22.64kBgzip:27.46kBgzip:gzip:28.16kB33.78kBgzip:38.71 kBgz1p:34.16 kBgzip:40.05kBgzip:36.72 kBgzip:43.03 kB9z1p:52.24kB9z1p:56.15kBgzip:67.85kBgzip:61.85kBgz1p:64.16kBgzip:60.31kB9z1p:77.21kBgz1p:103.86kBgzip:84.90kB9z1p:97.05kBgzip: 202.81kBgzip:72.53-kBgzip: 438.08 kB• ₴[EMAIL]:73.94kBmap:93.18kBmap:78.74kBmap:115.18kBmap:173.20 kBmap:138.34kBmap:150.73kBmap:150.62kBmap:294.48kBmap:153.25kBmap:65..85kBmap:239.59kBmap:219.27kBmap:201.62kBmap:244.72kBmap:300.68kBmap:3,452.35kBmap:292.79kBmap:308.10kBmap:500.60 kBmap:258.56kBmap:410.48kBmap:266.15kBmap:516.67kBmap:831..82kBmap:623.43kBmap:836.88kBmap:684.87kBmap:1,108.20kBmap:475.61kBmap:959.96kBmap:1,245.28kBmap:849.05kBmap:792.41kBmap:3,016.64kBmap:436.62kBmaр: 6,283.55kBAPP...
|
NULL
|
167349951759858119
|
NULL
|
click
|
ocr
|
NULL
|
FirefoxFileEditViewHistoryBookmarksProfilesToolsWi FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelplahl• Support Daily - in 5 mAPP (-zsh)X4-zsh100% <47• ₴78Fri 17 Apr 14:56:001₴81ec2-user@ip-10-...• 88DOCKER881DEV (docker)• *2APP (-zsh)X3-zsh./public/vue-assets/assets/ondemand-DutwAJ6x.js:./public/vue-assets/assets/CrmLink-DKYsnHnx.js./public/vue-assets/assets/liquor-tree-COUefof4.js./public/vue-assets/assets/DealRiskList-CjPoT6NF.js:./public/vue-assets/assets/AskAnything-a4BpUaF5.js:./public/vue-assets/assets/lib-CwM9toD2.js./public/vue-assets/assets/AppFormField-CtfldUq7.js./public/vue-assets/assets/deal-view-DhouIWLw.js:./public/vue-assets/assets/exports-D1lmea40.js../public/vue-assets/assets/playlists-BWns-By9.js../public/vue-assets/assets/callScoringTemplates-zeRn40ul.js../public/vue-assets/assets/_copy0bject-USkOnlaQ.js../public/vue-assets/assets/pusher-znYCfz7U.js./public/vue-assets/assets/onboard-BENebgvU.js./public/vue-assets/assets/StatusBadge-CmcY3nfX.js./public/vue-assets/assets/kiosk-1vhk4_89.js./public/vue-assets/assets/preload-helper-DCvhahzG.js../public/vue-assets/assets/deal-insights-DN-kVyxK.js../public/vue-assets/assets/ListView-POU6dSu7.js:./public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js./public/vue-assets/assets/WelcomeLayout-B6wd32HG.js../public/vue-assets/assets/dashboard-DYH5MiIH.js:./public/vue-assets/assets/emoji-input-CSq87OVy.js../public/vue-assets/assets/AppButton-D3qMd0Dr.js../public/vue-assets/assets/sentry-c0Rhilsu.js:./public/vue-assets/assets/OrgSettingsLayout-ByQjX4wG.js./public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js./public/vue-assets/assets/playback-BH9MB7YC.js./public/vue-assets/assets/index.module-Bjlhgfdl.js./public/vue-assets/assets/intl-tel-input-BW4mv40Q.js./public/vue-assets/assets/team-insights-BqCZQtVc.js../public/vue-assets/assets/popper-CQwVcrX4.js../public/vue-assets/assets/PhoneField-CwCIoAYm.js./public/vue-assets/assets/live-XL9ZmWsU.js./public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BN0485xV.js../public/vue-assets/assets/index-CPycMtZF.js../public/vue-assets/assets/logged-in-layout-B_JDhk5y.js• ₴526.88kB27.91kB30.75kB34.39kB39.50kB39.69kB41.91kB43.22kB47.84kB48.28kB55.13kB61.28kB62.98kB63.26kB64.66kB79.60kB82.59kB94.84kB115.71kB117.59 kВ120.67kB128.71 kB129.28 kB133.44 kB164.28kB176.33kB180.40kB198.79kB218.14kB264.94 kВ298.57kB307.13kB343.99kB367.43 kB689.63kB825.23 kB1,402.70 kB• built in 28.48s[plugin builtin:vite-reporter](!) Some chunksare larger than 500 kB after minification. Consider:- Using dynamic import() to code-split the application- Use build.rolldown0ptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/Output0ptions.codeSplitting- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-[API_KEY]) $U* Review screenp...gzip:9.38kBgz1p:10.18kBgzip:9.58kB9z1p:10.62kB9z1p:14.97kBgzip:12.70kBgzip:12.69kBgzip:14.34kBgz1p:16.46kBgzip:15.07kBgz1p:13.28kBgzip:20.09kBgzip:18.88kB9z1p:21.90kB921p:22.96kBgzip:22.64kBgzip:27.46kBgzip:gzip:28.16kB33.78kBgzip:38.71 kBgz1p:34.16 kBgzip:40.05kBgzip:36.72 kBgzip:43.03 kB9z1p:52.24kB9z1p:56.15kBgzip:67.85kBgzip:61.85kBgz1p:64.16kBgzip:60.31kB9z1p:77.21kBgz1p:103.86kBgzip:84.90kB9z1p:97.05kBgzip: 202.81kBgzip:72.53-kBgzip: 438.08 kB• ₴[EMAIL]:73.94kBmap:93.18kBmap:78.74kBmap:115.18kBmap:173.20 kBmap:138.34kBmap:150.73kBmap:150.62kBmap:294.48kBmap:153.25kBmap:65..85kBmap:239.59kBmap:219.27kBmap:201.62kBmap:244.72kBmap:300.68kBmap:3,452.35kBmap:292.79kBmap:308.10kBmap:500.60 kBmap:258.56kBmap:410.48kBmap:266.15kBmap:516.67kBmap:831..82kBmap:623.43kBmap:836.88kBmap:684.87kBmap:1,108.20kBmap:475.61kBmap:959.96kBmap:1,245.28kBmap:849.05kBmap:792.41kBmap:3,016.64kBmap:436.62kBmaр: 6,283.55kBAPP...
|
47696
|
|
47604
|
NULL
|
0
|
2026-04-17T11:50:51.291403+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776426651291_m2.jpg...
|
Slack
|
jiminny-x-integration-app (Channel) - Jiminny Inc jiminny-x-integration-app (Channel) - Jiminny Inc - Slack...
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
Directories
jiminny-x-integration-app
platform-inner-team
ai-chapter
alerts
backend
confusion-clinic
curiosity_lab
engineering
frontend
general
infra-changes
jiminny-bg
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Nikolay Nikolov
Aneliya Angelova
,
Nikolay Yankov
,
Steliyan Georgiev
Galya Dimitrova
Stoyan Tanev
Vasil Vasilev
Nikolay Ivanov
Aneliya Angelova
Ves
Steliyan Georgiev
Jira Cloud
Toast
Google Calendar
Messages
Messages
More
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
Membrane
APP
Nov 11th, 2025 at 12:38:55 AM
12:38 AM
Heads up – we’re moving to a new domain and legal name!
Heads up – we’re moving to a new domain and legal name!
In the coming days and weeks, we’ll be transitioning from
integration.app
integration.app
domain to
getmembrane.com
getmembrane.com
. You’ll start seeing our website, docs, and console automatically redirect to the new domain.
No action is needed on your side — all existing APIs and SDKs will continue working as usual.
Additionally, we have changed our legal name to Membrane Inc. It will be used in all the paperwork going forward.
We’ll share the official launch announcement in the next couple of weeks.
1 reaction, react with +1 emoji
1
Add reaction…
Jump to date
Membrane
APP
Dec 15th, 2025 at 7:29:37 PM
7:29 PM
Exclusive access
We’ve been working on a new capability at
Membrane
called
self-integration.
Instead of relying on pre-built integrations, your AI agent can now build integrations itself, on the fly, to any app.
We’re partnering with a small group of teams to pilot this, including our customers. If it sounds relevant to what you’re building, I’d love to include you.
For now, this is a closed experience as we want to refine the end-to-end flow with close partners as part of our
Founding Cohort for Self-Integrations
.
Read more here:
https://self-integration.getmembrane.com
https://self-integration.getmembrane.com
. Check out
self-integration manifesto
once in, if you’re interested in the vision and why we are working on this.
Reply or react a
if you’re interested
— we’re happy to walk you through it over a very short call.
Jump to date
Lukas Kovalik
Yesterday at 11:04:11 AM
11:04 AM
Hi guys, we have one issue we used to have before regarding the authorisation for Zoho CRM. When the clients go through all steps and login it just returns him back to the login screen. I believe this is the reference to the previous conversation
https://jiminny.slack.com/archives/C07RAC4U86M/p1748957897141919
https://jiminny.slack.com/archives/C07RAC4U86M/p1748957897141919
. Could you please have a look if there is any change?...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Jiminny Inc","depth":12,"bounds":{"left":0.00546875,"top":0.05486111,"width":0.0125,"height":0.022222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXRadioButton","text":"Jiminny (Staging)","depth":12,"bounds":{"left":0.00546875,"top":0.09097222,"width":0.0125,"height":0.022222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Add workspaces","depth":12,"bounds":{"left":0.00546875,"top":0.12708333,"width":0.0125,"height":0.022222223},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":14,"bounds":{"left":0.026953125,"top":0.048611112,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":16,"bounds":{"left":0.03125,"top":0.08125,"width":0.012109375,"height":0.009027778},"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":14,"bounds":{"left":0.026953125,"top":0.09583333,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":16,"bounds":{"left":0.032421876,"top":0.12847222,"width":0.009765625,"height":0.009027778},"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":14,"bounds":{"left":0.026953125,"top":0.14305556,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":16,"bounds":{"left":0.0296875,"top":0.17569445,"width":0.015234375,"height":0.009027778},"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":14,"bounds":{"left":0.026953125,"top":0.19027779,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":16,"bounds":{"left":0.0328125,"top":0.22291666,"width":0.008984375,"height":0.009027778},"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":14,"bounds":{"left":0.026953125,"top":0.2375,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":16,"bounds":{"left":0.03203125,"top":0.2701389,"width":0.010546875,"height":0.009027778},"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":14,"bounds":{"left":0.026953125,"top":0.2847222,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":16,"bounds":{"left":0.03203125,"top":0.31736112,"width":0.010546875,"height":0.009027778},"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":20,"bounds":{"left":0.06679688,"top":0.0875,"width":0.021484375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":20,"bounds":{"left":0.06679688,"top":0.10694444,"width":0.020703126,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":20,"bounds":{"left":0.06679688,"top":0.12638889,"width":0.021484375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":20,"bounds":{"left":0.06679688,"top":0.14583333,"width":0.034375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":20,"bounds":{"left":0.06679688,"top":0.16527778,"width":0.028515626,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"jiminny-x-integration-app","depth":20,"bounds":{"left":0.07304688,"top":0.24722221,"width":0.0515625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":20,"bounds":{"left":0.07304688,"top":0.26666668,"width":0.05234375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":20,"bounds":{"left":0.07304688,"top":0.3125,"width":0.026171874,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":20,"bounds":{"left":0.07304688,"top":0.33194444,"width":0.014453125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":20,"bounds":{"left":0.07304688,"top":0.3513889,"width":0.021484375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":20,"bounds":{"left":0.07304688,"top":0.37083334,"width":0.040625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"curiosity_lab","depth":20,"bounds":{"left":0.07304688,"top":0.39027777,"width":0.032421876,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":20,"bounds":{"left":0.07304688,"top":0.4097222,"width":0.03046875,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"frontend","depth":20,"bounds":{"left":0.07304688,"top":0.42916667,"width":0.02265625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"general","depth":20,"bounds":{"left":0.07304688,"top":0.4486111,"width":0.019140625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"infra-changes","depth":20,"bounds":{"left":0.07304688,"top":0.46805555,"width":0.034765624,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":20,"bounds":{"left":0.07304688,"top":0.4875,"width":0.02734375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"platform-tickets","depth":20,"bounds":{"left":0.07304688,"top":0.5069444,"width":0.041015625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"product_launches","depth":20,"bounds":{"left":0.07304688,"top":0.5263889,"width":0.0453125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"random","depth":20,"bounds":{"left":0.07304688,"top":0.54583335,"width":0.019921875,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"releases","depth":20,"bounds":{"left":0.07304688,"top":0.56527776,"width":0.020703126,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"support","depth":20,"bounds":{"left":0.07304688,"top":0.5847222,"width":0.0203125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"thank-yous","depth":20,"bounds":{"left":0.07304688,"top":0.6041667,"width":0.02890625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"the_people_of_jiminny","depth":20,"bounds":{"left":0.07304688,"top":0.6236111,"width":0.053125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Nikolov","depth":20,"bounds":{"left":0.07304688,"top":0.66944444,"width":0.040234376,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Aneliya Angelova","depth":20,"bounds":{"left":0.07304688,"top":0.6888889,"width":0.044140626,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":",","depth":20,"bounds":{"left":0.11679687,"top":0.6888889,"width":0.0078125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":20,"bounds":{"left":0.11992188,"top":0.6888889,"width":0.016796876,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":",","depth":20,"bounds":{"left":0.13632813,"top":0.70416665,"width":0.000390625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":20,"bounds":{"left":0.13632813,"top":0.70416665,"width":0.000390625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":20,"bounds":{"left":0.07304688,"top":0.7083333,"width":0.04140625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Stoyan Tanev","depth":20,"bounds":{"left":0.07304688,"top":0.7277778,"width":0.033984374,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":20,"bounds":{"left":0.07304688,"top":0.74722224,"width":0.03125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Ivanov","depth":20,"bounds":{"left":0.07304688,"top":0.76666665,"width":0.037890624,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Aneliya Angelova","depth":20,"bounds":{"left":0.07304688,"top":0.7861111,"width":0.044140626,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Ves","depth":20,"bounds":{"left":0.07304688,"top":0.8055556,"width":0.009375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":20,"bounds":{"left":0.07304688,"top":0.825,"width":0.044921875,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Jira Cloud","depth":20,"bounds":{"left":0.07304688,"top":0.87083334,"width":0.02578125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Toast","depth":20,"bounds":{"left":0.07304688,"top":0.8902778,"width":0.013671875,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Google Calendar","depth":20,"bounds":{"left":0.07304688,"top":0.9097222,"width":0.0359375,"height":0.0125},"role_description":"text"},{"role":"AXRadioButton","text":"Messages","depth":18,"bounds":{"left":0.14335938,"top":0.07986111,"width":0.036328126,"height":0.02638889},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Messages","depth":20,"bounds":{"left":0.15429688,"top":0.0875,"width":0.022265624,"height":0.011111111},"role_description":"text"},{"role":"AXRadioButton","text":"More","depth":19,"bounds":{"left":0.18085937,"top":0.07986111,"width":0.023828125,"height":0.02638889},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Add and Edit Channel Tabs","depth":18,"bounds":{"left":0.20429687,"top":0.07986111,"width":0.012890625,"height":0.02638889},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Canvas","depth":18,"bounds":{"left":0.13671875,"top":0.045138888,"width":0.01875,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"List","depth":18,"bounds":{"left":0.13671875,"top":0.045138888,"width":0.009375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":18,"bounds":{"left":0.13671875,"top":0.045138888,"width":0.01640625,"height":0.00069444446},"role_description":"text"},{"role":"AXPopUpButton","text":"Jump to date","depth":23,"bounds":{"left":0.1625,"top":0.10069445,"width":0.06640625,"height":0.00069444446},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Membrane","depth":24,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.02890625,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"APP","depth":24,"bounds":{"left":0.19335938,"top":0.10069445,"width":0.008203125,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"","depth":24,"bounds":{"left":0.20234375,"top":0.10069445,"width":0.003125,"height":0.00069444446},"role_description":"text"},{"role":"AXLink","text":"Nov 11th, 2025 at 12:38:55 AM","depth":24,"bounds":{"left":0.20507812,"top":0.10069445,"width":0.02109375,"height":0.00069444446},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"12:38 AM","depth":25,"bounds":{"left":0.20507812,"top":0.10069445,"width":0.02109375,"height":0.00069444446},"role_description":"text"},{"role":"AXHeading","text":"Heads up – we’re moving to a new domain and legal name!","depth":24,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.084375,"height":0.00069444446},"role_description":"heading"},{"role":"AXStaticText","text":"Heads up – we’re moving to a new domain and legal name!","depth":26,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.07773437,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"In the coming days and weeks, we’ll be transitioning from","depth":24,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.078125,"height":0.00069444446},"role_description":"text"},{"role":"AXLink","text":"integration.app","depth":24,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.0390625,"height":0.00069444446},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"integration.app","depth":25,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.0390625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"domain to","depth":24,"bounds":{"left":0.20078126,"top":0.10069445,"width":0.027734375,"height":0.00069444446},"role_description":"text"},{"role":"AXLink","text":"getmembrane.com","depth":24,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.048046876,"height":0.00069444446},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"getmembrane.com","depth":25,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.048046876,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":". You’ll start seeing our website, docs, and console automatically redirect to the new domain.","depth":24,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.083984375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"No action is needed on your side — all existing APIs and SDKs will continue working as usual.","depth":24,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.083984375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Additionally, we have changed our legal name to Membrane Inc. It will be used in all the paperwork going forward.","depth":24,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.084375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"We’ll share the official launch announcement in the next couple of weeks.","depth":24,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.075,"height":0.00069444446},"role_description":"text"},{"role":"AXCheckBox","text":"1 reaction, react with +1 emoji","depth":25,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.016796876,"height":0.00069444446},"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":26,"bounds":{"left":0.17304687,"top":0.10069445,"width":0.002734375,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Add reaction…","depth":25,"bounds":{"left":0.18007812,"top":0.10069445,"width":0.013671875,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Jump to date","depth":23,"bounds":{"left":0.16289063,"top":0.110416666,"width":0.065625,"height":0.019444445},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Membrane","depth":24,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.02890625,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"APP","depth":24,"bounds":{"left":0.19335938,"top":0.10069445,"width":0.008203125,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"","depth":24,"bounds":{"left":0.20234375,"top":0.10069445,"width":0.003125,"height":0.00069444446},"role_description":"text"},{"role":"AXLink","text":"Dec 15th, 2025 at 7:29:37 PM","depth":24,"bounds":{"left":0.20507812,"top":0.10069445,"width":0.018359374,"height":0.00069444446},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"7:29 PM","depth":25,"bounds":{"left":0.20507812,"top":0.10069445,"width":0.018359374,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Exclusive access","depth":24,"bounds":{"left":0.171875,"top":0.10069445,"width":0.04296875,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"We’ve been working on a new capability at","depth":24,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.07734375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Membrane","depth":24,"bounds":{"left":0.19414063,"top":0.10069445,"width":0.03046875,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"called","depth":24,"bounds":{"left":0.22421876,"top":0.10069445,"width":0.015234375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"self-integration.","depth":24,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.039453126,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Instead of relying on pre-built integrations, your AI agent can now build integrations itself, on the fly, to any app.","depth":24,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.08085938,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"We’re partnering with a small group of teams to pilot this, including our customers. If it sounds relevant to what you’re building, I’d love to include you.","depth":24,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.08046875,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"For now, this is a closed experience as we want to refine the end-to-end flow with close partners as part of our","depth":24,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.08164062,"height":0.034722224},"role_description":"text"},{"role":"AXStaticText","text":"Founding Cohort for Self-Integrations","depth":24,"bounds":{"left":0.16210938,"top":0.12222222,"width":0.08320312,"height":0.028472222},"role_description":"text"},{"role":"AXStaticText","text":".","depth":24,"bounds":{"left":0.23398438,"top":0.1375,"width":0.0015625,"height":0.013194445},"role_description":"text"},{"role":"AXStaticText","text":"Read more here:","depth":24,"bounds":{"left":0.16210938,"top":0.15833333,"width":0.04375,"height":0.013194445},"role_description":"text"},{"role":"AXLink","text":"https://self-integration.getmembrane.com","depth":24,"bounds":{"left":0.16210938,"top":0.15833333,"width":0.07734375,"height":0.028472222},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"https://self-integration.getmembrane.com","depth":25,"bounds":{"left":0.16210938,"top":0.15833333,"width":0.07734375,"height":0.028472222},"role_description":"text"},{"role":"AXStaticText","text":". Check out","depth":24,"bounds":{"left":0.16210938,"top":0.1736111,"width":0.07890625,"height":0.028472222},"role_description":"text"},{"role":"AXStaticText","text":"self-integration manifesto","depth":24,"bounds":{"left":0.16210938,"top":0.18888889,"width":0.06484375,"height":0.028472222},"role_description":"text"},{"role":"AXStaticText","text":"once in, if you’re interested in the vision and why we are working on this.","depth":24,"bounds":{"left":0.16210938,"top":0.20416667,"width":0.08164062,"height":0.04375},"role_description":"text"},{"role":"AXStaticText","text":"Reply or react a","depth":24,"bounds":{"left":0.16210938,"top":0.25555557,"width":0.041796874,"height":0.013194445},"role_description":"text"},{"role":"AXStaticText","text":"if you’re interested","depth":24,"bounds":{"left":0.16210938,"top":0.25555557,"width":0.07304688,"height":0.028472222},"role_description":"text"},{"role":"AXStaticText","text":"— we’re happy to walk you through it over a very short call.","depth":24,"bounds":{"left":0.16210938,"top":0.27083334,"width":0.07929687,"height":0.04375},"role_description":"text"},{"role":"AXPopUpButton","text":"Jump to date","depth":23,"bounds":{"left":0.1765625,"top":0.32777777,"width":0.03828125,"height":0.02013889},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Lukas Kovalik","depth":24,"bounds":{"left":0.16210938,"top":0.3548611,"width":0.036328126,"height":0.015972223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":24,"bounds":{"left":0.19804688,"top":0.35625,"width":0.003515625,"height":0.013194445},"role_description":"text"},{"role":"AXLink","text":"Yesterday at 11:04:11 AM","depth":24,"bounds":{"left":0.20117188,"top":0.35833332,"width":0.02109375,"height":0.011111111},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"11:04 AM","depth":25,"bounds":{"left":0.20117188,"top":0.35833332,"width":0.02109375,"height":0.011111111},"role_description":"text"},{"role":"AXStaticText","text":"Hi guys, we have one issue we used to have before regarding the authorisation for Zoho CRM. When the clients go through all steps and login it just returns him back to the login screen. I believe this is the reference to the previous conversation","depth":25,"bounds":{"left":0.16210938,"top":0.3715278,"width":0.08359375,"height":0.12013889},"role_description":"text"},{"role":"AXLink","text":"https://jiminny.slack.com/archives/C07RAC4U86M/p1748957897141919","depth":25,"bounds":{"left":0.16210938,"top":0.49375,"width":0.083984375,"height":0.04375},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"https://jiminny.slack.com/archives/C07RAC4U86M/p1748957897141919","depth":26,"bounds":{"left":0.16210938,"top":0.49375,"width":0.083984375,"height":0.04375},"role_description":"text"},{"role":"AXStaticText","text":". Could you please have a look if there is any change?","depth":25,"bounds":{"left":0.16210938,"top":0.5243056,"width":0.084375,"height":0.028472222},"role_description":"text"}]...
|
8759043163292253884
|
-3650103901671263030
|
idle
|
hybrid
|
NULL
|
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
Directories
jiminny-x-integration-app
platform-inner-team
ai-chapter
alerts
backend
confusion-clinic
curiosity_lab
engineering
frontend
general
infra-changes
jiminny-bg
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Nikolay Nikolov
Aneliya Angelova
,
Nikolay Yankov
,
Steliyan Georgiev
Galya Dimitrova
Stoyan Tanev
Vasil Vasilev
Nikolay Ivanov
Aneliya Angelova
Ves
Steliyan Georgiev
Jira Cloud
Toast
Google Calendar
Messages
Messages
More
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
Membrane
APP
Nov 11th, 2025 at 12:38:55 AM
12:38 AM
Heads up – we’re moving to a new domain and legal name!
Heads up – we’re moving to a new domain and legal name!
In the coming days and weeks, we’ll be transitioning from
integration.app
integration.app
domain to
getmembrane.com
getmembrane.com
. You’ll start seeing our website, docs, and console automatically redirect to the new domain.
No action is needed on your side — all existing APIs and SDKs will continue working as usual.
Additionally, we have changed our legal name to Membrane Inc. It will be used in all the paperwork going forward.
We’ll share the official launch announcement in the next couple of weeks.
1 reaction, react with +1 emoji
1
Add reaction…
Jump to date
Membrane
APP
Dec 15th, 2025 at 7:29:37 PM
7:29 PM
Exclusive access
We’ve been working on a new capability at
Membrane
called
self-integration.
Instead of relying on pre-built integrations, your AI agent can now build integrations itself, on the fly, to any app.
We’re partnering with a small group of teams to pilot this, including our customers. If it sounds relevant to what you’re building, I’d love to include you.
For now, this is a closed experience as we want to refine the end-to-end flow with close partners as part of our
Founding Cohort for Self-Integrations
.
Read more here:
https://self-integration.getmembrane.com
https://self-integration.getmembrane.com
. Check out
self-integration manifesto
once in, if you’re interested in the vision and why we are working on this.
Reply or react a
if you’re interested
— we’re happy to walk you through it over a very short call.
Jump to date
Lukas Kovalik
Yesterday at 11:04:11 AM
11:04 AM
Hi guys, we have one issue we used to have before regarding the authorisation for Zoho CRM. When the clients go through all steps and login it just returns him back to the login screen. I believe this is the reference to the previous conversation
https://jiminny.slack.com/archives/C07RAC4U86M/p1748957897141919
https://jiminny.slack.com/archives/C07RAC4U86M/p1748957897141919
. Could you please have a look if there is any change?
SlackFileEditViewJiminny ...DMs= Unreads@ Threads6 HuddlesDrafts & sent8 DirectoriesAchivityEh External connectionsFiles# Starred8 jiminny-x-integrati...A platform-inner-teamMoreohannes# ai-chapter# alerts# backendconflicion-clnid# curiosity lab# engineering# frontend# general# infra-changes# jiminny-bg# platrorm-uckets# product_launchesac random# releases# support# thank-yous# the people of jimi...Direct messazesP. Nikolay Nikolov O(3 Aneliya Angelova, ...% Galya Dimitrovaio Stoyan TanevC. Vasil Vasilev. Nikolay Ivanov®. Aneliya Angelovae VesR. Steliyan GeorgievApps# Jira CloudToastGoogle Cale…..HistoryWindowHelpQ Search Jiminny IncA jiminn... & 18• MessagesMore~l December 15th, 2025 •crosePartrers as vart ur vur roundingCohort for Self-Integrations.Read more here: https://self-integration.getmembrane.com.Check out self-integrationmanifesto once in, if you'reinterested in the vision and whywe are working on thisReply or react a if you'reinterested - we're happy towalk you through it over a verySnoncallnYesterdavylLukas Kovalk 11:04 AMHi guys, we have one issue weusea to nave derore regarainginecutnorsarontor cono ckv.When the clients go through allsteps and login it just returnshim back to the login screen. Ibelieve this is the reference tothe previous conversationhttps://jiminny.slack.com/archives/CO7RAC4U86M/p1748957897141919. Could you please havea look if there is any change?e Lukas Kovalik1. There appears to be arecent change in thesok OAutn mecnanism.Wnen a new clientconnees to ine da.rormnusine cono, we nelonger receive a Promise(https://console.integration.app/ref/sdk/classes/IntegrationAccessor.html#openNewConnection)upon successful login.This functionality wasworking just a fewweeks ago.edlledThread in auminnv-x-integraton.app Jun sra. 2025 View messageĐea 20 replies Last reply ...lu external people are tromMembraneMessage & jiminny-x-integration-...Aa40hohlf Support Daily • in 10 m[A]100% 1Z7Fri 17 Aor 14:50:50Thread- sandboxdescription: What is the type of your Zoho account you wantto connect to?default: productiontitle: OAuth 2.0wunoh4C0-5V11-/553-[CREDIT_CARD] 0020externalAppUuid: ae83d614-3dee-48a6-8b2d-944d1ff80687Membrane Al Assistant APP 4 minutes ago• Almost! You're looking at the **connector** definition, but you need to update the**integration** configuration instead.The connector defines what auth options are available, but the integration controlswhich options are shown to your users.Here's how to do it:1. Go to your Zoho CRM integration (not the connector)2. The integration ID from your earlier API response is: 66fe6c913202f3a165e3c14dMake this API call:PATCH https://api.getmembrane.com/integrations/66fe6c913202f3a165e3c14d"optionsConfig": {"auth-proxy": {"disabled": trueOrin the conso le:Navigate to: https://console.getmembrane.com/w/66fd5a6e813fde5d1b8aa505?elenente/mrecrarions/ooreocy15404150.105e50140nis wilo sapeune connec.vavemoane od.ontortnssoecncniecc.omshowing only "OAuth 2.0" to your users.ILukas Novallk 1 minute agoCan I toggle this instead?CleanShot 2026-04-17 at 14.48.17.png vAlso I assume that will be one settings for each zoho crm clientMembrane Al Assistant APP Just now• Perfect! Yes, that's exactly it! ©Just toggle off the "Connect via Membrane" switch in that screenshot and it willhide that auth option from your users. This will leave only the "OAuth 2.0" optionvisible in the connection Ul.And to answer your second question: Yes, this setting is at the integration level, sonaoe es to all connecrons er unis cone crv meccronacroess all vour tenanis.Once you disable it, all your clients will only see the "OAuth 2.0" option whenconnecun?10 external people are from MembraneReply…Also send to jiminny-x-integration-appGo to mark......
|
NULL
|
|
47603
|
NULL
|
0
|
2026-04-17T11:50:39.406454+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776426639406_m1.jpg...
|
Slack
|
jiminny-x-integration-app (Channel) - Jiminny Inc jiminny-x-integration-app (Channel) - Jiminny Inc - Slack...
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
Directories
jiminny-x-integration-app
platform-inner-team
ai-chapter
alerts
backend
confusion-clinic
curiosity_lab
engineering
frontend
general
infra-changes
jiminny-bg
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Nikolay Nikolov
Aneliya Angelova
,
Nikolay Yankov
,
Steliyan Georgiev
Galya Dimitrova
Stoyan Tanev
Vasil Vasilev
Nikolay Ivanov
Aneliya Angelova
Ves
Steliyan Georgiev
Jira Cloud
Toast
Google Calendar
Messages
Messages
More
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
Membrane
APP
Nov 11th, 2025 at 12:38:55 AM
12:38 AM
Heads up – we’re moving to a new domain and legal name!
Heads up – we’re moving to a new domain and legal name!
In the coming days and weeks, we’ll be transitioning from
integration.app
integration.app
domain to
getmembrane.com
getmembrane.com
. You’ll start seeing our website, docs, and console automatically redirect to the new domain.
No action is needed on your side — all existing APIs and SDKs will continue working as usual.
Additionally, we have changed our legal name to Membrane Inc. It will be used in all the paperwork going forward.
We’ll share the official launch announcement in the next couple of weeks.
1 reaction, react with +1 emoji
1
Add reaction…
Jump to date
Membrane
APP
Dec 15th, 2025 at 7:29:37 PM
7:29 PM
Exclusive access
We’ve been working on a new capability at
Membrane
called
self-integration.
Instead of relying on pre-built integrations, your AI agent can now build integrations itself, on the fly, to any app.
We’re partnering with a small group of teams to pilot this, including our customers. If it sounds relevant to what you’re building, I’d love to include you.
For now, this is a closed experience as we want to refine the end-to-end flow with close partners as part of our
Founding Cohort for Self-Integrations
.
Read more here:
https://self-integration.getmembrane.com
https://self-integration.getmembrane.com
. Check out
self-integration manifesto
once in, if you’re interested in the vision and why we are working on this.
Reply or react a
if you’re interested
— we’re happy to walk you through it over a very short call.
Jump to date
Lukas Kovalik
Yesterday at 11:04:11 AM
11:04 AM
Hi guys, we have one issue we used to have before regarding the authorisation for Zoho CRM. When the clients go through all steps and login it just returns him back to the login screen. I believe this is the reference to the previous conversation
https://jiminny.slack.com/archives/C07RAC4U86M/p1748957897141919
https://jiminny.slack.com/archives/C07RAC4U86M/p1748957897141919
. Could you please have a look if there is any change?
Remove preview
Lukas Kovalik
Lukas Kovalik
There appears to be a recent change in the SDK OAuth mechanism. When a new client connects to the platform using Zoho, we no longer receive a Promise (
https://console.integration.app/ref/sdk/classes/IntegrationAccessor.html#openNewConnection
https://console.integration.app/ref/sdk/classes/IntegrationAccessor.html#openNewConnection
) upon successful login. This functionality was working just a few weeks ago.
(edited)
Thread in jiminny-x-integration-app
Thread in
jiminny-x-integration-app
|
Jun 3rd, 2025
Jun 3rd, 2025
|
View message
View message
20 replies
Last reply today at 2:50 PM
View thread
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply to thread
Forward message…...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Jiminny Inc","depth":12,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXRadioButton","text":"Jiminny (Staging)","depth":12,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Add workspaces","depth":12,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":16,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":16,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":16,"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":16,"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":16,"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":16,"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"jiminny-x-integration-app","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"curiosity_lab","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"frontend","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"general","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"infra-changes","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"platform-tickets","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"product_launches","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"random","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"releases","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"support","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"thank-yous","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"the_people_of_jiminny","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Nikolov","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Aneliya Angelova","depth":20,"role_description":"text"},{"role":"AXStaticText","text":",","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":20,"role_description":"text"},{"role":"AXStaticText","text":",","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Stoyan Tanev","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Ivanov","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Aneliya Angelova","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Ves","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Jira Cloud","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Toast","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Google Calendar","depth":20,"role_description":"text"},{"role":"AXRadioButton","text":"Messages","depth":18,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Messages","depth":20,"role_description":"text"},{"role":"AXRadioButton","text":"More","depth":19,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Add and Edit Channel Tabs","depth":18,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Canvas","depth":18,"role_description":"text"},{"role":"AXStaticText","text":"List","depth":18,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":18,"role_description":"text"},{"role":"AXPopUpButton","text":"Jump to date","depth":23,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Membrane","depth":24,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"APP","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"","depth":24,"role_description":"text"},{"role":"AXLink","text":"Nov 11th, 2025 at 12:38:55 AM","depth":24,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"12:38 AM","depth":25,"role_description":"text"},{"role":"AXHeading","text":"Heads up – we’re moving to a new domain and legal name!","depth":24,"role_description":"heading"},{"role":"AXStaticText","text":"Heads up – we’re moving to a new domain and legal name!","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"In the coming days and weeks, we’ll be transitioning from","depth":24,"role_description":"text"},{"role":"AXLink","text":"integration.app","depth":24,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"integration.app","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"domain to","depth":24,"role_description":"text"},{"role":"AXLink","text":"getmembrane.com","depth":24,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"getmembrane.com","depth":25,"role_description":"text"},{"role":"AXStaticText","text":". You’ll start seeing our website, docs, and console automatically redirect to the new domain.","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"No action is needed on your side — all existing APIs and SDKs will continue working as usual.","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"Additionally, we have changed our legal name to Membrane Inc. It will be used in all the paperwork going forward.","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"We’ll share the official launch announcement in the next couple of weeks.","depth":24,"role_description":"text"},{"role":"AXCheckBox","text":"1 reaction, react with +1 emoji","depth":25,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":26,"role_description":"text"},{"role":"AXButton","text":"Add reaction…","depth":25,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Jump to date","depth":23,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Membrane","depth":24,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"APP","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"","depth":24,"role_description":"text"},{"role":"AXLink","text":"Dec 15th, 2025 at 7:29:37 PM","depth":24,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"7:29 PM","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"Exclusive access","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"We’ve been working on a new capability at","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"Membrane","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"called","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"self-integration.","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"Instead of relying on pre-built integrations, your AI agent can now build integrations itself, on the fly, to any app.","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"We’re partnering with a small group of teams to pilot this, including our customers. If it sounds relevant to what you’re building, I’d love to include you.","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"For now, this is a closed experience as we want to refine the end-to-end flow with close partners as part of our","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"Founding Cohort for Self-Integrations","depth":24,"role_description":"text"},{"role":"AXStaticText","text":".","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"Read more here:","depth":24,"role_description":"text"},{"role":"AXLink","text":"https://self-integration.getmembrane.com","depth":24,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"https://self-integration.getmembrane.com","depth":25,"role_description":"text"},{"role":"AXStaticText","text":". Check out","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"self-integration manifesto","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"once in, if you’re interested in the vision and why we are working on this.","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"Reply or react a","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"if you’re interested","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"— we’re happy to walk you through it over a very short call.","depth":24,"role_description":"text"},{"role":"AXPopUpButton","text":"Jump to date","depth":23,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Lukas Kovalik","depth":24,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":24,"role_description":"text"},{"role":"AXLink","text":"Yesterday at 11:04:11 AM","depth":24,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"11:04 AM","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"Hi guys, we have one issue we used to have before regarding the authorisation for Zoho CRM. When the clients go through all steps and login it just returns him back to the login screen. I believe this is the reference to the previous conversation","depth":25,"role_description":"text"},{"role":"AXLink","text":"https://jiminny.slack.com/archives/C07RAC4U86M/p1748957897141919","depth":25,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"https://jiminny.slack.com/archives/C07RAC4U86M/p1748957897141919","depth":26,"role_description":"text"},{"role":"AXStaticText","text":". Could you please have a look if there is any change?","depth":25,"role_description":"text"},{"role":"AXButton","text":"Remove preview","depth":26,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Lukas Kovalik","depth":26,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Lukas Kovalik","depth":28,"role_description":"text"},{"role":"AXStaticText","text":"There appears to be a recent change in the SDK OAuth mechanism. When a new client connects to the platform using Zoho, we no longer receive a Promise (","depth":27,"role_description":"text"},{"role":"AXLink","text":"https://console.integration.app/ref/sdk/classes/IntegrationAccessor.html#openNewConnection","depth":27,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"https://console.integration.app/ref/sdk/classes/IntegrationAccessor.html#openNewConnection","depth":28,"role_description":"text"},{"role":"AXStaticText","text":") upon successful login. This functionality was working just a few weeks ago.","depth":27,"role_description":"text"},{"role":"AXStaticText","text":"(edited)","depth":26,"role_description":"text"},{"role":"AXLink","text":"Thread in jiminny-x-integration-app","depth":26,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Thread in","depth":27,"role_description":"text"},{"role":"AXStaticText","text":"jiminny-x-integration-app","depth":27,"role_description":"text"},{"role":"AXStaticText","text":"|","depth":26,"role_description":"text"},{"role":"AXLink","text":"Jun 3rd, 2025","depth":26,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Jun 3rd, 2025","depth":27,"role_description":"text"},{"role":"AXStaticText","text":"|","depth":26,"role_description":"text"},{"role":"AXLink","text":"View message","depth":26,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"View message","depth":27,"role_description":"text"},{"role":"AXButton","text":"20 replies","depth":24,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Last reply today at 2:50 PM","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"View thread","depth":25,"role_description":"text"},{"role":"AXCheckBox","text":"React with white_check_mark","depth":26,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":26,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":26,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":26,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reply to thread","depth":26,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":26,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
9046074738412255592
|
-3713181308045761334
|
idle
|
hybrid
|
NULL
|
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
Directories
jiminny-x-integration-app
platform-inner-team
ai-chapter
alerts
backend
confusion-clinic
curiosity_lab
engineering
frontend
general
infra-changes
jiminny-bg
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Nikolay Nikolov
Aneliya Angelova
,
Nikolay Yankov
,
Steliyan Georgiev
Galya Dimitrova
Stoyan Tanev
Vasil Vasilev
Nikolay Ivanov
Aneliya Angelova
Ves
Steliyan Georgiev
Jira Cloud
Toast
Google Calendar
Messages
Messages
More
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
Membrane
APP
Nov 11th, 2025 at 12:38:55 AM
12:38 AM
Heads up – we’re moving to a new domain and legal name!
Heads up – we’re moving to a new domain and legal name!
In the coming days and weeks, we’ll be transitioning from
integration.app
integration.app
domain to
getmembrane.com
getmembrane.com
. You’ll start seeing our website, docs, and console automatically redirect to the new domain.
No action is needed on your side — all existing APIs and SDKs will continue working as usual.
Additionally, we have changed our legal name to Membrane Inc. It will be used in all the paperwork going forward.
We’ll share the official launch announcement in the next couple of weeks.
1 reaction, react with +1 emoji
1
Add reaction…
Jump to date
Membrane
APP
Dec 15th, 2025 at 7:29:37 PM
7:29 PM
Exclusive access
We’ve been working on a new capability at
Membrane
called
self-integration.
Instead of relying on pre-built integrations, your AI agent can now build integrations itself, on the fly, to any app.
We’re partnering with a small group of teams to pilot this, including our customers. If it sounds relevant to what you’re building, I’d love to include you.
For now, this is a closed experience as we want to refine the end-to-end flow with close partners as part of our
Founding Cohort for Self-Integrations
.
Read more here:
https://self-integration.getmembrane.com
https://self-integration.getmembrane.com
. Check out
self-integration manifesto
once in, if you’re interested in the vision and why we are working on this.
Reply or react a
if you’re interested
— we’re happy to walk you through it over a very short call.
Jump to date
Lukas Kovalik
Yesterday at 11:04:11 AM
11:04 AM
Hi guys, we have one issue we used to have before regarding the authorisation for Zoho CRM. When the clients go through all steps and login it just returns him back to the login screen. I believe this is the reference to the previous conversation
https://jiminny.slack.com/archives/C07RAC4U86M/p1748957897141919
https://jiminny.slack.com/archives/C07RAC4U86M/p1748957897141919
. Could you please have a look if there is any change?
Remove preview
Lukas Kovalik
Lukas Kovalik
There appears to be a recent change in the SDK OAuth mechanism. When a new client connects to the platform using Zoho, we no longer receive a Promise (
https://console.integration.app/ref/sdk/classes/IntegrationAccessor.html#openNewConnection
https://console.integration.app/ref/sdk/classes/IntegrationAccessor.html#openNewConnection
) upon successful login. This functionality was working just a few weeks ago.
(edited)
Thread in jiminny-x-integration-app
Thread in
jiminny-x-integration-app
|
Jun 3rd, 2025
Jun 3rd, 2025
|
View message
View message
20 replies
Last reply today at 2:50 PM
View thread
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply to thread
Forward message…
FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelp(ah)= Support Daily - in 10 mlAPP (-zsh)X4-zsh100% <47• 878Fri 17 Apr 14:50:381₴81ec2-user@ip-10-...• 88DOCKER• ₴1DEV (docker)• *2APP (-zsh)X3-zsh./public/vue-assets/assets/ondemand-DutwAJ6x.js:./public/vue-assets/assets/CrmLink-DKYsnHnx.js./public/vue-assets/assets/liquor-tree-COUefof4.js./public/vue-assets/assets/DealRiskList-CjPoT6NF.js:./public/vue-assets/assets/AskAnything-a4BpUaF5.js:./public/vue-assets/assets/lib-CwM9toD2.js./public/vue-assets/assets/AppFormField-CtfldUq7.js:./public/vue-assets/assets/deal-view-DhouIWLw.js../public/vue-assets/assets/exports-D1lmea40.js../public/vue-assets/assets/playlists-BWns-By9.js../public/vue-assets/assets/callScoringTemplates-zeRn40ul.js../public/vue-assets/assets/_copy0bject-USkOnlaQ.js../public/vue-assets/assets/pusher-znYCfz7U.js./public/vue-assets/assets/onboard-BENebgvU.js./public/vue-assets/assets/StatusBadge-CmcY3nfX.js./public/vue-assets/assets/kiosk-1vhk4_89.js./public/vue-assets/assets/preload-helper-DCvhahzG.js../public/vue-assets/assets/deal-insights-DN-kVyxK.js../public/vue-assets/assets/ListView-POU6dSu7.js:./public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js./public/vue-assets/assets/WelcomeLayout-B6wd32HG.js../public/vue-assets/assets/dashboard-DYH5MiIH.js:./public/vue-assets/assets/emoji-input-CSq87OVy.js../public/vue-assets/assets/AppButton-D3qMd0Dr.js../public/vue-assets/assets/sentry-c0Rhilsu.js:./public/vue-assets/assets/OrgSettingsLayout-ByQjX4wG.js./public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js./public/vue-assets/assets/playback-BH9MB7YC.js./public/vue-assets/assets/index.module-Bjlhgfdl.js./public/vue-assets/assets/intl-tel-input-BW4mv40Q.js./public/vue-assets/assets/team-insights-BqCZQtVc.js../public/vue-assets/assets/popper-CQwVcrX4.js../public/vue-assets/assets/PhoneField-CwCIoAYm.js./public/vue-assets/assets/live-XL9ZmWsU.js./public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BN0485xV.js../public/vue-assets/assets/index-CPycMtZF.js../public/vue-assets/assets/logged-in-layout-B_JDhk5y.js• ₴526.88kB27.91kB30.75kB34.39kB39.50kB39.69kB41.91kB43.22kB47.84kB48.28kB55.13kB61.28kB62.98kB63.26kB64.66kB79.60kB82.59kB94.84kB115.71kB117.59 kВ120.67kB128.71 kB129.28 kB133.44 kB164.28kB176.33kB180.40kB198.79kB218.14kB264.94 kВ298.57kB307.13kB343.99kB367.43 kB689.63kB825.23 kB1,402.70 kB• built in 28.48s[plugin builtin:vite-reporter](!) Some chunksare larger than 500 kB after minification. Consider:- Using dynamic import() to code-split the application- Use build.rolldown0ptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/Output0ptions.codeSplitting- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-[API_KEY]) $U* Review screenp...gzip:9.38kBgz1p:10.18kBgzip:9.58kB9z1p:10.62kB9z1p:14.97kBgzip:12.70kBgzip:12.69kBgzip:14.34kBgz1p:16.46kBgzip:15.07kBgz1p:13.28kBgzip:20.09kBgzip:18.88kB9z1p:21.90kB921p:22.96kBgzip:22.64kBgzip:27.46kBgzip:gzip:28.16kB33.78kBgzip:38.71 kBgz1p:34.16 kBgzip:40.05kBgzip:36.72 kBgzip:43.03 kB9z1p:52.24kB9z1p:56.15kBgzip:67.85kBgzip:61.85kBgz1p:64.16kBgzip:60.31kB9z1p:77.21kBgz1p:103.86kBgzip:84.90kB9z1p:97.05kBgzip: 202.81kBgzip:72.53-kBgzip: 438.08 kB• [EMAIL]:73.94kBmap:93.18kBmap:78.74kBmap:115.18kBmap:173.20 kBmap:138.34kBmap:150.73kBmap:150.62kBmap:294.48kBmap:153.25kBmap:65..85kBmap:239.59kBmap:219.27kBmap:201.62kBmap:244.72kBmap:300.68kBmap:3,452.35kBmap:292.79kBmap:308.10kBmap:500.60 kBmap:258.56kBmap:410.48kBmap:266.15kBmap:516.67kBmap:831..82kBmap:623.43kBmap:836.88kBmap:684.87kBmap:1,108.20kBmap:475.61kBmap:959.96kBmap:1,245.28kBmap:849.05kBmap:792.41kBmap:3,016.64kBmap:436.62kBmaр: 6,283.55kBAPP...
|
NULL
|
|
47532
|
NULL
|
0
|
2026-04-17T11:45:20.693767+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776426320693_m1.jpg...
|
Slack
|
jiminny-x-integration-app (Channel) - Jiminny Inc jiminny-x-integration-app (Channel) - Jiminny Inc - Slack...
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
Directories
jiminny-x-integration-app
platform-inner-team
ai-chapter
alerts
backend
confusion-clinic
curiosity_lab
engineering
frontend
general
infra-changes
jiminny-bg
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Nikolay Nikolov
Aneliya Angelova
,
Nikolay Yankov
,
Steliyan Georgiev
Galya Dimitrova
Stoyan Tanev
Vasil Vasilev
Nikolay Ivanov
Aneliya Angelova
Ves
Steliyan Georgiev
Jira Cloud
Toast
Google Calendar
Messages
Messages
More
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
Membrane
APP
Nov 11th, 2025 at 12:38:55 AM
12:38 AM
Heads up – we’re moving to a new domain and legal name!
Heads up – we’re moving to a new domain and legal name!
In the coming days and weeks, we’ll be transitioning from
integration.app
integration.app
domain to
getmembrane.com
getmembrane.com
. You’ll start seeing our website, docs, and console automatically redirect to the new domain.
No action is needed on your side — all existing APIs and SDKs will continue working as usual.
Additionally, we have changed our legal name to Membrane Inc. It will be used in all the paperwork going forward.
We’ll share the official launch announcement in the next couple of weeks.
1 reaction, react with +1 emoji
1
Add reaction…
Jump to date
Membrane
APP
Dec 15th, 2025 at 7:29:37 PM
7:29 PM
Exclusive access
We’ve been working on a new capability at
Membrane
called
self-integration.
Instead of relying on pre-built integrations, your AI agent can now build integrations itself, on the fly, to any app.
We’re partnering with a small group of teams to pilot this, including our customers. If it sounds relevant to what you’re building, I’d love to include you.
For now, this is a closed experience as we want to refine the end-to-end flow with close partners as part of our
Founding Cohort for Self-Integrations
.
Read more here:
https://self-integration.getmembrane.com
https://self-integration.getmembrane.com
. Check out
self-integration manifesto
once in, if you’re interested in the vision and why we are working on this.
Reply or react a
if you’re interested
— we’re happy to walk you through it over a very short call.
Jump to date
Lukas Kovalik
Yesterday at 11:04:11 AM
11:04 AM
Hi guys, we have one issue we used to have before regarding the authorisation for Zoho CRM. When the clients go through all steps and login it just returns him back to the login screen. I believe this is the reference to the previous conversation
https://jiminny.slack.com/archives/C07RAC4U86M/p1748957897141919
https://jiminny.slack.com/archives/C07RAC4U86M/p1748957897141919
. Could you please have a look if there is any change?
Remove preview
Lukas Kovalik
Lukas Kovalik
There appears to be a recent change in the SDK OAuth mechanism. When a new client connects to the platform using Zoho, we no longer receive a Promise (
https://console.integration.app/ref/sdk/classes/IntegrationAccessor.html#openNewConnection
https://console.integration.app/ref/sdk/classes/IntegrationAccessor.html#openNewConnection
) upon successful login. This functionality was working just a few weeks ago.
(edited)
Thread in jiminny-x-integration-app
Thread in
jiminny-x-integration-app
|
Jun 3rd, 2025
Jun 3rd, 2025
|
View message
View message
16 replies
Last reply today at 2:45 PM...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Jiminny Inc","depth":12,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXRadioButton","text":"Jiminny (Staging)","depth":12,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Add workspaces","depth":12,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":16,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":16,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":16,"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":16,"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":16,"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":16,"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"jiminny-x-integration-app","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"curiosity_lab","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"frontend","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"general","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"infra-changes","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"platform-tickets","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"product_launches","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"random","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"releases","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"support","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"thank-yous","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"the_people_of_jiminny","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Nikolov","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Aneliya Angelova","depth":22,"role_description":"text"},{"role":"AXStaticText","text":",","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":22,"role_description":"text"},{"role":"AXStaticText","text":",","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Stoyan Tanev","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Ivanov","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Aneliya Angelova","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Ves","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Jira Cloud","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Toast","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Google Calendar","depth":22,"role_description":"text"},{"role":"AXRadioButton","text":"Messages","depth":17,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Messages","depth":19,"role_description":"text"},{"role":"AXRadioButton","text":"More","depth":18,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Add and Edit Channel Tabs","depth":17,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Canvas","depth":17,"role_description":"text"},{"role":"AXStaticText","text":"List","depth":17,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":17,"role_description":"text"},{"role":"AXPopUpButton","text":"Jump to date","depth":23,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Membrane","depth":24,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"APP","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"","depth":24,"role_description":"text"},{"role":"AXLink","text":"Nov 11th, 2025 at 12:38:55 AM","depth":24,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"12:38 AM","depth":25,"role_description":"text"},{"role":"AXHeading","text":"Heads up – we’re moving to a new domain and legal name!","depth":24,"role_description":"heading"},{"role":"AXStaticText","text":"Heads up – we’re moving to a new domain and legal name!","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"In the coming days and weeks, we’ll be transitioning from","depth":24,"role_description":"text"},{"role":"AXLink","text":"integration.app","depth":24,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"integration.app","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"domain to","depth":24,"role_description":"text"},{"role":"AXLink","text":"getmembrane.com","depth":24,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"getmembrane.com","depth":25,"role_description":"text"},{"role":"AXStaticText","text":". You’ll start seeing our website, docs, and console automatically redirect to the new domain.","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"No action is needed on your side — all existing APIs and SDKs will continue working as usual.","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"Additionally, we have changed our legal name to Membrane Inc. It will be used in all the paperwork going forward.","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"We’ll share the official launch announcement in the next couple of weeks.","depth":24,"role_description":"text"},{"role":"AXCheckBox","text":"1 reaction, react with +1 emoji","depth":25,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":26,"role_description":"text"},{"role":"AXButton","text":"Add reaction…","depth":25,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Jump to date","depth":23,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Membrane","depth":24,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"APP","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"","depth":24,"role_description":"text"},{"role":"AXLink","text":"Dec 15th, 2025 at 7:29:37 PM","depth":24,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"7:29 PM","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"Exclusive access","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"We’ve been working on a new capability at","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"Membrane","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"called","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"self-integration.","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"Instead of relying on pre-built integrations, your AI agent can now build integrations itself, on the fly, to any app.","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"We’re partnering with a small group of teams to pilot this, including our customers. If it sounds relevant to what you’re building, I’d love to include you.","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"For now, this is a closed experience as we want to refine the end-to-end flow with close partners as part of our","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"Founding Cohort for Self-Integrations","depth":24,"role_description":"text"},{"role":"AXStaticText","text":".","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"Read more here:","depth":24,"role_description":"text"},{"role":"AXLink","text":"https://self-integration.getmembrane.com","depth":24,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"https://self-integration.getmembrane.com","depth":25,"role_description":"text"},{"role":"AXStaticText","text":". Check out","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"self-integration manifesto","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"once in, if you’re interested in the vision and why we are working on this.","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"Reply or react a","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"if you’re interested","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"— we’re happy to walk you through it over a very short call.","depth":24,"role_description":"text"},{"role":"AXPopUpButton","text":"Jump to date","depth":23,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Lukas Kovalik","depth":24,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":24,"role_description":"text"},{"role":"AXLink","text":"Yesterday at 11:04:11 AM","depth":24,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"11:04 AM","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"Hi guys, we have one issue we used to have before regarding the authorisation for Zoho CRM. When the clients go through all steps and login it just returns him back to the login screen. I believe this is the reference to the previous conversation","depth":25,"role_description":"text"},{"role":"AXLink","text":"https://jiminny.slack.com/archives/C07RAC4U86M/p1748957897141919","depth":25,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"https://jiminny.slack.com/archives/C07RAC4U86M/p1748957897141919","depth":26,"role_description":"text"},{"role":"AXStaticText","text":". Could you please have a look if there is any change?","depth":25,"role_description":"text"},{"role":"AXButton","text":"Remove preview","depth":26,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Lukas Kovalik","depth":26,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Lukas Kovalik","depth":28,"role_description":"text"},{"role":"AXStaticText","text":"There appears to be a recent change in the SDK OAuth mechanism. When a new client connects to the platform using Zoho, we no longer receive a Promise (","depth":27,"role_description":"text"},{"role":"AXLink","text":"https://console.integration.app/ref/sdk/classes/IntegrationAccessor.html#openNewConnection","depth":27,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"https://console.integration.app/ref/sdk/classes/IntegrationAccessor.html#openNewConnection","depth":28,"role_description":"text"},{"role":"AXStaticText","text":") upon successful login. This functionality was working just a few weeks ago.","depth":27,"role_description":"text"},{"role":"AXStaticText","text":"(edited)","depth":26,"role_description":"text"},{"role":"AXLink","text":"Thread in jiminny-x-integration-app","depth":26,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Thread in","depth":27,"role_description":"text"},{"role":"AXStaticText","text":"jiminny-x-integration-app","depth":27,"role_description":"text"},{"role":"AXStaticText","text":"|","depth":26,"role_description":"text"},{"role":"AXLink","text":"Jun 3rd, 2025","depth":26,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Jun 3rd, 2025","depth":27,"role_description":"text"},{"role":"AXStaticText","text":"|","depth":26,"role_description":"text"},{"role":"AXLink","text":"View message","depth":26,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"View message","depth":27,"role_description":"text"},{"role":"AXButton","text":"16 replies","depth":24,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Last reply today at 2:45 PM","depth":25,"role_description":"text"}]...
|
194623728590833772
|
-3640824663994665750
|
idle
|
hybrid
|
NULL
|
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
Directories
jiminny-x-integration-app
platform-inner-team
ai-chapter
alerts
backend
confusion-clinic
curiosity_lab
engineering
frontend
general
infra-changes
jiminny-bg
platform-tickets
product_launches
random
releases
support
thank-yous
the_people_of_jiminny
Nikolay Nikolov
Aneliya Angelova
,
Nikolay Yankov
,
Steliyan Georgiev
Galya Dimitrova
Stoyan Tanev
Vasil Vasilev
Nikolay Ivanov
Aneliya Angelova
Ves
Steliyan Georgiev
Jira Cloud
Toast
Google Calendar
Messages
Messages
More
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
Membrane
APP
Nov 11th, 2025 at 12:38:55 AM
12:38 AM
Heads up – we’re moving to a new domain and legal name!
Heads up – we’re moving to a new domain and legal name!
In the coming days and weeks, we’ll be transitioning from
integration.app
integration.app
domain to
getmembrane.com
getmembrane.com
. You’ll start seeing our website, docs, and console automatically redirect to the new domain.
No action is needed on your side — all existing APIs and SDKs will continue working as usual.
Additionally, we have changed our legal name to Membrane Inc. It will be used in all the paperwork going forward.
We’ll share the official launch announcement in the next couple of weeks.
1 reaction, react with +1 emoji
1
Add reaction…
Jump to date
Membrane
APP
Dec 15th, 2025 at 7:29:37 PM
7:29 PM
Exclusive access
We’ve been working on a new capability at
Membrane
called
self-integration.
Instead of relying on pre-built integrations, your AI agent can now build integrations itself, on the fly, to any app.
We’re partnering with a small group of teams to pilot this, including our customers. If it sounds relevant to what you’re building, I’d love to include you.
For now, this is a closed experience as we want to refine the end-to-end flow with close partners as part of our
Founding Cohort for Self-Integrations
.
Read more here:
https://self-integration.getmembrane.com
https://self-integration.getmembrane.com
. Check out
self-integration manifesto
once in, if you’re interested in the vision and why we are working on this.
Reply or react a
if you’re interested
— we’re happy to walk you through it over a very short call.
Jump to date
Lukas Kovalik
Yesterday at 11:04:11 AM
11:04 AM
Hi guys, we have one issue we used to have before regarding the authorisation for Zoho CRM. When the clients go through all steps and login it just returns him back to the login screen. I believe this is the reference to the previous conversation
https://jiminny.slack.com/archives/C07RAC4U86M/p1748957897141919
https://jiminny.slack.com/archives/C07RAC4U86M/p1748957897141919
. Could you please have a look if there is any change?
Remove preview
Lukas Kovalik
Lukas Kovalik
There appears to be a recent change in the SDK OAuth mechanism. When a new client connects to the platform using Zoho, we no longer receive a Promise (
https://console.integration.app/ref/sdk/classes/IntegrationAccessor.html#openNewConnection
https://console.integration.app/ref/sdk/classes/IntegrationAccessor.html#openNewConnection
) upon successful login. This functionality was working just a few weeks ago.
(edited)
Thread in jiminny-x-integration-app
Thread in
jiminny-x-integration-app
|
Jun 3rd, 2025
Jun 3rd, 2025
|
View message
View message
16 replies
Last reply today at 2:45 PM
FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelp(ah)= Support Daily - in 15 mlAPP (-zsh)X4-zsh100% <47• ₴78Fri 17 Apr 14:45:201₴81ec2-user@ip-10-...• 88DOCKER881DEV (docker)• *2APP (-zsh)X3-zsh./public/vue-assets/assets/ondemand-DutwAJ6x.js:./public/vue-assets/assets/CrmLink-DKYsnHnx.js./public/vue-assets/assets/liquor-tree-COUefof4.js./public/vue-assets/assets/DealRiskList-CjPoT6NF.js:./public/vue-assets/assets/AskAnything-a4BpUaF5.js:./public/vue-assets/assets/lib-CwM9toD2.js./public/vue-assets/assets/AppFormField-CtfldUq7.js./public/vue-assets/assets/deal-view-DhouIWLw.js:./public/vue-assets/assets/exports-D1lmea40.js../public/vue-assets/assets/playlists-BWns-By9.js../public/vue-assets/assets/callScoringTemplates-zeRn40ul.js../public/vue-assets/assets/_copy0bject-USkOnlaQ.js../public/vue-assets/assets/pusher-znYCfz7U.js./public/vue-assets/assets/onboard-BENebgvU.js./public/vue-assets/assets/StatusBadge-CmcY3nfX.js./public/vue-assets/assets/kiosk-1vhk4_89.js./public/vue-assets/assets/preload-helper-DCvhahzG.js../public/vue-assets/assets/deal-insights-DN-kVyxK.js../public/vue-assets/assets/ListView-POU6dSu7.js:./public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js./public/vue-assets/assets/WelcomeLayout-B6wd32HG.js../public/vue-assets/assets/dashboard-DYH5MiIH.js:./public/vue-assets/assets/emoji-input-CSq87OVy.js../public/vue-assets/assets/AppButton-D3qMd0Dr.js../public/vue-assets/assets/sentry-c0Rhilsu.js:./public/vue-assets/assets/OrgSettingsLayout-ByQjX4wG.js./public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js./public/vue-assets/assets/playback-BH9MB7YC.js./public/vue-assets/assets/index.module-Bjlhgfdl.js./public/vue-assets/assets/intl-tel-input-BW4mv40Q.js./public/vue-assets/assets/team-insights-BqCZQtVc.js../public/vue-assets/assets/popper-CQwVcrX4.js../public/vue-assets/assets/PhoneField-CwCIoAYm.js./public/vue-assets/assets/live-XL9ZmWsU.js./public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BN0485xV.js../public/vue-assets/assets/index-CPycMtZF.js../public/vue-assets/assets/logged-in-layout-B_JDhk5y.js• ₴526.88kB27.91kB30.75kB34.39kB39.50kB39.69kB41.91kB43.22kB47.84kB48.28kB55.13kB61.28kB62.98kB63.26kB64.66kB79.60kB82.59kB94.84kB115.71kB117.59 kВ120.67kB128.71 kB129.28 kB133.44 kB164.28kB176.33kB180.40kB198.79kB218.14kB264.94 kВ298.57kB307.13kB343.99kB367.43 kB689.63kB825.23 kB1,402.70 kB• built in 28.48s[plugin builtin:vite-reporter](!) Some chunksare larger than 500 kB after minification. Consider:- Using dynamic import() to code-split the application- Use build.rolldown0ptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/Output0ptions.codeSplitting- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-[API_KEY]) $U* Review screenp...gzip:9.38kBgz1p:10.18kBgzip:9.58kB9z1p:10.62kB9z1p:14.97kBgzip:12.70kBgzip:12.69kBgzip:14.34kBgz1p:16.46kBgzip:15.07kBgz1p:13.28kBgzip:20.09kBgzip:18.88kB9z1p:21.90kB921p:22.96kBgzip:22.64kBgzip:27.46kBgzip:gzip:28.16kB33.78kBgzip:38.71 kBgz1p:34.16 kBgzip:40.05kBgzip:36.72 kBgzip:43.03 kB9z1p:52.24kB9z1p:56.15kBgzip:67.85kBgzip:61.85kBgz1p:64.16kBgzip:60.31kB9z1p:77.21kBgz1p:103.86kBgzip:84.90kB9z1p:97.05kBgzip: 202.81kBgzip:72.53-kBgzip: 438.08 kB• [EMAIL]:73.94kBmap:93.18kBmap:78.74kBmap:115.18kBmap:173.20 kBmap:138.34kBmap:150.73kBmap:150.62kBmap:294.48kBmap:153.25kBmap:65..85kBmap:239.59kBmap:219.27kBmap:201.62kBmар:244.72kBmap:300.68kBmap:3,452.35kBmap:292.79kBmap:308.10kBmap:500.60 kBmap:258.56kBmap:410.48kBmap:266.15kBmap:516.67kBmap:831..82kBmap:623.43kBmap:836.88kBmap:684.87kBmap:1,108.20kBmap:475.61kBmap:959.96kBmap:1,245.28kBmap:849.05kBmap:792.41kBmap:3,016.64kBmap:436.62kBmaр: 6,283.55kBAPP...
|
47522
|
|
47531
|
NULL
|
0
|
2026-04-17T11:45:16.460434+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776426316460_m2.jpg...
|
NULL
|
NULL
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
SlackFileViewJiminny ...DMs= Unreads@ Threads6 Hud SlackFileViewJiminny ...DMs= Unreads@ Threads6 HuddlesDrafts & sent8 DirectoriesAchivityEh External connectionsFilesMore# Starred8 jiminny-x-integrati...A platform-inner-team# Channels# ai-chapter# alerts# backendcontlicion-clinia# curiosity lab# engineering# frontendi# general# infra-changes# jiminny-bg# platrorm-uckets# product_launchesac random# releases# support# thank-yous# the people of jimi...Direct messazesP. Nikolay Nikolov O(3 Aneliya Angelova, ...®. Galya Dimitrova lio Stoyan TanevC. Vasil Vasilev. Nikolay Ivanov®. Aneliya Angelovae VesR. Steliyan Georgiev# Apps# Jira CloudToastGoogle Cale…..HistoryWindowHelpQ Search Jiminny Inc& jiminn... & 18• MessagesMore vl December 15th, 2025 •crosePartrers as vart ur vur roundingCohort for Self-Integrations.Read more here: [URL_WITH_CREDENTIALS] axios from 'axios'exoort ceraule asyne runccion ceccredenclatsrromtokennesponsecokennesponse,connectionParameters,const avallableurgs = awalt axzos.getc'${tokenResponse.api_domain}/crm/v6/organizations*,headers: {Authorization:Lono-ouuerrcoken${tokenResponse.access_token}',const selected0rg = available0rgs.data.organizations.findC(org) = org. type == (connectionParameters?.account_type ??'production'), // When account_type is not specified, we treat it as"production one.if (!selectedorg) {throo zen conm oriontzation found. You probabty need tocreate one.'return{...tokenResponse,accountserven:tokenResponse.accountsServer,apt _url: selected0rg?.api_url, //"https://developer.zohoapis.eu" for developer account or"https://www.zohoapis.eu" for prodweb_url: selected0rg?.web_url, //"developer.zoho.eu/crm/endpointsintegr" for developer account or"https://crm.zoho.eu/crm/orgZ0079607241" for prodtype: objectproperties:account_type:type: string- production- developer- sandboxdescription: What is the type of your zoho account you wantto connect toaerault. proauctiontitle: OAuth 2.0uuid: Zc8930ff-7333-4Zac-94b8-1189bcdc9277externalAppUuid: ae83d614-3dee-48a6-8b2d-944d1ff8068710 external people are from MembraneReply...Also send to jiminny-x-integration-aprcount_type is not specified, we treat it as 'production' one.e40kbSepportaily in1m100% C Fri17 Apr 14:45:16@ AgentWhat would you like to do?Kecent sessionsbeld1 All sessionsnow lo cisaole connectva Memoranent or "https://www.zohoapis.eu" for proder account or "https://crm.zoho.eu/crm/org20079607241" for prodMpov Tomaune oov Dontask aralin...
|
NULL
|
-4505058237835438461
|
NULL
|
visual_change
|
ocr
|
NULL
|
SlackFileViewJiminny ...DMs= Unreads@ Threads6 Hud SlackFileViewJiminny ...DMs= Unreads@ Threads6 HuddlesDrafts & sent8 DirectoriesAchivityEh External connectionsFilesMore# Starred8 jiminny-x-integrati...A platform-inner-team# Channels# ai-chapter# alerts# backendcontlicion-clinia# curiosity lab# engineering# frontendi# general# infra-changes# jiminny-bg# platrorm-uckets# product_launchesac random# releases# support# thank-yous# the people of jimi...Direct messazesP. Nikolay Nikolov O(3 Aneliya Angelova, ...®. Galya Dimitrova lio Stoyan TanevC. Vasil Vasilev. Nikolay Ivanov®. Aneliya Angelovae VesR. Steliyan Georgiev# Apps# Jira CloudToastGoogle Cale…..HistoryWindowHelpQ Search Jiminny Inc& jiminn... & 18• MessagesMore vl December 15th, 2025 •crosePartrers as vart ur vur roundingCohort for Self-Integrations.Read more here: [URL_WITH_CREDENTIALS] axios from 'axios'exoort ceraule asyne runccion ceccredenclatsrromtokennesponsecokennesponse,connectionParameters,const avallableurgs = awalt axzos.getc'${tokenResponse.api_domain}/crm/v6/organizations*,headers: {Authorization:Lono-ouuerrcoken${tokenResponse.access_token}',const selected0rg = available0rgs.data.organizations.findC(org) = org. type == (connectionParameters?.account_type ??'production'), // When account_type is not specified, we treat it as"production one.if (!selectedorg) {throo zen conm oriontzation found. You probabty need tocreate one.'return{...tokenResponse,accountserven:tokenResponse.accountsServer,apt _url: selected0rg?.api_url, //"https://developer.zohoapis.eu" for developer account or"https://www.zohoapis.eu" for prodweb_url: selected0rg?.web_url, //"developer.zoho.eu/crm/endpointsintegr" for developer account or"https://crm.zoho.eu/crm/orgZ0079607241" for prodtype: objectproperties:account_type:type: string- production- developer- sandboxdescription: What is the type of your zoho account you wantto connect toaerault. proauctiontitle: OAuth 2.0uuid: Zc8930ff-7333-4Zac-94b8-1189bcdc9277externalAppUuid: ae83d614-3dee-48a6-8b2d-944d1ff8068710 external people are from MembraneReply...Also send to jiminny-x-integration-aprcount_type is not specified, we treat it as 'production' one.e40kbSepportaily in1m100% C Fri17 Apr 14:45:16@ AgentWhat would you like to do?Kecent sessionsbeld1 All sessionsnow lo cisaole connectva Memoranent or "https://www.zohoapis.eu" for proder account or "https://crm.zoho.eu/crm/org20079607241" for prodMpov Tomaune oov Dontask aralin...
|
47530
|
|
47455
|
NULL
|
0
|
2026-04-17T11:40:08.824028+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776426008824_m2.jpg...
|
NULL
|
NULL
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
FirefoxFileEoitViewHistoryBookmarksProfilesToolsWi FirefoxFileEoitViewHistoryBookmarksProfilesToolsWindow Help...• •= console.getmembrane.com/w/66fd5a6e813fde5d1b8aa505?element=%2FcConnectors / Zoho CRM#a Public‹> Development vDevelopers | HubSpot• OverviewM'inbox (1,576) - lukas.kovalik@jiminQ EK© Basic InfoM 120216 is your HubSpot Log In ColConnector Configuration• OperationsNameLono cKvDashboarc• Data CollectionsCa CloudWatch | eu-west-1Keyzoho-crmNew TabExplor= Universal Data Models4 Global WebhooksApp2 Zoho CRMZ Configure SSH access to multipleAcuvity4 EventsLOC0https://static.integration.app/connectors/zoho-crm/logo.png. fix-cache-for-business-processesSettings4 [JY-20692] Issue with reconnectir(8) JiminnyDoCs &Auth ProxyDashooard• Jiminny• Memoral X+ New TabLogoutVersion2026-04-16<› ImplementationMultiple optionsCommonConnect via MembraneOAuth 2.0Title?OAuth 2.0KeyoaulnlDescriptionEnter description….EnabledImplementation TypeIu oauth2Delete this optionParametersUl CodeDescriptionSecret.Register a server-based application in the Zoho API Console to obtain your Client ID and ClientSchemaclientld: stringTitle: Client ID Required: yesclientSecret: stringTitle: Client Secret Required: yesscopes: listitem: stringDefault: ZohoCRM.modules.ALL,ZohoCRM.users.ALL,ZohoCRM.settings.ALL,ZohoCRM.org.READ,Z..Ul CodeD Test40 lil Support Daily • in 20mA100% CS•8 Fri 17 Apr 14:40:08< @ Agent / How to disable Connect via MembraneHow to disable Connect via MembraneI'd be happy to help you disable Connect via Membrane! However, I need a bit more context togive you the most accurate answer.coule vou cerly wnal vou mean ov "connec.va vemorene. Hre vou reterrine lo1. Disabling a specific connection - removing a customer's connection to an external app (likeStripe, Salesforce, etc.)?2. Disabling an integration - turning off an entire integration in your workspace?3. Disabling OAuth/authentication flow - preventing users from connecting their accounts?4. Disabling a specific action or flow - turning off certain integration functionality?5. Something else - perhaps related to Membrane's connection management features?Once you clarify, I can provide you with the specific steps or code needed to accomplish whatvoure lookine tor.I want to see just connect to zoho and one connect button not OAuth 2.0 or Connect via MembraneI understand now! You want to customize the connection Ul so that users see only a single"Connect to Zoho" button instead of multiple connection options like OAuth 2.0 or "Connect viaMembrane".To help you with this, I need to understand your setup better:1. Are you building a custom Ul for your customers to connect to Zoho, or are you usingMembrane's built-in connection Ul?2. What type of authentication does your Zoho integration use?• OAuth 2.0?• API Key?• Custom authentication?3. Where is this connection Ul being displayed?In vour own application?• In a Membrane-hosted portal?Once I understand your setup, I can guide you on:• How to customize the connection now to snow only one button• How to hide alternative authentication methods• How to configure the Zoho integration to use a specific auth methodCould you provide these details?What would you like to do?[ Open Iframe Session Screen...
|
NULL
|
1247962075334066611
|
NULL
|
visual_change
|
ocr
|
NULL
|
FirefoxFileEoitViewHistoryBookmarksProfilesToolsWi FirefoxFileEoitViewHistoryBookmarksProfilesToolsWindow Help...• •= console.getmembrane.com/w/66fd5a6e813fde5d1b8aa505?element=%2FcConnectors / Zoho CRM#a Public‹> Development vDevelopers | HubSpot• OverviewM'inbox (1,576) - lukas.kovalik@jiminQ EK© Basic InfoM 120216 is your HubSpot Log In ColConnector Configuration• OperationsNameLono cKvDashboarc• Data CollectionsCa CloudWatch | eu-west-1Keyzoho-crmNew TabExplor= Universal Data Models4 Global WebhooksApp2 Zoho CRMZ Configure SSH access to multipleAcuvity4 EventsLOC0https://static.integration.app/connectors/zoho-crm/logo.png. fix-cache-for-business-processesSettings4 [JY-20692] Issue with reconnectir(8) JiminnyDoCs &Auth ProxyDashooard• Jiminny• Memoral X+ New TabLogoutVersion2026-04-16<› ImplementationMultiple optionsCommonConnect via MembraneOAuth 2.0Title?OAuth 2.0KeyoaulnlDescriptionEnter description….EnabledImplementation TypeIu oauth2Delete this optionParametersUl CodeDescriptionSecret.Register a server-based application in the Zoho API Console to obtain your Client ID and ClientSchemaclientld: stringTitle: Client ID Required: yesclientSecret: stringTitle: Client Secret Required: yesscopes: listitem: stringDefault: ZohoCRM.modules.ALL,ZohoCRM.users.ALL,ZohoCRM.settings.ALL,ZohoCRM.org.READ,Z..Ul CodeD Test40 lil Support Daily • in 20mA100% CS•8 Fri 17 Apr 14:40:08< @ Agent / How to disable Connect via MembraneHow to disable Connect via MembraneI'd be happy to help you disable Connect via Membrane! However, I need a bit more context togive you the most accurate answer.coule vou cerly wnal vou mean ov "connec.va vemorene. Hre vou reterrine lo1. Disabling a specific connection - removing a customer's connection to an external app (likeStripe, Salesforce, etc.)?2. Disabling an integration - turning off an entire integration in your workspace?3. Disabling OAuth/authentication flow - preventing users from connecting their accounts?4. Disabling a specific action or flow - turning off certain integration functionality?5. Something else - perhaps related to Membrane's connection management features?Once you clarify, I can provide you with the specific steps or code needed to accomplish whatvoure lookine tor.I want to see just connect to zoho and one connect button not OAuth 2.0 or Connect via MembraneI understand now! You want to customize the connection Ul so that users see only a single"Connect to Zoho" button instead of multiple connection options like OAuth 2.0 or "Connect viaMembrane".To help you with this, I need to understand your setup better:1. Are you building a custom Ul for your customers to connect to Zoho, or are you usingMembrane's built-in connection Ul?2. What type of authentication does your Zoho integration use?• OAuth 2.0?• API Key?• Custom authentication?3. Where is this connection Ul being displayed?In vour own application?• In a Membrane-hosted portal?Once I understand your setup, I can guide you on:• How to customize the connection now to snow only one button• How to hide alternative authentication methods• How to configure the Zoho integration to use a specific auth methodCould you provide these details?What would you like to do?[ Open Iframe Session Screen...
|
NULL
|
|
47453
|
NULL
|
0
|
2026-04-17T11:40:07.653490+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776426007653_m1.jpg...
|
Firefox
|
Dashboard · Jiminny · Membrane — Work
|
True
|
console.getmembrane.com/w/66fd5a6e813fde5d1b8aa505 console.getmembrane.com/w/66fd5a6e813fde5d1b8aa505?element=%2Fconnectors%2F64a158e7d2605720d232e07b&aside=%2Fagent%2F69e21b571e4d92fd3b379d4c...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Developers | HubSpot
Developers Workers | Datadog
Developers | HubSpot
Developers | HubSpot
Inbox (1,576) - [EMAIL] - Jiminny Mail
Inbox (1,576) - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
CloudWatch | eu-west-1
CloudWatch | eu-west-1
New Tab
New Tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
[JY-20692] Issue with reconnecting Zoho - Jira
[JY-20692] Issue with reconnecting Zoho - Jira
Jiminny
Jiminny
Auth Proxy
Auth Proxy
Dashboard · Jiminny · Membrane
Dashboard · Jiminny · Membrane
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Workspace: Jiminny
Jiminny
Search
⌘K
Dashboard
Dashboard
Explore
Explore
Activity
Activity
Settings
Settings
Docs
Docs
Logout
Version
2026-04-16
Connectors
Connectors
Zoho CRM
Zoho CRM...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Developers | HubSpot","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Developers | HubSpot","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Inbox (1,576) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox (1,576) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"120216 is your HubSpot Log In Code - integration-account@jiminny.com - Jiminny Mail","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"120216 is your HubSpot Log In Code - integration-account@jiminny.com - Jiminny Mail","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | eu-west-1","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | eu-west-1","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20692] Issue with reconnecting Zoho - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20692] Issue with reconnecting Zoho - Jira","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Auth Proxy","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Auth Proxy","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Dashboard · Jiminny · Membrane","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Dashboard · Jiminny · Membrane","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Workspace: Jiminny","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search","depth":10,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"⌘K","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Dashboard","depth":11,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Dashboard","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Explore","depth":11,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Explore","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Activity","depth":11,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Activity","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":11,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Docs","depth":11,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Docs","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Logout","depth":10,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Version","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2026-04-16","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Connectors","depth":18,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Connectors","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Zoho CRM","depth":18,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Zoho CRM","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-1452650438026713403
|
-7332039362156557812
|
click
|
accessibility
|
NULL
|
Workers | Datadog
Developers | HubSpot
Developers Workers | Datadog
Developers | HubSpot
Developers | HubSpot
Inbox (1,576) - [EMAIL] - Jiminny Mail
Inbox (1,576) - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
CloudWatch | eu-west-1
CloudWatch | eu-west-1
New Tab
New Tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
[JY-20692] Issue with reconnecting Zoho - Jira
[JY-20692] Issue with reconnecting Zoho - Jira
Jiminny
Jiminny
Auth Proxy
Auth Proxy
Dashboard · Jiminny · Membrane
Dashboard · Jiminny · Membrane
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Workspace: Jiminny
Jiminny
Search
⌘K
Dashboard
Dashboard
Explore
Explore
Activity
Activity
Settings
Settings
Docs
Docs
Logout
Version
2026-04-16
Connectors
Connectors
Zoho CRM
Zoho CRM...
|
47450
|
|
47385
|
NULL
|
0
|
2026-04-17T11:34:56.552536+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776425696552_m2.jpg...
|
Firefox
|
Dashboard · Jiminny · Membrane — Work
|
True
|
console.getmembrane.com/w/66fd5a6e813fde5d1b8aa505 console.getmembrane.com/w/66fd5a6e813fde5d1b8aa505?element=/connectors/64a158e7d2605720d232e07b...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Developers | HubSpot
Developers Workers | Datadog
Developers | HubSpot
Developers | HubSpot
Inbox (1,576) - [EMAIL] - Jiminny Mail
Inbox (1,576) - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
CloudWatch | eu-west-1
CloudWatch | eu-west-1
New Tab
New Tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
[JY-20692] Issue with reconnecting Zoho - Jira
[JY-20692] Issue with reconnecting Zoho - Jira
Jiminny
Jiminny
Auth Proxy
Auth Proxy
Dashboard · Jiminny · Membrane
Dashboard · Jiminny · Membrane
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Workspace: Jiminny
Jiminny
Search
⌘K
Dashboard
Dashboard
Explore
Explore
Activity
Activity
Settings
Settings
Docs
Docs
Logout
Version
2026-04-16
Connectors
Connectors
Zoho CRM
Zoho CRM
Public
Development
Development
Test
Test
Overview
Overview
Connector Configuration
Connector Configuration
Operations
Operations
Data Collections
Data Collections
Universal Data Models
Universal Data Models
Global Webhooks
Global Webhooks
Events
Events
Editor content
34
36
37
45
46
makeApiClient
:
mapping
:
args
:
headers
:
Authorization
:
Agent
Agent
Beta
What would you like to do?...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"bounds":{"left":0.00234375,"top":0.045138888,"width":0.0890625,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Developers | HubSpot","depth":4,"bounds":{"left":0.0,"top":0.08263889,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Developers | HubSpot","depth":5,"bounds":{"left":0.015625,"top":0.09236111,"width":0.04453125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Inbox (1,576) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.11111111,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox (1,576) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"bounds":{"left":0.015625,"top":0.12083333,"width":0.11445312,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"120216 is your HubSpot Log In Code - integration-account@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.13958333,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"120216 is your HubSpot Log In Code - integration-account@jiminny.com - Jiminny Mail","depth":5,"bounds":{"left":0.015625,"top":0.14930555,"width":0.17734376,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | eu-west-1","depth":4,"bounds":{"left":0.0,"top":0.16805555,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | eu-west-1","depth":5,"bounds":{"left":0.015625,"top":0.17777778,"width":0.048828125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.19652778,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.015625,"top":0.20625,"width":0.017578125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.225,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":5,"bounds":{"left":0.015625,"top":0.23472223,"width":0.1515625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.2534722,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app","depth":5,"bounds":{"left":0.015625,"top":0.26319444,"width":0.17421874,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20692] Issue with reconnecting Zoho - Jira","depth":4,"bounds":{"left":0.0,"top":0.28194445,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20692] Issue with reconnecting Zoho - Jira","depth":5,"bounds":{"left":0.015625,"top":0.29166666,"width":0.09726562,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.31041667,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.015625,"top":0.3201389,"width":0.015625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Auth Proxy","depth":4,"bounds":{"left":0.0,"top":0.33888888,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Auth Proxy","depth":5,"bounds":{"left":0.015625,"top":0.34861112,"width":0.021875,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Dashboard · Jiminny · Membrane","depth":4,"bounds":{"left":0.0,"top":0.3673611,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Dashboard · Jiminny · Membrane","depth":5,"bounds":{"left":0.015625,"top":0.37708333,"width":0.06679688,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.07890625,"top":0.37361112,"width":0.009375,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.003125,"top":0.39722222,"width":0.08710937,"height":0.022222223},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.003125,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.01640625,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.029296875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.0421875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.05546875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Workspace: Jiminny","depth":10,"bounds":{"left":0.09609375,"top":0.047916666,"width":0.0265625,"height":0.05277778},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":12,"bounds":{"left":0.09921875,"top":0.088194445,"width":0.01484375,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search","depth":10,"bounds":{"left":0.09765625,"top":0.10625,"width":0.0234375,"height":0.023611112},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"⌘K","depth":11,"bounds":{"left":0.11054687,"top":0.11388889,"width":0.00546875,"height":0.008333334},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Dashboard","depth":11,"bounds":{"left":0.09609375,"top":0.13541667,"width":0.0265625,"height":0.038194444},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Dashboard","depth":12,"bounds":{"left":0.09921875,"top":0.15833333,"width":0.0203125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Explore","depth":11,"bounds":{"left":0.09609375,"top":0.17638889,"width":0.0265625,"height":0.038194444},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Explore","depth":12,"bounds":{"left":0.10234375,"top":0.19930555,"width":0.0140625,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Activity","depth":11,"bounds":{"left":0.09609375,"top":0.21736111,"width":0.0265625,"height":0.038194444},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Activity","depth":12,"bounds":{"left":0.10195313,"top":0.24027778,"width":0.01484375,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":11,"bounds":{"left":0.09609375,"top":0.25833333,"width":0.0265625,"height":0.038194444},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":12,"bounds":{"left":0.1015625,"top":0.28125,"width":0.015625,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Docs","depth":11,"bounds":{"left":0.09609375,"top":0.30833334,"width":0.0265625,"height":0.038194444},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Docs","depth":12,"bounds":{"left":0.10234375,"top":0.33125,"width":0.009375,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Logout","depth":10,"bounds":{"left":0.09609375,"top":0.9166667,"width":0.0265625,"height":0.038194444},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Version","depth":11,"bounds":{"left":0.10234375,"top":0.9673611,"width":0.0140625,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2026-04-16","depth":11,"bounds":{"left":0.0984375,"top":0.9791667,"width":0.021875,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Connectors","depth":18,"bounds":{"left":0.13125,"top":0.054166667,"width":0.030078124,"height":0.0125},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Connectors","depth":19,"bounds":{"left":0.13125,"top":0.054166667,"width":0.030078124,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Zoho CRM","depth":18,"bounds":{"left":0.16914062,"top":0.054166667,"width":0.02734375,"height":0.0125},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Zoho CRM","depth":19,"bounds":{"left":0.16914062,"top":0.054166667,"width":0.02734375,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Public","depth":15,"bounds":{"left":0.21171875,"top":0.055555556,"width":0.013671875,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Development","depth":15,"bounds":{"left":0.23320313,"top":0.052083332,"width":0.056640625,"height":0.016666668},"value":"Development","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Development","depth":17,"bounds":{"left":0.246875,"top":0.055555556,"width":0.029296875,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Test","depth":15,"bounds":{"left":0.6304687,"top":0.050694443,"width":0.03203125,"height":0.019444445},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Test","depth":16,"bounds":{"left":0.6449219,"top":0.054166667,"width":0.0109375,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Overview","depth":20,"bounds":{"left":0.128125,"top":0.08194444,"width":0.07460938,"height":0.022916667},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Overview","depth":22,"bounds":{"left":0.1390625,"top":0.0875,"width":0.024609376,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Connector Configuration","depth":18,"bounds":{"left":0.128125,"top":0.11875,"width":0.07460938,"height":0.017361112},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Connector Configuration","depth":19,"bounds":{"left":0.1296875,"top":0.121527776,"width":0.06015625,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Operations","depth":20,"bounds":{"left":0.128125,"top":0.1388889,"width":0.07460938,"height":0.022916667},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Operations","depth":22,"bounds":{"left":0.1390625,"top":0.14444445,"width":0.028515626,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Data Collections","depth":20,"bounds":{"left":0.128125,"top":0.16319445,"width":0.07460938,"height":0.022916667},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Data Collections","depth":22,"bounds":{"left":0.1390625,"top":0.16875,"width":0.0421875,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Universal Data Models","depth":20,"bounds":{"left":0.128125,"top":0.1875,"width":0.07460938,"height":0.022916667},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Universal Data Models","depth":22,"bounds":{"left":0.1390625,"top":0.19305556,"width":0.058203124,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Global Webhooks","depth":20,"bounds":{"left":0.128125,"top":0.21180555,"width":0.07460938,"height":0.022916667},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Global Webhooks","depth":22,"bounds":{"left":0.1390625,"top":0.21736111,"width":0.0453125,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Events","depth":20,"bounds":{"left":0.128125,"top":0.2361111,"width":0.07460938,"height":0.022916667},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Events","depth":22,"bounds":{"left":0.1390625,"top":0.24166666,"width":0.0171875,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextArea","text":"Editor content","depth":20,"help_text":"","role_description":"editor","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"34","depth":23,"bounds":{"left":0.215625,"top":0.07777778,"width":0.00546875,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":23,"bounds":{"left":0.22382812,"top":0.077083334,"width":0.00625,"height":0.011111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"36","depth":23,"bounds":{"left":0.215625,"top":0.090277776,"width":0.00546875,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":23,"bounds":{"left":0.22382812,"top":0.08958333,"width":0.00625,"height":0.011111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"37","depth":23,"bounds":{"left":0.215625,"top":0.10277778,"width":0.00546875,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":23,"bounds":{"left":0.22382812,"top":0.10208333,"width":0.00625,"height":0.011111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"45","depth":23,"bounds":{"left":0.215625,"top":0.115277775,"width":0.00546875,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":23,"bounds":{"left":0.22382812,"top":0.114583336,"width":0.00625,"height":0.011111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"46","depth":23,"bounds":{"left":0.215625,"top":0.12777779,"width":0.00546875,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":23,"bounds":{"left":0.22382812,"top":0.12708333,"width":0.00625,"height":0.011111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"makeApiClient","depth":24,"bounds":{"left":0.22734375,"top":0.07777778,"width":0.03671875,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":24,"bounds":{"left":0.2640625,"top":0.07777778,"width":0.002734375,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"mapping","depth":24,"bounds":{"left":0.2328125,"top":0.090277776,"width":0.019921875,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":24,"bounds":{"left":0.25273436,"top":0.090277776,"width":0.002734375,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"args","depth":24,"bounds":{"left":0.23867187,"top":0.10277778,"width":0.011328125,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":24,"bounds":{"left":0.25,"top":0.10277778,"width":0.002734375,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"headers","depth":24,"bounds":{"left":0.24414062,"top":0.115277775,"width":0.019921875,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":24,"bounds":{"left":0.2640625,"top":0.115277775,"width":0.002734375,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Authorization","depth":24,"bounds":{"left":0.25,"top":0.12777779,"width":0.03671875,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":24,"bounds":{"left":0.28671876,"top":0.12777779,"width":0.002734375,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Agent","depth":12,"bounds":{"left":0.70820314,"top":0.05347222,"width":0.016015625,"height":0.014583333},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Agent","depth":13,"bounds":{"left":0.70820314,"top":0.054166667,"width":0.016015625,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Beta","depth":13,"bounds":{"left":0.98125,"top":0.055555556,"width":0.01015625,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"","depth":13,"bounds":{"left":0.7046875,"top":0.09652778,"width":0.28515625,"height":0.022222223},"value":"","help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"What would you like to do?","depth":14,"bounds":{"left":0.7046875,"top":0.09791667,"width":0.06992187,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
5638205737690869664
|
-7333444280310396340
|
visual_change
|
accessibility
|
NULL
|
Workers | Datadog
Developers | HubSpot
Developers Workers | Datadog
Developers | HubSpot
Developers | HubSpot
Inbox (1,576) - [EMAIL] - Jiminny Mail
Inbox (1,576) - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
CloudWatch | eu-west-1
CloudWatch | eu-west-1
New Tab
New Tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
[JY-20692] Issue with reconnecting Zoho - Jira
[JY-20692] Issue with reconnecting Zoho - Jira
Jiminny
Jiminny
Auth Proxy
Auth Proxy
Dashboard · Jiminny · Membrane
Dashboard · Jiminny · Membrane
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Workspace: Jiminny
Jiminny
Search
⌘K
Dashboard
Dashboard
Explore
Explore
Activity
Activity
Settings
Settings
Docs
Docs
Logout
Version
2026-04-16
Connectors
Connectors
Zoho CRM
Zoho CRM
Public
Development
Development
Test
Test
Overview
Overview
Connector Configuration
Connector Configuration
Operations
Operations
Data Collections
Data Collections
Universal Data Models
Universal Data Models
Global Webhooks
Global Webhooks
Events
Events
Editor content
34
36
37
45
46
makeApiClient
:
mapping
:
args
:
headers
:
Authorization
:
Agent
Agent
Beta
What would you like to do?...
|
47384
|
|
47382
|
NULL
|
0
|
2026-04-17T11:34:51.388894+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776425691388_m1.jpg...
|
NULL
|
NULL
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
FirefoxFileEditViewHistoryBookmarksProfilesToolsWi FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelpalol• Support Daily - in 26 mlAPP (-zsh)X4-zsh100% <47• ₴78Fri 17 Apr 14:34:511₴81ec2-user@ip-10-...• 88DOCKER• ₴1DEV (docker)• *2APP (-zsh)X3-zsh./public/vue-assets/assets/ondemand-DutwAJ6x.js:./public/vue-assets/assets/CrmLink-DKYsnHnx.js./public/vue-assets/assets/liquor-tree-COUefof4.js./public/vue-assets/assets/DealRiskList-CjPoT6NF.js:./public/vue-assets/assets/AskAnything-a4BpUaF5.js:./public/vue-assets/assets/lib-CwM9toD2.js./public/vue-assets/assets/AppFormField-CtfldUq7.js:./public/vue-assets/assets/deal-view-DhouIWLw.js../public/vue-assets/assets/exports-D1lmea40.js../public/vue-assets/assets/playlists-BWns-By9.js../public/vue-assets/assets/callScoringTemplates-zeRn40ul.js../public/vue-assets/assets/_copy0bject-USkOnlaQ.js../public/vue-assets/assets/pusher-znYCfz7U.js./public/vue-assets/assets/onboard-BENebgvU.js./public/vue-assets/assets/StatusBadge-CmcY3nfX.js./public/vue-assets/assets/kiosk-1vhk4_89.js./public/vue-assets/assets/preload-helper-DCvhahzG.js../public/vue-assets/assets/deal-insights-DN-kVyxK.js../public/vue-assets/assets/ListView-POU6dSu7.js:./public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js./public/vue-assets/assets/WelcomeLayout-B6wd32HG.js../public/vue-assets/assets/dashboard-DYH5MiIH.js:./public/vue-assets/assets/emoji-input-CSq87OVy.js../public/vue-assets/assets/AppButton-D3qMd0Dr.js../public/vue-assets/assets/sentry-c0Rhilsu.js:./public/vue-assets/assets/OrgSettingsLayout-ByQjX4wG.js./public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js./public/vue-assets/assets/playback-BH9MB7YC.js./public/vue-assets/assets/index.module-Bjlhgfdl.js./public/vue-assets/assets/intl-tel-input-BW4mv40Q.js./public/vue-assets/assets/team-insights-BqCZQtVc.js../public/vue-assets/assets/popper-CQwVcrX4.js../public/vue-assets/assets/PhoneField-CwCIoAYm.js./public/vue-assets/assets/live-XL9ZmWsU.js./public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BN0485xV.js../public/vue-assets/assets/index-CPycMtZF.js../public/vue-assets/assets/logged-in-layout-B_JDhk5y.js• ₴526.88kB27.91kB30.75kB34.39kB39.50kB39.69kB41.91kB43.22kB47.84kB48.28kB55.13kB61.28kB62.98kB63.26kB64.66kB79.60kB82.59kB94.84kB115.71kB117.59 kВ120.67kB128.71 kB129.28 kB133.44 kB164.28kB176.33kB180.40kB198.79kB218.14kB264.94 kВ298.57kB307.13kB343.99kB367.43 kB689.63kB825.23 kB1,402.70 kB• built in 28.48s[plugin builtin:vite-reporter](!) Some chunksare larger than 500 kB after minification. Consider:- Using dynamic import() to code-split the application- Use build.rolldown0ptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/Output0ptions.codeSplitting- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-[API_KEY]) $U* Review screenp...gzip:9.38kBgz1p:10.18kBgzip:9.58kB9z1p:10.62kB9z1p:14.97kBgzip:12.70kBgzip:12.69kBgzip:14.34kBgz1p:16.46kBgzip:15.07kBgz1p:13.28kBgzip:20.09kBgzip:18.88kB9z1p:21.90kB921p:22.96kBgzip:22.64kBgzip:27.46kBgzip:gzip:28.16kB33.78kBgzip:38.71 kBgz1p:34.16 kBgzip:40.05kBgzip:36.72 kBgzip:43.03 kB9z1p:52.24kB9z1p:56.15kBgzip:67.85kBgzip:61.85kBgz1p:64.16kBgzip:60.31kB9z1p:77.21kBgz1p:103.86kBgzip:84.90kB9z1p:97.05kBgzip: 202.81kBgzip:72.53-kBgzip: 438.08 kB• [EMAIL]:73.94kBmap:93.18kBmap:78.74kBmap:115.18kBmap:173.20 kBmap:138.34kBmap:150.73kBmap:150.62kBmap:294.48kBmap:153.25kBmap:65..85kBmap:239.59kBmap:219.27kBmap:201.62kBmap:244.72kBmap:300.68kBmap:3,452.35kBmap:292.79kBmap:308.10kBmap:500.60 kBmap:258.56kBmap:410.48kBmap:266.15kBmap:516.67kBmap:831..82kBmap:623.43kBmap:836.88kBmap:684.87kBmap:1,108.20kBmap:475.61kBmap:959.96kBmap:1,245.28kBmap:849.05kBmap:792.41kBmap:3,016.64kBmap:436.62kBmaр: 6,283.55kBAPP...
|
NULL
|
-7096262296817934594
|
NULL
|
click
|
ocr
|
NULL
|
FirefoxFileEditViewHistoryBookmarksProfilesToolsWi FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelpalol• Support Daily - in 26 mlAPP (-zsh)X4-zsh100% <47• ₴78Fri 17 Apr 14:34:511₴81ec2-user@ip-10-...• 88DOCKER• ₴1DEV (docker)• *2APP (-zsh)X3-zsh./public/vue-assets/assets/ondemand-DutwAJ6x.js:./public/vue-assets/assets/CrmLink-DKYsnHnx.js./public/vue-assets/assets/liquor-tree-COUefof4.js./public/vue-assets/assets/DealRiskList-CjPoT6NF.js:./public/vue-assets/assets/AskAnything-a4BpUaF5.js:./public/vue-assets/assets/lib-CwM9toD2.js./public/vue-assets/assets/AppFormField-CtfldUq7.js:./public/vue-assets/assets/deal-view-DhouIWLw.js../public/vue-assets/assets/exports-D1lmea40.js../public/vue-assets/assets/playlists-BWns-By9.js../public/vue-assets/assets/callScoringTemplates-zeRn40ul.js../public/vue-assets/assets/_copy0bject-USkOnlaQ.js../public/vue-assets/assets/pusher-znYCfz7U.js./public/vue-assets/assets/onboard-BENebgvU.js./public/vue-assets/assets/StatusBadge-CmcY3nfX.js./public/vue-assets/assets/kiosk-1vhk4_89.js./public/vue-assets/assets/preload-helper-DCvhahzG.js../public/vue-assets/assets/deal-insights-DN-kVyxK.js../public/vue-assets/assets/ListView-POU6dSu7.js:./public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js./public/vue-assets/assets/WelcomeLayout-B6wd32HG.js../public/vue-assets/assets/dashboard-DYH5MiIH.js:./public/vue-assets/assets/emoji-input-CSq87OVy.js../public/vue-assets/assets/AppButton-D3qMd0Dr.js../public/vue-assets/assets/sentry-c0Rhilsu.js:./public/vue-assets/assets/OrgSettingsLayout-ByQjX4wG.js./public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js./public/vue-assets/assets/playback-BH9MB7YC.js./public/vue-assets/assets/index.module-Bjlhgfdl.js./public/vue-assets/assets/intl-tel-input-BW4mv40Q.js./public/vue-assets/assets/team-insights-BqCZQtVc.js../public/vue-assets/assets/popper-CQwVcrX4.js../public/vue-assets/assets/PhoneField-CwCIoAYm.js./public/vue-assets/assets/live-XL9ZmWsU.js./public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BN0485xV.js../public/vue-assets/assets/index-CPycMtZF.js../public/vue-assets/assets/logged-in-layout-B_JDhk5y.js• ₴526.88kB27.91kB30.75kB34.39kB39.50kB39.69kB41.91kB43.22kB47.84kB48.28kB55.13kB61.28kB62.98kB63.26kB64.66kB79.60kB82.59kB94.84kB115.71kB117.59 kВ120.67kB128.71 kB129.28 kB133.44 kB164.28kB176.33kB180.40kB198.79kB218.14kB264.94 kВ298.57kB307.13kB343.99kB367.43 kB689.63kB825.23 kB1,402.70 kB• built in 28.48s[plugin builtin:vite-reporter](!) Some chunksare larger than 500 kB after minification. Consider:- Using dynamic import() to code-split the application- Use build.rolldown0ptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/Output0ptions.codeSplitting- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-[API_KEY]) $U* Review screenp...gzip:9.38kBgz1p:10.18kBgzip:9.58kB9z1p:10.62kB9z1p:14.97kBgzip:12.70kBgzip:12.69kBgzip:14.34kBgz1p:16.46kBgzip:15.07kBgz1p:13.28kBgzip:20.09kBgzip:18.88kB9z1p:21.90kB921p:22.96kBgzip:22.64kBgzip:27.46kBgzip:gzip:28.16kB33.78kBgzip:38.71 kBgz1p:34.16 kBgzip:40.05kBgzip:36.72 kBgzip:43.03 kB9z1p:52.24kB9z1p:56.15kBgzip:67.85kBgzip:61.85kBgz1p:64.16kBgzip:60.31kB9z1p:77.21kBgz1p:103.86kBgzip:84.90kB9z1p:97.05kBgzip: 202.81kBgzip:72.53-kBgzip: 438.08 kB• [EMAIL]:73.94kBmap:93.18kBmap:78.74kBmap:115.18kBmap:173.20 kBmap:138.34kBmap:150.73kBmap:150.62kBmap:294.48kBmap:153.25kBmap:65..85kBmap:239.59kBmap:219.27kBmap:201.62kBmap:244.72kBmap:300.68kBmap:3,452.35kBmap:292.79kBmap:308.10kBmap:500.60 kBmap:258.56kBmap:410.48kBmap:266.15kBmap:516.67kBmap:831..82kBmap:623.43kBmap:836.88kBmap:684.87kBmap:1,108.20kBmap:475.61kBmap:959.96kBmap:1,245.28kBmap:849.05kBmap:792.41kBmap:3,016.64kBmap:436.62kBmaр: 6,283.55kBAPP...
|
NULL
|
|
47328
|
NULL
|
0
|
2026-04-17T11:29:18.631308+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776425358631_m2.jpg...
|
Firefox
|
Auth Proxy — Work
|
True
|
docs.getmembrane.com/reference/auth-proxy
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Jiminny · Membrane
console.getmembrane.com
Workers Jiminny · Membrane
console.getmembrane.com
Workers | Datadog
Developers | HubSpot
Developers | HubSpot
Inbox (1,576) - [EMAIL] - Jiminny Mail
Inbox (1,576) - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
CloudWatch | eu-west-1
CloudWatch | eu-west-1
New Tab
New Tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
[JY-20692] Issue with reconnecting Zoho - Jira
[JY-20692] Issue with reconnecting Zoho - Jira
Jiminny
Jiminny
Auth Proxy
Auth Proxy
Close tab
Jiminny · Membrane
Jiminny · Membrane
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Jump to Content
Jump to Content
Membrane Docs
Docs
Docs
API Reference
API Reference
Search ⌘k
Search
Log In
Log In
JUMP TO
OVERVIEW
OVERVIEW
Authentication
Authentication
Element Selectors
Element Selectors
API Errors
API Errors
WORKSPACE ELEMENTS
WORKSPACE ELEMENTS
Connections Show subpages for Connections
Connections
Show subpages for Connections
List connections GET
List connections
GET
Get connection GET
Get connection
GET
Create connection POST
Create connection
POST
Patch connection PATCH
Patch connection
PATCH
Update connection PUT
Update connection
PUT
Test connection POST
Test connection
POST
Refresh connection credentials POST
Refresh connection credentials
POST
Get connection logs GET
Get connection logs
GET
Get connection dependencies GET
Get connection dependencies
GET
Export connection GET
Export connection
GET
Restore connection POST
Restore connection
POST
Archive connection DEL...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"Jiminny · Membrane","depth":4,"bounds":{"left":0.09804688,"top":0.37430555,"width":0.04296875,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"console.getmembrane.com","depth":4,"bounds":{"left":0.09804688,"top":0.38402778,"width":0.05546875,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"bounds":{"left":0.00234375,"top":0.045138888,"width":0.0890625,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Developers | HubSpot","depth":4,"bounds":{"left":0.0,"top":0.08263889,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Developers | HubSpot","depth":5,"bounds":{"left":0.015625,"top":0.09236111,"width":0.04453125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Inbox (1,576) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.11111111,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox (1,576) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"bounds":{"left":0.015625,"top":0.12083333,"width":0.11445312,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"120216 is your HubSpot Log In Code - integration-account@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.13958333,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"120216 is your HubSpot Log In Code - integration-account@jiminny.com - Jiminny Mail","depth":5,"bounds":{"left":0.015625,"top":0.14930555,"width":0.17734376,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | eu-west-1","depth":4,"bounds":{"left":0.0,"top":0.16805555,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | eu-west-1","depth":5,"bounds":{"left":0.015625,"top":0.17777778,"width":0.048828125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.19652778,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.015625,"top":0.20625,"width":0.017578125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.225,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":5,"bounds":{"left":0.015625,"top":0.23472223,"width":0.1515625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.2534722,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app","depth":5,"bounds":{"left":0.015625,"top":0.26319444,"width":0.17421874,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20692] Issue with reconnecting Zoho - Jira","depth":4,"bounds":{"left":0.0,"top":0.28194445,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20692] Issue with reconnecting Zoho - Jira","depth":5,"bounds":{"left":0.015625,"top":0.29166666,"width":0.09726562,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.31041667,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.015625,"top":0.3201389,"width":0.015625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Auth Proxy","depth":4,"bounds":{"left":0.0,"top":0.33888888,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Auth Proxy","depth":5,"bounds":{"left":0.015625,"top":0.34861112,"width":0.021875,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.07890625,"top":0.34513888,"width":0.009375,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny · Membrane","depth":4,"bounds":{"left":0.0,"top":0.3673611,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny · Membrane","depth":5,"bounds":{"left":0.015625,"top":0.37708333,"width":0.041015625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.07890625,"top":0.37361112,"width":0.009375,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.003125,"top":0.39722222,"width":0.08710937,"height":0.022222223},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.003125,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.01640625,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.029296875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.0421875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.05546875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Jump to Content","depth":11,"bounds":{"left":0.09765625,"top":0.052083332,"width":0.048828125,"height":0.020833334},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jump to Content","depth":12,"bounds":{"left":0.1,"top":0.056944445,"width":0.044140626,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Membrane Docs","depth":11,"bounds":{"left":0.1015625,"top":0.048611112,"width":0.05078125,"height":0.027083334},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":" Docs","depth":11,"bounds":{"left":0.16015625,"top":0.048611112,"width":0.02421875,"height":0.027083334},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":13,"bounds":{"left":0.16132812,"top":0.056944445,"width":0.00625,"height":0.011111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Docs","depth":13,"bounds":{"left":0.16992188,"top":0.05625,"width":0.01328125,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":" API Reference","depth":11,"bounds":{"left":0.1921875,"top":0.048611112,"width":0.048046876,"height":0.027083334},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":13,"bounds":{"left":0.19335938,"top":0.056944445,"width":0.00625,"height":0.011111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"API Reference","depth":13,"bounds":{"left":0.20195313,"top":0.05625,"width":0.037109375,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search ⌘k","depth":10,"bounds":{"left":0.90117186,"top":0.052083332,"width":0.05859375,"height":0.020833334},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":11,"bounds":{"left":0.903125,"top":0.056944445,"width":0.00625,"height":0.011111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Search","depth":12,"bounds":{"left":0.91132814,"top":0.05625,"width":0.017578125,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Log In","depth":10,"bounds":{"left":0.96367186,"top":0.052083332,"width":0.024609376,"height":0.020833334},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Log In","depth":11,"bounds":{"left":0.96796876,"top":0.05625,"width":0.016015625,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"JUMP TO","depth":10,"bounds":{"left":0.099609375,"top":0.097222224,"width":0.09765625,"height":0.020833334},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false},{"role":"AXHeading","text":"OVERVIEW","depth":11,"bounds":{"left":0.099609375,"top":0.14166667,"width":0.09765625,"height":0.011111111},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"OVERVIEW","depth":12,"bounds":{"left":0.103515625,"top":0.1423611,"width":0.02578125,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Authentication","depth":13,"bounds":{"left":0.099609375,"top":0.16041666,"width":0.09765625,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Authentication","depth":15,"bounds":{"left":0.103515625,"top":0.16597222,"width":0.037109375,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Element Selectors","depth":13,"bounds":{"left":0.099609375,"top":0.18333334,"width":0.09765625,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Element Selectors","depth":15,"bounds":{"left":0.103515625,"top":0.18888889,"width":0.04609375,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"API Errors","depth":13,"bounds":{"left":0.099609375,"top":0.20625,"width":0.09765625,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"API Errors","depth":15,"bounds":{"left":0.103515625,"top":0.21180555,"width":0.025390625,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"WORKSPACE ELEMENTS","depth":11,"bounds":{"left":0.099609375,"top":0.24930556,"width":0.09765625,"height":0.011111111},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WORKSPACE ELEMENTS","depth":12,"bounds":{"left":0.103515625,"top":0.25,"width":0.058203124,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Connections Show subpages for Connections","depth":13,"bounds":{"left":0.099609375,"top":0.26805556,"width":0.09765625,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Connections","depth":15,"bounds":{"left":0.103515625,"top":0.2736111,"width":0.03203125,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show subpages for Connections","depth":14,"bounds":{"left":0.1890625,"top":0.2736111,"width":0.00625,"height":0.011111111},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"List connections GET","depth":15,"bounds":{"left":0.103515625,"top":0.28541666,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"List connections","depth":17,"bounds":{"left":0.107421875,"top":0.29097223,"width":0.041796874,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"bounds":{"left":0.18359375,"top":0.2923611,"width":0.0078125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Get connection GET","depth":15,"bounds":{"left":0.103515625,"top":0.30833334,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Get connection","depth":17,"bounds":{"left":0.107421875,"top":0.31388888,"width":0.0390625,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"bounds":{"left":0.18359375,"top":0.31527779,"width":0.0078125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Create connection POST","depth":15,"bounds":{"left":0.103515625,"top":0.33125,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create connection","depth":17,"bounds":{"left":0.107421875,"top":0.33680555,"width":0.046875,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST","depth":16,"bounds":{"left":0.18203124,"top":0.33819443,"width":0.0109375,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Patch connection PATCH","depth":15,"bounds":{"left":0.103515625,"top":0.35416666,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Patch connection","depth":17,"bounds":{"left":0.107421875,"top":0.35972223,"width":0.04453125,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PATCH","depth":16,"bounds":{"left":0.18085937,"top":0.3611111,"width":0.01328125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Update connection PUT","depth":15,"bounds":{"left":0.103515625,"top":0.37708333,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Update connection","depth":17,"bounds":{"left":0.107421875,"top":0.3826389,"width":0.0484375,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PUT","depth":16,"bounds":{"left":0.18320313,"top":0.38402778,"width":0.00859375,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Test connection POST","depth":15,"bounds":{"left":0.103515625,"top":0.4,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Test connection","depth":17,"bounds":{"left":0.107421875,"top":0.40555555,"width":0.040625,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST","depth":16,"bounds":{"left":0.18203124,"top":0.40694445,"width":0.0109375,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Refresh connection credentials POST","depth":15,"bounds":{"left":0.103515625,"top":0.42291668,"width":0.09375,"height":0.036111113},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Refresh connection credentials","depth":17,"bounds":{"left":0.107421875,"top":0.42847222,"width":0.049609374,"height":0.025694445},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST","depth":16,"bounds":{"left":0.18203124,"top":0.4298611,"width":0.0109375,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Get connection logs GET","depth":15,"bounds":{"left":0.103515625,"top":0.45972222,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Get connection logs","depth":17,"bounds":{"left":0.107421875,"top":0.4652778,"width":0.051171876,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"bounds":{"left":0.18359375,"top":0.46666667,"width":0.0078125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Get connection dependencies GET","depth":15,"bounds":{"left":0.103515625,"top":0.4826389,"width":0.09375,"height":0.036111113},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Get connection dependencies","depth":17,"bounds":{"left":0.107421875,"top":0.48819444,"width":0.0390625,"height":0.025694445},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"bounds":{"left":0.18359375,"top":0.48958334,"width":0.0078125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Export connection GET","depth":15,"bounds":{"left":0.103515625,"top":0.51944447,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Export connection","depth":17,"bounds":{"left":0.107421875,"top":0.525,"width":0.046484374,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"bounds":{"left":0.18359375,"top":0.5263889,"width":0.0078125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Restore connection POST","depth":15,"bounds":{"left":0.103515625,"top":0.54236114,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Restore connection","depth":17,"bounds":{"left":0.107421875,"top":0.54791665,"width":0.049609374,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST","depth":16,"bounds":{"left":0.18203124,"top":0.54930556,"width":0.0109375,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Archive connection DEL","depth":15,"bounds":{"left":0.103515625,"top":0.56527776,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-6410721714148244440
|
-2824534269259367860
|
idle
|
accessibility
|
NULL
|
Jiminny · Membrane
console.getmembrane.com
Workers Jiminny · Membrane
console.getmembrane.com
Workers | Datadog
Developers | HubSpot
Developers | HubSpot
Inbox (1,576) - [EMAIL] - Jiminny Mail
Inbox (1,576) - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
CloudWatch | eu-west-1
CloudWatch | eu-west-1
New Tab
New Tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
[JY-20692] Issue with reconnecting Zoho - Jira
[JY-20692] Issue with reconnecting Zoho - Jira
Jiminny
Jiminny
Auth Proxy
Auth Proxy
Close tab
Jiminny · Membrane
Jiminny · Membrane
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Jump to Content
Jump to Content
Membrane Docs
Docs
Docs
API Reference
API Reference
Search ⌘k
Search
Log In
Log In
JUMP TO
OVERVIEW
OVERVIEW
Authentication
Authentication
Element Selectors
Element Selectors
API Errors
API Errors
WORKSPACE ELEMENTS
WORKSPACE ELEMENTS
Connections Show subpages for Connections
Connections
Show subpages for Connections
List connections GET
List connections
GET
Get connection GET
Get connection
GET
Create connection POST
Create connection
POST
Patch connection PATCH
Patch connection
PATCH
Update connection PUT
Update connection
PUT
Test connection POST
Test connection
POST
Refresh connection credentials POST
Refresh connection credentials
POST
Get connection logs GET
Get connection logs
GET
Get connection dependencies GET
Get connection dependencies
GET
Export connection GET
Export connection
GET
Restore connection POST
Restore connection
POST
Archive connection DEL...
|
47326
|
|
47327
|
NULL
|
0
|
2026-04-17T11:29:10.966876+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776425350966_m1.jpg...
|
Firefox
|
Auth Proxy — Work
|
True
|
docs.getmembrane.com/reference/auth-proxy
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Jiminny · Membrane
console.getmembrane.com
Workers Jiminny · Membrane
console.getmembrane.com
Workers | Datadog
Developers | HubSpot
Developers | HubSpot
Inbox (1,576) - [EMAIL] - Jiminny Mail
Inbox (1,576) - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
CloudWatch | eu-west-1
CloudWatch | eu-west-1
New Tab
New Tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
[JY-20692] Issue with reconnecting Zoho - Jira
[JY-20692] Issue with reconnecting Zoho - Jira
Jiminny
Jiminny
Auth Proxy
Auth Proxy
Close tab
Jiminny · Membrane
Jiminny · Membrane
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Jump to Content
Jump to Content
Membrane Docs
Docs
Docs
API Reference
API Reference
Search ⌘k
Search
Log In
Log In
JUMP TO
OVERVIEW
OVERVIEW
Authentication
Authentication
Element Selectors
Element Selectors
API Errors
API Errors
WORKSPACE ELEMENTS
WORKSPACE ELEMENTS
Connections Show subpages for Connections
Connections
Show subpages for Connections
List connections GET
List connections
GET
Get connection GET
Get connection
GET
Create connection POST
Create connection
POST
Patch connection PATCH
Patch connection
PATCH
Update connection PUT
Update connection
PUT
Test connection POST
Test connection
POST
Refresh connection credentials POST
Refresh connection credentials
POST
Get connection logs GET
Get connection logs
GET
Get connection dependencies GET
Get connection dependencies
GET
Export connection GET
Export connection
GET
Restore connection POST
Restore connection...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"Jiminny · Membrane","depth":4,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"console.getmembrane.com","depth":4,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Developers | HubSpot","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Developers | HubSpot","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Inbox (1,576) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox (1,576) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"120216 is your HubSpot Log In Code - integration-account@jiminny.com - Jiminny Mail","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"120216 is your HubSpot Log In Code - integration-account@jiminny.com - Jiminny Mail","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | eu-west-1","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | eu-west-1","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20692] Issue with reconnecting Zoho - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20692] Issue with reconnecting Zoho - Jira","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Auth Proxy","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Auth Proxy","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny · Membrane","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny · Membrane","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Jump to Content","depth":11,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jump to Content","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Membrane Docs","depth":11,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":" Docs","depth":11,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Docs","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":" API Reference","depth":11,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"API Reference","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search ⌘k","depth":10,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Search","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Log In","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Log In","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"JUMP TO","depth":10,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false},{"role":"AXHeading","text":"OVERVIEW","depth":11,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"OVERVIEW","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Authentication","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Authentication","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Element Selectors","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Element Selectors","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"API Errors","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"API Errors","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"WORKSPACE ELEMENTS","depth":11,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WORKSPACE ELEMENTS","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Connections Show subpages for Connections","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Connections","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show subpages for Connections","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"List connections GET","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"List connections","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Get connection GET","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Get connection","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Create connection POST","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create connection","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Patch connection PATCH","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Patch connection","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PATCH","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Update connection PUT","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Update connection","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PUT","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Test connection POST","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Test connection","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Refresh connection credentials POST","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Refresh connection credentials","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Get connection logs GET","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Get connection logs","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Get connection dependencies GET","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Get connection dependencies","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Export connection GET","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Export connection","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Restore connection POST","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Restore connection","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
4438693934993446109
|
6398556292618697292
|
idle
|
accessibility
|
NULL
|
Jiminny · Membrane
console.getmembrane.com
Workers Jiminny · Membrane
console.getmembrane.com
Workers | Datadog
Developers | HubSpot
Developers | HubSpot
Inbox (1,576) - [EMAIL] - Jiminny Mail
Inbox (1,576) - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
CloudWatch | eu-west-1
CloudWatch | eu-west-1
New Tab
New Tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
[JY-20692] Issue with reconnecting Zoho - Jira
[JY-20692] Issue with reconnecting Zoho - Jira
Jiminny
Jiminny
Auth Proxy
Auth Proxy
Close tab
Jiminny · Membrane
Jiminny · Membrane
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Jump to Content
Jump to Content
Membrane Docs
Docs
Docs
API Reference
API Reference
Search ⌘k
Search
Log In
Log In
JUMP TO
OVERVIEW
OVERVIEW
Authentication
Authentication
Element Selectors
Element Selectors
API Errors
API Errors
WORKSPACE ELEMENTS
WORKSPACE ELEMENTS
Connections Show subpages for Connections
Connections
Show subpages for Connections
List connections GET
List connections
GET
Get connection GET
Get connection
GET
Create connection POST
Create connection
POST
Patch connection PATCH
Patch connection
PATCH
Update connection PUT
Update connection
PUT
Test connection POST
Test connection
POST
Refresh connection credentials POST
Refresh connection credentials
POST
Get connection logs GET
Get connection logs
GET
Get connection dependencies GET
Get connection dependencies
GET
Export connection GET
Export connection
GET
Restore connection POST
Restore connection...
|
47325
|
|
47310
|
NULL
|
0
|
2026-04-17T11:24:44.812934+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776425084812_m2.jpg...
|
Firefox
|
Auth Proxy — Work
|
True
|
docs.getmembrane.com/reference/auth-proxy
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Jiminny · Membrane
console.getmembrane.com
Workers Jiminny · Membrane
console.getmembrane.com
Workers | Datadog
Developers | HubSpot
Developers | HubSpot
Inbox (1,576) - [EMAIL] - Jiminny Mail
Inbox (1,576) - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
CloudWatch | eu-west-1
CloudWatch | eu-west-1
New Tab
New Tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
[JY-20692] Issue with reconnecting Zoho - Jira
[JY-20692] Issue with reconnecting Zoho - Jira
Jiminny
Jiminny
Auth Proxy
Auth Proxy
Close tab
Jiminny · Membrane
Jiminny · Membrane
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Jump to Content
Jump to Content
Membrane Docs
Docs
Docs
API Reference
API Reference
Search ⌘k
Search
Log In
Log In
JUMP TO
OVERVIEW
OVERVIEW
Authentication
Authentication
Element Selectors
Element Selectors
API Errors
API Errors
WORKSPACE ELEMENTS
WORKSPACE ELEMENTS
Connections Show subpages for Connections
Connections
Show subpages for Connections
List connections GET
List connections
GET
Get connection GET
Get connection
GET
Create connection POST
Create connection
POST
Patch connection PATCH
Patch connection
PATCH
Update connection PUT
Update connection
PUT
Test connection POST
Test connection
POST
Refresh connection credentials POST
Refresh connection credentials
POST
Get connection logs GET
Get connection logs
GET
Get connection dependencies GET
Get connection dependencies
GET
Export connection GET
Export connection
GET
Restore connection POST
Restore connection
POST
Archive connection DEL
Archive connection
DEL
Connectors Hide subpages for Connectors
Connectors
Hide subpages for Connectors
Connector Types Hide subpages for Connector Types
Connector Types
Hide subpages for Connector Types
Client Credentials
Client Credentials
Membrane Token
Membrane Token
OAuth1
OAuth1
OAuth2
OAuth2
Auth Proxy
Auth Proxy
Connector Functions Show subpages for Connector Functions
Connector Functions
Show subpages for Connector Functions
Get Credentials from Connection Parameters
Get Credentials from Connection Parameters
Make API Client
Make API Client
Refresh Credentials
Refresh Credentials
Test
Test
Disconnect
Disconnect
Universal Data Models
Universal Data Models
List connectors GET
List connectors
GET
List public connectors GET
List public connectors
GET
Get connector GET
Get connector
GET
Create connector POST
Create connector
POST
Patch connector PATCH
Patch connector
PATCH
Delete connector DEL
Delete connector
DEL
Download connector GET
Download connector
GET
Upload connector POST
Upload connector
POST
Import connector POST
Import connector
POST
Clone connector POST
Clone connector
POST
Get connector versions GET
Get connector versions
GET
Publish connector version POST
Publish connector version
POST
Update connector completely PUT
Update connector completely
PUT
Get a list of connector files GET
Get a list of connector files
GET
Get connector file by path GET
Get connector file by path
GET
Update connector file PUT
Update connector file
PUT
Delete connector file DEL
Delete connector file
DEL
Delete connector directory DEL
Delete connector directory
DEL
Export connector as zip file GET
Export connector as zip file
GET
Export connector as JSON GET
Export connector as JSON
GET
Integrations Show subpages for Integrations
Integrations
Show subpages for Integrations...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"Jiminny · Membrane","depth":4,"bounds":{"left":0.09804688,"top":0.37430555,"width":0.04296875,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"console.getmembrane.com","depth":4,"bounds":{"left":0.09804688,"top":0.38402778,"width":0.05546875,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"bounds":{"left":0.00234375,"top":0.045138888,"width":0.0890625,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Developers | HubSpot","depth":4,"bounds":{"left":0.0,"top":0.08263889,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Developers | HubSpot","depth":5,"bounds":{"left":0.015625,"top":0.09236111,"width":0.04453125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Inbox (1,576) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.11111111,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox (1,576) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"bounds":{"left":0.015625,"top":0.12083333,"width":0.11445312,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"120216 is your HubSpot Log In Code - integration-account@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.13958333,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"120216 is your HubSpot Log In Code - integration-account@jiminny.com - Jiminny Mail","depth":5,"bounds":{"left":0.015625,"top":0.14930555,"width":0.17734376,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | eu-west-1","depth":4,"bounds":{"left":0.0,"top":0.16805555,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | eu-west-1","depth":5,"bounds":{"left":0.015625,"top":0.17777778,"width":0.048828125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.19652778,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.015625,"top":0.20625,"width":0.017578125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.225,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":5,"bounds":{"left":0.015625,"top":0.23472223,"width":0.1515625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.2534722,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app","depth":5,"bounds":{"left":0.015625,"top":0.26319444,"width":0.17421874,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20692] Issue with reconnecting Zoho - Jira","depth":4,"bounds":{"left":0.0,"top":0.28194445,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20692] Issue with reconnecting Zoho - Jira","depth":5,"bounds":{"left":0.015625,"top":0.29166666,"width":0.09726562,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.31041667,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.015625,"top":0.3201389,"width":0.015625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Auth Proxy","depth":4,"bounds":{"left":0.0,"top":0.33888888,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Auth Proxy","depth":5,"bounds":{"left":0.015625,"top":0.34861112,"width":0.021875,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.07890625,"top":0.34513888,"width":0.009375,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny · Membrane","depth":4,"bounds":{"left":0.0,"top":0.3673611,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny · Membrane","depth":5,"bounds":{"left":0.015625,"top":0.37708333,"width":0.041015625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.07890625,"top":0.37361112,"width":0.009375,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.003125,"top":0.39722222,"width":0.08710937,"height":0.022222223},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.003125,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.01640625,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.029296875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.0421875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.05546875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Jump to Content","depth":11,"bounds":{"left":0.09765625,"top":0.052083332,"width":0.048828125,"height":0.020833334},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jump to Content","depth":12,"bounds":{"left":0.1,"top":0.056944445,"width":0.044140626,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Membrane Docs","depth":11,"bounds":{"left":0.1015625,"top":0.048611112,"width":0.05078125,"height":0.027083334},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":" Docs","depth":11,"bounds":{"left":0.16015625,"top":0.048611112,"width":0.02421875,"height":0.027083334},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":13,"bounds":{"left":0.16132812,"top":0.056944445,"width":0.00625,"height":0.011111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Docs","depth":13,"bounds":{"left":0.16992188,"top":0.05625,"width":0.01328125,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":" API Reference","depth":11,"bounds":{"left":0.1921875,"top":0.048611112,"width":0.048046876,"height":0.027083334},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":13,"bounds":{"left":0.19335938,"top":0.056944445,"width":0.00625,"height":0.011111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"API Reference","depth":13,"bounds":{"left":0.20195313,"top":0.05625,"width":0.037109375,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search ⌘k","depth":10,"bounds":{"left":0.90117186,"top":0.052083332,"width":0.05859375,"height":0.020833334},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":11,"bounds":{"left":0.903125,"top":0.056944445,"width":0.00625,"height":0.011111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Search","depth":12,"bounds":{"left":0.91132814,"top":0.05625,"width":0.017578125,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Log In","depth":10,"bounds":{"left":0.96367186,"top":0.052083332,"width":0.024609376,"height":0.020833334},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Log In","depth":11,"bounds":{"left":0.96796876,"top":0.05625,"width":0.016015625,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"JUMP TO","depth":10,"bounds":{"left":0.099609375,"top":0.097222224,"width":0.09765625,"height":0.020833334},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false},{"role":"AXHeading","text":"OVERVIEW","depth":11,"bounds":{"left":0.099609375,"top":0.14166667,"width":0.09765625,"height":0.011111111},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"OVERVIEW","depth":12,"bounds":{"left":0.103515625,"top":0.1423611,"width":0.02578125,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Authentication","depth":13,"bounds":{"left":0.099609375,"top":0.16041666,"width":0.09765625,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Authentication","depth":15,"bounds":{"left":0.103515625,"top":0.16597222,"width":0.037109375,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Element Selectors","depth":13,"bounds":{"left":0.099609375,"top":0.18333334,"width":0.09765625,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Element Selectors","depth":15,"bounds":{"left":0.103515625,"top":0.18888889,"width":0.04609375,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"API Errors","depth":13,"bounds":{"left":0.099609375,"top":0.20625,"width":0.09765625,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"API Errors","depth":15,"bounds":{"left":0.103515625,"top":0.21180555,"width":0.025390625,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"WORKSPACE ELEMENTS","depth":11,"bounds":{"left":0.099609375,"top":0.24930556,"width":0.09765625,"height":0.011111111},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WORKSPACE ELEMENTS","depth":12,"bounds":{"left":0.103515625,"top":0.25,"width":0.058203124,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Connections Show subpages for Connections","depth":13,"bounds":{"left":0.099609375,"top":0.26805556,"width":0.09765625,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Connections","depth":15,"bounds":{"left":0.103515625,"top":0.2736111,"width":0.03203125,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show subpages for Connections","depth":14,"bounds":{"left":0.1890625,"top":0.2736111,"width":0.00625,"height":0.011111111},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"List connections GET","depth":15,"bounds":{"left":0.103515625,"top":0.28541666,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"List connections","depth":17,"bounds":{"left":0.107421875,"top":0.29097223,"width":0.041796874,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"bounds":{"left":0.18359375,"top":0.2923611,"width":0.0078125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Get connection GET","depth":15,"bounds":{"left":0.103515625,"top":0.30833334,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Get connection","depth":17,"bounds":{"left":0.107421875,"top":0.31388888,"width":0.0390625,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"bounds":{"left":0.18359375,"top":0.31527779,"width":0.0078125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Create connection POST","depth":15,"bounds":{"left":0.103515625,"top":0.33125,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create connection","depth":17,"bounds":{"left":0.107421875,"top":0.33680555,"width":0.046875,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST","depth":16,"bounds":{"left":0.18203124,"top":0.33819443,"width":0.0109375,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Patch connection PATCH","depth":15,"bounds":{"left":0.103515625,"top":0.35416666,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Patch connection","depth":17,"bounds":{"left":0.107421875,"top":0.35972223,"width":0.04453125,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PATCH","depth":16,"bounds":{"left":0.18085937,"top":0.3611111,"width":0.01328125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Update connection PUT","depth":15,"bounds":{"left":0.103515625,"top":0.37708333,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Update connection","depth":17,"bounds":{"left":0.107421875,"top":0.3826389,"width":0.0484375,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PUT","depth":16,"bounds":{"left":0.18320313,"top":0.38402778,"width":0.00859375,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Test connection POST","depth":15,"bounds":{"left":0.103515625,"top":0.4,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Test connection","depth":17,"bounds":{"left":0.107421875,"top":0.40555555,"width":0.040625,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST","depth":16,"bounds":{"left":0.18203124,"top":0.40694445,"width":0.0109375,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Refresh connection credentials POST","depth":15,"bounds":{"left":0.103515625,"top":0.42291668,"width":0.09375,"height":0.036111113},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Refresh connection credentials","depth":17,"bounds":{"left":0.107421875,"top":0.42847222,"width":0.049609374,"height":0.025694445},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST","depth":16,"bounds":{"left":0.18203124,"top":0.4298611,"width":0.0109375,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Get connection logs GET","depth":15,"bounds":{"left":0.103515625,"top":0.45972222,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Get connection logs","depth":17,"bounds":{"left":0.107421875,"top":0.4652778,"width":0.051171876,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"bounds":{"left":0.18359375,"top":0.46666667,"width":0.0078125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Get connection dependencies GET","depth":15,"bounds":{"left":0.103515625,"top":0.4826389,"width":0.09375,"height":0.036111113},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Get connection dependencies","depth":17,"bounds":{"left":0.107421875,"top":0.48819444,"width":0.0390625,"height":0.025694445},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"bounds":{"left":0.18359375,"top":0.48958334,"width":0.0078125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Export connection GET","depth":15,"bounds":{"left":0.103515625,"top":0.51944447,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Export connection","depth":17,"bounds":{"left":0.107421875,"top":0.525,"width":0.046484374,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"bounds":{"left":0.18359375,"top":0.5263889,"width":0.0078125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Restore connection POST","depth":15,"bounds":{"left":0.103515625,"top":0.54236114,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Restore connection","depth":17,"bounds":{"left":0.107421875,"top":0.54791665,"width":0.049609374,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST","depth":16,"bounds":{"left":0.18203124,"top":0.54930556,"width":0.0109375,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Archive connection DEL","depth":15,"bounds":{"left":0.103515625,"top":0.56527776,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Archive connection","depth":17,"bounds":{"left":0.107421875,"top":0.5708333,"width":0.04921875,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"DEL","depth":16,"bounds":{"left":0.18359375,"top":0.57222223,"width":0.0078125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Connectors Hide subpages for Connectors","depth":13,"bounds":{"left":0.099609375,"top":0.29097223,"width":0.09765625,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Connectors","depth":15,"bounds":{"left":0.103515625,"top":0.29652777,"width":0.0296875,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Hide subpages for Connectors","depth":14,"bounds":{"left":0.1890625,"top":0.29652777,"width":0.00625,"height":0.011111111},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXLink","text":"Connector Types Hide subpages for Connector Types","depth":15,"bounds":{"left":0.103515625,"top":0.31388888,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Connector Types","depth":17,"bounds":{"left":0.107421875,"top":0.31944445,"width":0.04375,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Hide subpages for Connector Types","depth":16,"bounds":{"left":0.1890625,"top":0.31944445,"width":0.00625,"height":0.011111111},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXLink","text":"Client Credentials","depth":17,"bounds":{"left":0.107421875,"top":0.33680555,"width":0.08984375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Client Credentials","depth":19,"bounds":{"left":0.111328125,"top":0.34236112,"width":0.0453125,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Membrane Token","depth":17,"bounds":{"left":0.107421875,"top":0.35972223,"width":0.08984375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Membrane Token","depth":19,"bounds":{"left":0.111328125,"top":0.36527777,"width":0.044140626,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"OAuth1","depth":17,"bounds":{"left":0.107421875,"top":0.3826389,"width":0.08984375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"OAuth1","depth":19,"bounds":{"left":0.111328125,"top":0.38819444,"width":0.018359374,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"OAuth2","depth":17,"bounds":{"left":0.107421875,"top":0.40555555,"width":0.08984375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"OAuth2","depth":19,"bounds":{"left":0.111328125,"top":0.41111112,"width":0.019140625,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Auth Proxy","depth":17,"bounds":{"left":0.107421875,"top":0.42847222,"width":0.08984375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Auth Proxy","depth":19,"bounds":{"left":0.111328125,"top":0.4340278,"width":0.027734375,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Connector Functions Show subpages for Connector Functions","depth":15,"bounds":{"left":0.103515625,"top":0.45277777,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Connector Functions","depth":17,"bounds":{"left":0.107421875,"top":0.45833334,"width":0.053515624,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show subpages for Connector Functions","depth":16,"bounds":{"left":0.1890625,"top":0.45833334,"width":0.00625,"height":0.011111111},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Get Credentials from Connection Parameters","depth":17,"bounds":{"left":0.107421875,"top":0.47013888,"width":0.08984375,"height":0.036111113},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Get Credentials from Connection Parameters","depth":19,"bounds":{"left":0.111328125,"top":0.47569445,"width":0.083984375,"height":0.025694445},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Make API Client","depth":17,"bounds":{"left":0.107421875,"top":0.5069444,"width":0.08984375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Make API Client","depth":19,"bounds":{"left":0.111328125,"top":0.5125,"width":0.040234376,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Refresh Credentials","depth":17,"bounds":{"left":0.107421875,"top":0.5298611,"width":0.08984375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Refresh Credentials","depth":19,"bounds":{"left":0.111328125,"top":0.53541666,"width":0.050390624,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Test","depth":17,"bounds":{"left":0.107421875,"top":0.55277777,"width":0.08984375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Test","depth":19,"bounds":{"left":0.111328125,"top":0.55833334,"width":0.0109375,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Disconnect","depth":17,"bounds":{"left":0.107421875,"top":0.57569444,"width":0.08984375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Disconnect","depth":19,"bounds":{"left":0.111328125,"top":0.58125,"width":0.02890625,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Universal Data Models","depth":15,"bounds":{"left":0.103515625,"top":0.47569445,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Universal Data Models","depth":17,"bounds":{"left":0.107421875,"top":0.48125,"width":0.05703125,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"List connectors GET","depth":15,"bounds":{"left":0.103515625,"top":0.49861112,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"List connectors","depth":17,"bounds":{"left":0.107421875,"top":0.50416666,"width":0.039453126,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"bounds":{"left":0.18359375,"top":0.50555557,"width":0.0078125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"List public connectors GET","depth":15,"bounds":{"left":0.103515625,"top":0.52152777,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"List public connectors","depth":17,"bounds":{"left":0.107421875,"top":0.52708334,"width":0.056640625,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"bounds":{"left":0.18359375,"top":0.52847224,"width":0.0078125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Get connector GET","depth":15,"bounds":{"left":0.103515625,"top":0.54444444,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Get connector","depth":17,"bounds":{"left":0.107421875,"top":0.55,"width":0.03671875,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"bounds":{"left":0.18359375,"top":0.55138886,"width":0.0078125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Create connector POST","depth":15,"bounds":{"left":0.103515625,"top":0.5673611,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create connector","depth":17,"bounds":{"left":0.107421875,"top":0.5729167,"width":0.04453125,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST","depth":16,"bounds":{"left":0.18203124,"top":0.57430553,"width":0.0109375,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Patch connector PATCH","depth":15,"bounds":{"left":0.103515625,"top":0.5902778,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Patch connector","depth":17,"bounds":{"left":0.107421875,"top":0.59583336,"width":0.041796874,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PATCH","depth":16,"bounds":{"left":0.18085937,"top":0.5972222,"width":0.01328125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Delete connector DEL","depth":15,"bounds":{"left":0.103515625,"top":0.61319447,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Delete connector","depth":17,"bounds":{"left":0.107421875,"top":0.61875,"width":0.044140626,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"DEL","depth":16,"bounds":{"left":0.18359375,"top":0.6201389,"width":0.0078125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Download connector GET","depth":15,"bounds":{"left":0.103515625,"top":0.63611114,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Download connector","depth":17,"bounds":{"left":0.107421875,"top":0.64166665,"width":0.052734375,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"bounds":{"left":0.18359375,"top":0.64305556,"width":0.0078125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Upload connector POST","depth":15,"bounds":{"left":0.103515625,"top":0.65902776,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Upload connector","depth":17,"bounds":{"left":0.107421875,"top":0.6645833,"width":0.045703124,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST","depth":16,"bounds":{"left":0.18203124,"top":0.66597223,"width":0.0109375,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Import connector POST","depth":15,"bounds":{"left":0.103515625,"top":0.68194443,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Import connector","depth":17,"bounds":{"left":0.107421875,"top":0.6875,"width":0.044140626,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST","depth":16,"bounds":{"left":0.18203124,"top":0.6888889,"width":0.0109375,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Clone connector POST","depth":15,"bounds":{"left":0.103515625,"top":0.7048611,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Clone connector","depth":17,"bounds":{"left":0.107421875,"top":0.7104167,"width":0.0421875,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST","depth":16,"bounds":{"left":0.18203124,"top":0.7118056,"width":0.0109375,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Get connector versions GET","depth":15,"bounds":{"left":0.103515625,"top":0.7277778,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Get connector versions","depth":17,"bounds":{"left":0.107421875,"top":0.73333335,"width":0.059375,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"bounds":{"left":0.18359375,"top":0.7347222,"width":0.0078125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Publish connector version POST","depth":15,"bounds":{"left":0.103515625,"top":0.75069445,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Publish connector version","depth":17,"bounds":{"left":0.107421875,"top":0.75625,"width":0.06601562,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST","depth":16,"bounds":{"left":0.18203124,"top":0.7576389,"width":0.0109375,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Update connector completely PUT","depth":15,"bounds":{"left":0.103515625,"top":0.7736111,"width":0.09375,"height":0.036111113},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Update connector completely","depth":17,"bounds":{"left":0.107421875,"top":0.77916664,"width":0.04609375,"height":0.025694445},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PUT","depth":16,"bounds":{"left":0.18320313,"top":0.78055555,"width":0.00859375,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Get a list of connector files GET","depth":15,"bounds":{"left":0.103515625,"top":0.81041664,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Get a list of connector files","depth":17,"bounds":{"left":0.107421875,"top":0.8159722,"width":0.06875,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"bounds":{"left":0.18359375,"top":0.8173611,"width":0.0078125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Get connector file by path GET","depth":15,"bounds":{"left":0.103515625,"top":0.8333333,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Get connector file by path","depth":17,"bounds":{"left":0.107421875,"top":0.8388889,"width":0.06640625,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"bounds":{"left":0.18359375,"top":0.8402778,"width":0.0078125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Update connector file PUT","depth":15,"bounds":{"left":0.103515625,"top":0.85625,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Update connector file","depth":17,"bounds":{"left":0.107421875,"top":0.86180556,"width":0.05546875,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PUT","depth":16,"bounds":{"left":0.18320313,"top":0.86319447,"width":0.00859375,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Delete connector file DEL","depth":15,"bounds":{"left":0.103515625,"top":0.87916666,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Delete connector file","depth":17,"bounds":{"left":0.107421875,"top":0.88472223,"width":0.053125,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"DEL","depth":16,"bounds":{"left":0.18359375,"top":0.88611114,"width":0.0078125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Delete connector directory DEL","depth":15,"bounds":{"left":0.103515625,"top":0.90208334,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Delete connector directory","depth":17,"bounds":{"left":0.107421875,"top":0.9076389,"width":0.068359375,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"DEL","depth":16,"bounds":{"left":0.18359375,"top":0.90902776,"width":0.0078125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Export connector as zip file GET","depth":15,"bounds":{"left":0.103515625,"top":0.925,"width":0.09375,"height":0.036111113},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Export connector as zip file","depth":17,"bounds":{"left":0.107421875,"top":0.9305556,"width":0.060546875,"height":0.025694445},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"bounds":{"left":0.18359375,"top":0.93194443,"width":0.0078125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Export connector as JSON GET","depth":15,"bounds":{"left":0.103515625,"top":0.9618056,"width":0.09375,"height":0.022222223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Export connector as JSON","depth":17,"bounds":{"left":0.107421875,"top":0.9673611,"width":0.06757812,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"bounds":{"left":0.18359375,"top":0.96875,"width":0.0078125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Integrations Show subpages for Integrations","depth":13,"bounds":{"left":0.099609375,"top":0.9861111,"width":0.09765625,"height":0.0138888955},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Integrations","depth":15,"bounds":{"left":0.103515625,"top":0.9916667,"width":0.03046875,"height":0.008333325},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show subpages for Integrations","depth":14,"bounds":{"left":0.1890625,"top":0.9916667,"width":0.00625,"height":0.008333325},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-2946507518247493010
|
-446809511585367476
|
idle
|
accessibility
|
NULL
|
Jiminny · Membrane
console.getmembrane.com
Workers Jiminny · Membrane
console.getmembrane.com
Workers | Datadog
Developers | HubSpot
Developers | HubSpot
Inbox (1,576) - [EMAIL] - Jiminny Mail
Inbox (1,576) - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
CloudWatch | eu-west-1
CloudWatch | eu-west-1
New Tab
New Tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
[JY-20692] Issue with reconnecting Zoho - Jira
[JY-20692] Issue with reconnecting Zoho - Jira
Jiminny
Jiminny
Auth Proxy
Auth Proxy
Close tab
Jiminny · Membrane
Jiminny · Membrane
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Jump to Content
Jump to Content
Membrane Docs
Docs
Docs
API Reference
API Reference
Search ⌘k
Search
Log In
Log In
JUMP TO
OVERVIEW
OVERVIEW
Authentication
Authentication
Element Selectors
Element Selectors
API Errors
API Errors
WORKSPACE ELEMENTS
WORKSPACE ELEMENTS
Connections Show subpages for Connections
Connections
Show subpages for Connections
List connections GET
List connections
GET
Get connection GET
Get connection
GET
Create connection POST
Create connection
POST
Patch connection PATCH
Patch connection
PATCH
Update connection PUT
Update connection
PUT
Test connection POST
Test connection
POST
Refresh connection credentials POST
Refresh connection credentials
POST
Get connection logs GET
Get connection logs
GET
Get connection dependencies GET
Get connection dependencies
GET
Export connection GET
Export connection
GET
Restore connection POST
Restore connection
POST
Archive connection DEL
Archive connection
DEL
Connectors Hide subpages for Connectors
Connectors
Hide subpages for Connectors
Connector Types Hide subpages for Connector Types
Connector Types
Hide subpages for Connector Types
Client Credentials
Client Credentials
Membrane Token
Membrane Token
OAuth1
OAuth1
OAuth2
OAuth2
Auth Proxy
Auth Proxy
Connector Functions Show subpages for Connector Functions
Connector Functions
Show subpages for Connector Functions
Get Credentials from Connection Parameters
Get Credentials from Connection Parameters
Make API Client
Make API Client
Refresh Credentials
Refresh Credentials
Test
Test
Disconnect
Disconnect
Universal Data Models
Universal Data Models
List connectors GET
List connectors
GET
List public connectors GET
List public connectors
GET
Get connector GET
Get connector
GET
Create connector POST
Create connector
POST
Patch connector PATCH
Patch connector
PATCH
Delete connector DEL
Delete connector
DEL
Download connector GET
Download connector
GET
Upload connector POST
Upload connector
POST
Import connector POST
Import connector
POST
Clone connector POST
Clone connector
POST
Get connector versions GET
Get connector versions
GET
Publish connector version POST
Publish connector version
POST
Update connector completely PUT
Update connector completely
PUT
Get a list of connector files GET
Get a list of connector files
GET
Get connector file by path GET
Get connector file by path
GET
Update connector file PUT
Update connector file
PUT
Delete connector file DEL
Delete connector file
DEL
Delete connector directory DEL
Delete connector directory
DEL
Export connector as zip file GET
Export connector as zip file
GET
Export connector as JSON GET
Export connector as JSON
GET
Integrations Show subpages for Integrations
Integrations
Show subpages for Integrations...
|
NULL
|
|
47309
|
NULL
|
0
|
2026-04-17T11:24:38.628802+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776425078628_m1.jpg...
|
Firefox
|
Auth Proxy — Work
|
True
|
docs.getmembrane.com/reference/auth-proxy
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Jiminny · Membrane
console.getmembrane.com
Workers Jiminny · Membrane
console.getmembrane.com
Workers | Datadog
Developers | HubSpot
Developers | HubSpot
Inbox (1,576) - [EMAIL] - Jiminny Mail
Inbox (1,576) - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
CloudWatch | eu-west-1
CloudWatch | eu-west-1
New Tab
New Tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
[JY-20692] Issue with reconnecting Zoho - Jira
[JY-20692] Issue with reconnecting Zoho - Jira
Jiminny
Jiminny
Auth Proxy
Auth Proxy
Close tab
Jiminny · Membrane
Jiminny · Membrane
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Jump to Content
Jump to Content
Membrane Docs
Docs
Docs
API Reference
API Reference
Search ⌘k
Search
Log In
Log In
JUMP TO
OVERVIEW
OVERVIEW
Authentication
Authentication
Element Selectors
Element Selectors
API Errors
API Errors
WORKSPACE ELEMENTS
WORKSPACE ELEMENTS
Connections Show subpages for Connections
Connections
Show subpages for Connections
List connections GET
List connections
GET
Get connection GET
Get connection
GET
Create connection POST
Create connection
POST
Patch connection PATCH
Patch connection
PATCH
Update connection PUT
Update connection
PUT
Test connection POST
Test connection
POST
Refresh connection credentials POST
Refresh connection credentials
POST
Get connection logs GET
Get connection logs
GET
Get connection dependencies GET
Get connection dependencies
GET
Export connection GET
Export connection
GET
Restore connection POST
Restore connection
POST
Archive connection DEL
Archive connection
DEL
Connectors Hide subpages for Connectors
Connectors
Hide subpages for Connectors
Connector Types Hide subpages for Connector Types
Connector Types
Hide subpages for Connector Types
Client Credentials
Client Credentials
Membrane Token
Membrane Token
OAuth1
OAuth1
OAuth2
OAuth2
Auth Proxy
Auth Proxy
Connector Functions Show subpages for Connector Functions
Connector Functions
Show subpages for Connector Functions
Get Credentials from Connection Parameters
Get Credentials from Connection Parameters
Make API Client
Make API Client
Refresh Credentials
Refresh Credentials
Test
Test
Disconnect
Disconnect
Universal Data Models
Universal Data Models
List connectors GET
List connectors
GET
List public connectors GET...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"Jiminny · Membrane","depth":4,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"console.getmembrane.com","depth":4,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Developers | HubSpot","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Developers | HubSpot","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Inbox (1,576) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox (1,576) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"120216 is your HubSpot Log In Code - integration-account@jiminny.com - Jiminny Mail","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"120216 is your HubSpot Log In Code - integration-account@jiminny.com - Jiminny Mail","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | eu-west-1","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | eu-west-1","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20692] Issue with reconnecting Zoho - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20692] Issue with reconnecting Zoho - Jira","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Auth Proxy","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Auth Proxy","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny · Membrane","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny · Membrane","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Jump to Content","depth":11,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jump to Content","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Membrane Docs","depth":11,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":" Docs","depth":11,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Docs","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":" API Reference","depth":11,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"API Reference","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search ⌘k","depth":10,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Search","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Log In","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Log In","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"JUMP TO","depth":10,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false},{"role":"AXHeading","text":"OVERVIEW","depth":11,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"OVERVIEW","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Authentication","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Authentication","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Element Selectors","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Element Selectors","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"API Errors","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"API Errors","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"WORKSPACE ELEMENTS","depth":11,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WORKSPACE ELEMENTS","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Connections Show subpages for Connections","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Connections","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show subpages for Connections","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"List connections GET","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"List connections","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Get connection GET","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Get connection","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Create connection POST","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create connection","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Patch connection PATCH","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Patch connection","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PATCH","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Update connection PUT","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Update connection","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PUT","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Test connection POST","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Test connection","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Refresh connection credentials POST","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Refresh connection credentials","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Get connection logs GET","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Get connection logs","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Get connection dependencies GET","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Get connection dependencies","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Export connection GET","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Export connection","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Restore connection POST","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Restore connection","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Archive connection DEL","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Archive connection","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"DEL","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Connectors Hide subpages for Connectors","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Connectors","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Hide subpages for Connectors","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXLink","text":"Connector Types Hide subpages for Connector Types","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Connector Types","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Hide subpages for Connector Types","depth":16,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXLink","text":"Client Credentials","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Client Credentials","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Membrane Token","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Membrane Token","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"OAuth1","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"OAuth1","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"OAuth2","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"OAuth2","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Auth Proxy","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Auth Proxy","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Connector Functions Show subpages for Connector Functions","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Connector Functions","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show subpages for Connector Functions","depth":16,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Get Credentials from Connection Parameters","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Get Credentials from Connection Parameters","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Make API Client","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Make API Client","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Refresh Credentials","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Refresh Credentials","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Test","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Test","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Disconnect","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Disconnect","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Universal Data Models","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Universal Data Models","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"List connectors GET","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"List connectors","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"List public connectors GET","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-5567825423644768547
|
1859033972171518540
|
idle
|
accessibility
|
NULL
|
Jiminny · Membrane
console.getmembrane.com
Workers Jiminny · Membrane
console.getmembrane.com
Workers | Datadog
Developers | HubSpot
Developers | HubSpot
Inbox (1,576) - [EMAIL] - Jiminny Mail
Inbox (1,576) - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
CloudWatch | eu-west-1
CloudWatch | eu-west-1
New Tab
New Tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
[JY-20692] Issue with reconnecting Zoho - Jira
[JY-20692] Issue with reconnecting Zoho - Jira
Jiminny
Jiminny
Auth Proxy
Auth Proxy
Close tab
Jiminny · Membrane
Jiminny · Membrane
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Jump to Content
Jump to Content
Membrane Docs
Docs
Docs
API Reference
API Reference
Search ⌘k
Search
Log In
Log In
JUMP TO
OVERVIEW
OVERVIEW
Authentication
Authentication
Element Selectors
Element Selectors
API Errors
API Errors
WORKSPACE ELEMENTS
WORKSPACE ELEMENTS
Connections Show subpages for Connections
Connections
Show subpages for Connections
List connections GET
List connections
GET
Get connection GET
Get connection
GET
Create connection POST
Create connection
POST
Patch connection PATCH
Patch connection
PATCH
Update connection PUT
Update connection
PUT
Test connection POST
Test connection
POST
Refresh connection credentials POST
Refresh connection credentials
POST
Get connection logs GET
Get connection logs
GET
Get connection dependencies GET
Get connection dependencies
GET
Export connection GET
Export connection
GET
Restore connection POST
Restore connection
POST
Archive connection DEL
Archive connection
DEL
Connectors Hide subpages for Connectors
Connectors
Hide subpages for Connectors
Connector Types Hide subpages for Connector Types
Connector Types
Hide subpages for Connector Types
Client Credentials
Client Credentials
Membrane Token
Membrane Token
OAuth1
OAuth1
OAuth2
OAuth2
Auth Proxy
Auth Proxy
Connector Functions Show subpages for Connector Functions
Connector Functions
Show subpages for Connector Functions
Get Credentials from Connection Parameters
Get Credentials from Connection Parameters
Make API Client
Make API Client
Refresh Credentials
Refresh Credentials
Test
Test
Disconnect
Disconnect
Universal Data Models
Universal Data Models
List connectors GET
List connectors
GET
List public connectors GET...
|
NULL
|
|
47288
|
NULL
|
0
|
2026-04-17T11:19:09.692253+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776424749692_m2.jpg...
|
NULL
|
NULL
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Firefox FileEoitViewHistory Bookmarks Profiles T Firefox FileEoitViewHistory Bookmarks Profiles Tools Window HelpDevelopers | HubSpotM'inbox (1,576) - lukas.kovalik@jiminM° 120216 is your HubSpot Log in CocFal CloudWatch | eu-west-1New Tabz Configure SSH access to multiple. fix-cache-for-business-processes4 [JY-20692] Issue with reconnectir(8) JiminnyAuth ProxyMembrahe-= docs.getmembrane.com/reference/auth-proxyMembraneM Docs<> API ReferenceJUMPTOOVERVIEWHutnenuicalionElement SelectorsAPl ErrorsWORKSPACE ELEMENISConnectionsConnectorsConnector TypesCent CredentialsMembraneconsole.getmemorane.com+ New TabC< 40 ll O SupportDaily - in 41m100% ( Fri 17 Apr 14:19:09=Q SearchLog InWORKSPACE ELEMENTS > CONNECTORS > CONNECTOR TYPESAuth Proxy•† Ask Al vAuth Proxy lets you use OAuth credentials provided by Membrane without registering your own OAuth app.You typically don't need to use this authentication type yourself, but you may find it in pre-builtconnectors.When using Auth Proxy, you will not have access to the connection credentials.Example connector definition:YAML# Connector definition: Use Membrane-provided OAuth credentials (no custom OAuth appпесасалtype: auth-proxyproxyKey: auth-proxy-key@ Updated 30 days ago< OAuth2Connector Functions →List connectorsGETOList public connectorsGet connectorCreate connectorPatch connectorDelete connectorDownload connectorUpload connectorImport connectorClone connectorGet connector versionsPublish connector versionUpdate connectorcompletelyGet a list of connector filesGet connector file by pathUpdate connector fileDelete connector fileDelete connector directoryExport connector as zip GETIfileExport connector as JSON GETDid this page help you? & Yes INo...
|
NULL
|
8253081986799999724
|
NULL
|
visual_change
|
ocr
|
NULL
|
Firefox FileEoitViewHistory Bookmarks Profiles T Firefox FileEoitViewHistory Bookmarks Profiles Tools Window HelpDevelopers | HubSpotM'inbox (1,576) - lukas.kovalik@jiminM° 120216 is your HubSpot Log in CocFal CloudWatch | eu-west-1New Tabz Configure SSH access to multiple. fix-cache-for-business-processes4 [JY-20692] Issue with reconnectir(8) JiminnyAuth ProxyMembrahe-= docs.getmembrane.com/reference/auth-proxyMembraneM Docs<> API ReferenceJUMPTOOVERVIEWHutnenuicalionElement SelectorsAPl ErrorsWORKSPACE ELEMENISConnectionsConnectorsConnector TypesCent CredentialsMembraneconsole.getmemorane.com+ New TabC< 40 ll O SupportDaily - in 41m100% ( Fri 17 Apr 14:19:09=Q SearchLog InWORKSPACE ELEMENTS > CONNECTORS > CONNECTOR TYPESAuth Proxy•† Ask Al vAuth Proxy lets you use OAuth credentials provided by Membrane without registering your own OAuth app.You typically don't need to use this authentication type yourself, but you may find it in pre-builtconnectors.When using Auth Proxy, you will not have access to the connection credentials.Example connector definition:YAML# Connector definition: Use Membrane-provided OAuth credentials (no custom OAuth appпесасалtype: auth-proxyproxyKey: auth-proxy-key@ Updated 30 days ago< OAuth2Connector Functions →List connectorsGETOList public connectorsGet connectorCreate connectorPatch connectorDelete connectorDownload connectorUpload connectorImport connectorClone connectorGet connector versionsPublish connector versionUpdate connectorcompletelyGet a list of connector filesGet connector file by pathUpdate connector fileDelete connector fileDelete connector directoryExport connector as zip GETIfileExport connector as JSON GETDid this page help you? & Yes INo...
|
47287
|
|
47285
|
NULL
|
0
|
2026-04-17T11:19:05.901113+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776424745901_m1.jpg...
|
Firefox
|
Auth Proxy — Work
|
True
|
docs.getmembrane.com/reference/auth-proxy
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Developers | HubSpot
Developers Workers | Datadog
Developers | HubSpot
Developers | HubSpot
Inbox (1,576) - [EMAIL] - Jiminny Mail
Inbox (1,576) - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
CloudWatch | eu-west-1
CloudWatch | eu-west-1
New Tab
New Tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
[JY-20692] Issue with reconnecting Zoho - Jira
[JY-20692] Issue with reconnecting Zoho - Jira
Jiminny
Jiminny
Auth Proxy
Auth Proxy
Close tab
Membrane
Membrane
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Jump to Content
Jump to Content
Membrane Docs
Docs
Docs
API Reference
API Reference
Search ⌘k
Search
Log In
Log In
JUMP TO
OVERVIEW
OVERVIEW
Authentication
Authentication
Element Selectors
Element Selectors
API Errors
API Errors
WORKSPACE ELEMENTS
WORKSPACE ELEMENTS
Connections Show subpages for Connections
Connections
Show subpages for Connections
List connections GET
List connections
GET
Get connection GET
Get connection
GET
Create connection POST
Create connection
POST
Patch connection PATCH
Patch connection
PATCH
Update connection PUT
Update connection
PUT
Test connection POST
Test connection
POST
Refresh connection credentials POST
Refresh connection credentials
POST
Get connection logs GET
Get connection logs
GET
Get connection dependencies GET
Get connection dependencies
GET
Export connection GET
Export connection
GET
Restore connection POST
Restore connection
POST
Archive connection DEL
Archive connection
DEL
Connectors Hide subpages for Connectors
Connectors
Hide subpages for Connectors
Connector Types Hide subpages for Connector Types
Connector Types
Hide subpages for Connector Types
Client Credentials
Client Credentials
Membrane Token
Membrane Token
OAuth1...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Developers | HubSpot","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Developers | HubSpot","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Inbox (1,576) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox (1,576) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"120216 is your HubSpot Log In Code - integration-account@jiminny.com - Jiminny Mail","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"120216 is your HubSpot Log In Code - integration-account@jiminny.com - Jiminny Mail","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | eu-west-1","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | eu-west-1","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20692] Issue with reconnecting Zoho - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20692] Issue with reconnecting Zoho - Jira","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Auth Proxy","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Auth Proxy","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Membrane","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Membrane","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Jump to Content","depth":11,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jump to Content","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Membrane Docs","depth":11,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":" Docs","depth":11,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Docs","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":" API Reference","depth":11,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"API Reference","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search ⌘k","depth":10,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Search","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Log In","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Log In","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"JUMP TO","depth":10,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false},{"role":"AXHeading","text":"OVERVIEW","depth":11,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"OVERVIEW","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Authentication","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Authentication","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Element Selectors","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Element Selectors","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"API Errors","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"API Errors","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"WORKSPACE ELEMENTS","depth":11,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"WORKSPACE ELEMENTS","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Connections Show subpages for Connections","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Connections","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show subpages for Connections","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"List connections GET","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"List connections","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Get connection GET","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Get connection","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Create connection POST","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create connection","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Patch connection PATCH","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Patch connection","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PATCH","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Update connection PUT","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Update connection","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PUT","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Test connection POST","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Test connection","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Refresh connection credentials POST","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Refresh connection credentials","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Get connection logs GET","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Get connection logs","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Get connection dependencies GET","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Get connection dependencies","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Export connection GET","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Export connection","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Restore connection POST","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Restore connection","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Archive connection DEL","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Archive connection","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"DEL","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Connectors Hide subpages for Connectors","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Connectors","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Hide subpages for Connectors","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXLink","text":"Connector Types Hide subpages for Connector Types","depth":15,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Connector Types","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Hide subpages for Connector Types","depth":16,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXLink","text":"Client Credentials","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Client Credentials","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Membrane Token","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Membrane Token","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"OAuth1","depth":17,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-1880352834673245291
|
-7454551344340616596
|
click
|
accessibility
|
NULL
|
Workers | Datadog
Developers | HubSpot
Developers Workers | Datadog
Developers | HubSpot
Developers | HubSpot
Inbox (1,576) - [EMAIL] - Jiminny Mail
Inbox (1,576) - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
CloudWatch | eu-west-1
CloudWatch | eu-west-1
New Tab
New Tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
[JY-20692] Issue with reconnecting Zoho - Jira
[JY-20692] Issue with reconnecting Zoho - Jira
Jiminny
Jiminny
Auth Proxy
Auth Proxy
Close tab
Membrane
Membrane
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Jump to Content
Jump to Content
Membrane Docs
Docs
Docs
API Reference
API Reference
Search ⌘k
Search
Log In
Log In
JUMP TO
OVERVIEW
OVERVIEW
Authentication
Authentication
Element Selectors
Element Selectors
API Errors
API Errors
WORKSPACE ELEMENTS
WORKSPACE ELEMENTS
Connections Show subpages for Connections
Connections
Show subpages for Connections
List connections GET
List connections
GET
Get connection GET
Get connection
GET
Create connection POST
Create connection
POST
Patch connection PATCH
Patch connection
PATCH
Update connection PUT
Update connection
PUT
Test connection POST
Test connection
POST
Refresh connection credentials POST
Refresh connection credentials
POST
Get connection logs GET
Get connection logs
GET
Get connection dependencies GET
Get connection dependencies
GET
Export connection GET
Export connection
GET
Restore connection POST
Restore connection
POST
Archive connection DEL
Archive connection
DEL
Connectors Hide subpages for Connectors
Connectors
Hide subpages for Connectors
Connector Types Hide subpages for Connector Types
Connector Types
Hide subpages for Connector Types
Client Credentials
Client Credentials
Membrane Token
Membrane Token
OAuth1...
|
47282
|
|
47201
|
NULL
|
0
|
2026-04-17T11:14:00.766529+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776424440766_m2.jpg...
|
Firefox
|
Jiminny — Work
|
True
|
app.dev.jiminny.com/onboard
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Developers | HubSpot
Developers Workers | Datadog
Developers | HubSpot
Developers | HubSpot
Inbox (1,576) - [EMAIL] - Jiminny Mail
Inbox (1,576) - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
CloudWatch | eu-west-1
CloudWatch | eu-west-1
New Tab
New Tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
[JY-20692] Issue with reconnecting Zoho - Jira
[JY-20692] Issue with reconnecting Zoho - Jira
Jiminny
Jiminny
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Update your information
Update your information
GENERAL
TIMEZONE
Select option Europe/Sofia (UTC +03:00)
Select option
Europe/Sofia (UTC +03:00)
*
LANGUAGES SPOKEN DURING CALLS
DEFAULT SPOKEN LANGUAGE
Select option English (United Kingdom)
Select option
English (United Kingdom)
*
If the language isn't detected we'll default to this one
Add language
CONNECT/SYNC SETTINGS
Connect Zoho CRM
zohocrm Sign in with Zoho CRM
Sign in with Zoho CRM
Import Calendar Meetings
*
google Sign in with Google
Sign in with Google
Let's Get Started!
Zoho CRM Zoho CRM
Zoho CRM
Linking your Zoho CRM account
Linking your Zoho CRM account
Please select one of authentication options:
Connect via Membrane
Connect via Membrane
Connect via Membrane
OAuth 2.0
OAuth 2.0
OAuth 2.0
Close Dialog
Clear the Web Console output (⌘K, Ctrl+L)
Integr
Integr
230 hidden
Clear filter input
Errors
Warnings
Info
Logs
Debug
CSS
XHR
Requests
Console Settings
Navigated to https://app.dev.jiminny.com/dashboard
Show/hide message details.
XHR
GET
https://app.dev.jiminny.com/api/v1/integration-app-token
https://app.dev.jiminny.com/api/v1/integration-app-token
[HTTP/2
200
1450ms]
Show/hide message details.
GET
https://ui.integration.app/embed/integrations/zohocrm/connect?token=[JWT_TOKEN]&showPoweredBy=false&allowMultipleConnections=
https://ui.integration.app/embed/integrations/zohocrm/connect?token=[JWT_TOKEN]&showPoweredBy=false&allowMultipleConnections=
[HTTP/2
200
226ms]
Show/hide message details.
GET
https://ui.integration.app/assets/index-DQ2tm1dS.css
https://ui.integration.app/assets/index-DQ2tm1dS.css
[HTTP/3
200
0ms]
Show/hide message details.
GET
https://ui.integration.app/assets/index-DCou__JW.js
https://ui.integration.app/assets/index-DCou__JW.js
[HTTP/3
200
0ms]
None of the “sha384” hashes in the integrity attribute match the content of the subresource at “
https://fonts.googleapis.com/css2?family=IBM+Plex+Serif&display=swap
https://fonts.googleapis.com/css2?family=IBM+Plex+Serif&display=swap
”. The computed hash is “oxpr/SPifeVqNx5O/1ow9nS0QIt60XJIKkUcSrPclwH/ruMEWK7C1JNqlqUMCV5N”.
connect
connect
Show/hide message details.
GET
https://ui.integration.app/assets/RefreshConnectionPopup-P6RvdGCd.js
https://ui.integration.app/assets/RefreshConnectionPopup-P6RvdGCd.js
[HTTP/3
200
0ms]
Show/hide message details.
GET
https://ui.integration.app/assets/index-DRKPh6L6.js
https://ui.integration.app/assets/index-DRKPh6L6.js
[HTTP/3
200
0ms]
Show/hide message details.
GET
https://ui.integration.app/assets/RefreshConnectionPopup-_TEQhDXU.css
https://ui.integration.app/assets/RefreshConnectionPopup-_TEQhDXU.css
[HTTP/3
200
0ms]
Show/hide message details.
GET
https://ui.integration.app/assets/IntegrationConnectionGuard-CgFvoH9G.js
https://ui.integration.app/assets/IntegrationConnectionGuard-CgFvoH9G.js
[HTTP/3
200
0ms]
Show/hide message details.
GET
https://ui.integration.app/assets/index-7Ka1N6pQ.js
https://ui.integration.app/assets/index-7Ka1N6pQ.js
[HTTP/3
200
0ms]
Show/hide message details.
GET
https://ui.integration.app/assets/Routes-CDWw4D3f.js
https://ui.integration.app/assets/Routes-CDWw4D3f.js
[HTTP/3
200
0ms]
Show/hide message details.
GET
https://ui.integration.app/assets/index-Dg8yQbmF.js
https://ui.integration.app/assets/index-Dg8yQbmF.js
[HTTP/3
200
0ms]
Show/hide message details.
GET
https://ui.integration.app/assets/ResetButton-B5vGAh3h.js
https://ui.integration.app/assets/ResetButton-B5vGAh3h.js
[HTTP/3
200
0ms]
Show/hide message details.
GET
https://ui.integration.app/assets/flow-instance-context-CJr_s6BC.js
https://ui.integration.app/assets/flow-instance-context-CJr_s6BC.js
[HTTP/3
200
0ms]
Show/hide message details.
GET
https://ui.integration.app/assets/index-BhMG5VAy.css
https://ui.integration.app/assets/index-BhMG5VAy.css
[HTTP/3
200
0ms]
Show/hide message details....
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"bounds":{"left":0.00234375,"top":0.045138888,"width":0.0890625,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Developers | HubSpot","depth":4,"bounds":{"left":0.0,"top":0.08263889,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Developers | HubSpot","depth":5,"bounds":{"left":0.015625,"top":0.09236111,"width":0.04453125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Inbox (1,576) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.11111111,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox (1,576) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"bounds":{"left":0.015625,"top":0.12083333,"width":0.11445312,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"120216 is your HubSpot Log In Code - integration-account@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.13958333,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"120216 is your HubSpot Log In Code - integration-account@jiminny.com - Jiminny Mail","depth":5,"bounds":{"left":0.015625,"top":0.14930555,"width":0.17734376,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | eu-west-1","depth":4,"bounds":{"left":0.0,"top":0.16805555,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | eu-west-1","depth":5,"bounds":{"left":0.015625,"top":0.17777778,"width":0.048828125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.19652778,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.015625,"top":0.20625,"width":0.017578125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.225,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":5,"bounds":{"left":0.015625,"top":0.23472223,"width":0.1515625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.2534722,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app","depth":5,"bounds":{"left":0.015625,"top":0.26319444,"width":0.17421874,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20692] Issue with reconnecting Zoho - Jira","depth":4,"bounds":{"left":0.0,"top":0.28194445,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20692] Issue with reconnecting Zoho - Jira","depth":5,"bounds":{"left":0.015625,"top":0.29166666,"width":0.09726562,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.31041667,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.015625,"top":0.3201389,"width":0.015625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.07890625,"top":0.31666666,"width":0.009375,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.003125,"top":0.3402778,"width":0.08710937,"height":0.022222223},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.003125,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.01640625,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.029296875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.0421875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.05546875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Update your information","depth":9,"bounds":{"left":0.29335937,"top":0.3375,"width":0.15273437,"height":0.016666668},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Update your information","depth":10,"bounds":{"left":0.31796876,"top":0.33541667,"width":0.103515625,"height":0.020833334},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GENERAL","depth":10,"bounds":{"left":0.29335937,"top":0.36944443,"width":0.025,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"TIMEZONE","depth":12,"bounds":{"left":0.2984375,"top":0.39722222,"width":0.0203125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Select option Europe/Sofia (UTC +03:00)","depth":10,"bounds":{"left":0.2984375,"top":0.40625,"width":0.14726563,"height":0.017361112},"value":"Select option Europe/Sofia (UTC +03:00)","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Select option","depth":11,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Europe/Sofia (UTC +03:00)","depth":12,"bounds":{"left":0.2984375,"top":0.40902779,"width":0.061328124,"height":0.011111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"*","depth":11,"bounds":{"left":0.44023436,"top":0.3923611,"width":0.003125,"height":0.017361112},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"LANGUAGES SPOKEN DURING CALLS","depth":10,"bounds":{"left":0.29335937,"top":0.44791666,"width":0.09609375,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"DEFAULT SPOKEN LANGUAGE","depth":12,"bounds":{"left":0.2984375,"top":0.47569445,"width":0.05546875,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Select option English (United Kingdom)","depth":10,"bounds":{"left":0.2984375,"top":0.48472223,"width":0.14726563,"height":0.017361112},"value":"Select option English (United Kingdom)","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Select option","depth":11,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"English (United Kingdom)","depth":12,"bounds":{"left":0.2984375,"top":0.4875,"width":0.057421874,"height":0.011111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"*","depth":11,"bounds":{"left":0.44023436,"top":0.47083333,"width":0.003125,"height":0.017361112},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the language isn't detected we'll default to this one","depth":11,"bounds":{"left":0.29335937,"top":0.5076389,"width":0.109375,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add language","depth":11,"bounds":{"left":0.29335937,"top":0.52847224,"width":0.037890624,"height":0.013888889},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CONNECT/SYNC SETTINGS","depth":10,"bounds":{"left":0.29335937,"top":0.56458336,"width":0.06992187,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Connect Zoho CRM","depth":11,"bounds":{"left":0.29335937,"top":0.5902778,"width":0.0546875,"height":0.013888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"zohocrm Sign in with Zoho CRM","depth":10,"bounds":{"left":0.36875,"top":0.5847222,"width":0.07734375,"height":0.025},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sign in with Zoho CRM","depth":11,"bounds":{"left":0.38632813,"top":0.59097224,"width":0.054296874,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Import Calendar Meetings","depth":11,"bounds":{"left":0.29335937,"top":0.62083334,"width":0.045703124,"height":0.027777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"*","depth":12,"bounds":{"left":0.3371094,"top":0.6173611,"width":0.003125,"height":0.017361112},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"google Sign in with Google","depth":10,"bounds":{"left":0.36875,"top":0.62222224,"width":0.07734375,"height":0.025},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sign in with Google","depth":11,"bounds":{"left":0.39023438,"top":0.6284722,"width":0.046484374,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Let's Get Started!","depth":9,"bounds":{"left":0.3347656,"top":0.68333334,"width":0.06992187,"height":0.025},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Zoho CRM Zoho CRM","depth":12,"bounds":{"left":0.16835937,"top":0.17777778,"width":0.20625,"height":0.016666668},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Zoho CRM","depth":13,"bounds":{"left":0.18085937,"top":0.17986111,"width":0.02578125,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Linking your Zoho CRM account","depth":14,"bounds":{"left":0.18046875,"top":0.30069444,"width":0.19453125,"height":0.04236111},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Linking your Zoho CRM account","depth":15,"bounds":{"left":0.18046875,"top":0.31180555,"width":0.134375,"height":0.02013889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please select one of authentication options:","depth":15,"bounds":{"left":0.18046875,"top":0.33541667,"width":0.10625,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Connect via Membrane","depth":13,"bounds":{"left":0.16679688,"top":0.35694444,"width":0.221875,"height":0.033333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Connect via Membrane","depth":15,"bounds":{"left":0.19453125,"top":0.36805555,"width":0.055859376,"height":0.009722223},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Connect via Membrane","depth":16,"bounds":{"left":0.19453125,"top":0.3673611,"width":0.055859376,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"OAuth 2.0","depth":13,"bounds":{"left":0.16679688,"top":0.39097223,"width":0.221875,"height":0.033333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"OAuth 2.0","depth":15,"bounds":{"left":0.19453125,"top":0.40208334,"width":0.023828125,"height":0.009722223},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"OAuth 2.0","depth":16,"bounds":{"left":0.19453125,"top":0.40138888,"width":0.023828125,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close Dialog","depth":12,"bounds":{"left":0.3816406,"top":0.17638889,"width":0.0109375,"height":0.019444445},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false},{"role":"AXButton","text":"Clear the Web Console output (⌘K, Ctrl+L)","depth":15,"bounds":{"left":0.46328124,"top":0.068055555,"width":0.01015625,"height":0.013888889},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXTextField","text":"Integr","depth":15,"bounds":{"left":0.47578126,"top":0.065972224,"width":0.32851562,"height":0.018055556},"value":"Integr","role_description":"search text field","subrole":"AXSearchField","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Integr","depth":16,"bounds":{"left":0.484375,"top":0.07013889,"width":0.012109375,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"230 hidden","depth":16,"bounds":{"left":0.8058594,"top":0.07013889,"width":0.0234375,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Clear filter input","depth":15,"bounds":{"left":0.83085936,"top":0.06944445,"width":0.00625,"height":0.011111111},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Errors","depth":15,"bounds":{"left":0.8417969,"top":0.068055555,"width":0.0171875,"height":0.013888889},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Warnings","depth":15,"bounds":{"left":0.85976565,"top":0.068055555,"width":0.023828125,"height":0.013888889},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Info","depth":15,"bounds":{"left":0.884375,"top":0.068055555,"width":0.0125,"height":0.013888889},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Logs","depth":15,"bounds":{"left":0.89765626,"top":0.068055555,"width":0.014453125,"height":0.013888889},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Debug","depth":15,"bounds":{"left":0.9128906,"top":0.068055555,"width":0.01796875,"height":0.013888889},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"CSS","depth":15,"bounds":{"left":0.9359375,"top":0.068055555,"width":0.01328125,"height":0.013888889},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"XHR","depth":15,"bounds":{"left":0.95,"top":0.068055555,"width":0.013671875,"height":0.013888889},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Requests","depth":15,"bounds":{"left":0.9644531,"top":0.068055555,"width":0.023828125,"height":0.013888889},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Console Settings","depth":15,"bounds":{"left":0.9898437,"top":0.06527778,"width":0.01015625,"height":0.019444445},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Navigated to https://app.dev.jiminny.com/dashboard","depth":20,"bounds":{"left":0.471875,"top":0.0875,"width":0.12929687,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show/hide message details.","depth":17,"bounds":{"left":0.46367186,"top":0.10138889,"width":0.009375,"height":0.011111111},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"XHR","depth":21,"bounds":{"left":0.475,"top":0.10277778,"width":0.00703125,"height":0.009027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":21,"bounds":{"left":0.48476562,"top":0.10208333,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://app.dev.jiminny.com/api/v1/integration-app-token","depth":20,"bounds":{"left":0.4953125,"top":0.10208333,"width":0.4441406,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://app.dev.jiminny.com/api/v1/integration-app-token","depth":21,"bounds":{"left":0.4953125,"top":0.10208333,"width":0.14453125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[HTTP/2","depth":21,"bounds":{"left":0.9421875,"top":0.10208333,"width":0.020703126,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":22,"bounds":{"left":0.96367186,"top":0.10208333,"width":0.007421875,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1450ms]","depth":21,"bounds":{"left":0.971875,"top":0.10208333,"width":0.0234375,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show/hide message details.","depth":17,"bounds":{"left":0.46367186,"top":0.11666667,"width":0.009375,"height":0.011111111},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"GET","depth":21,"bounds":{"left":0.47421876,"top":0.11736111,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://ui.integration.app/embed/integrations/zohocrm/connect?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjFlY2U2NmM4LWZlYjEtNGRmMS1iMzIxLTIxNjA3ZGFmNDYyMyIsIm5hbWUiOiJEZXYgWm9obyBDUk0gY2xpZW50IiwiaXNzIjoiNjg3YTU5ZjctMjI3Ni00ODZjLTgzMDYtMTQ1MDdmZDc5N2FlIiwiZXhwIjoxNzc2NTEwODMyLCJmaWVsZHMiOnsiZW52IjoibG9jYWwiLCJ3ZWJob29rVXJsIjoiaHR0cHM6Ly9xYXRlc3Q6UWFZZU14MS02NDJuYkBsdWthc2submdyb2suaW8iLCJldmVudEFjY291bnREZWxldGVkIjoiL3dlYmhvb2svaW50YXBwL2FjYy1kZWwiLCJldmVudENvbnRhY3REZWxldGVkIjoiL3dlYmhvb2svaW50YXBwL2NudC1kZWwiLCJldmVudFByb2ZpbGVEZWxldGVkIjoiL3dlYmhvb2svaW50YXBwL3Vzci1kZWwiLCJldmVudExlYWREZWxldGVkIjoiL3dlYmhvb2svaW50YXBwL2xkLWRlbCIsImV2ZW50RGVhbERlbGV0ZWQiOiIvd2ViaG9vay9pbnRhcHAvb3BwLWRlbCIsImV2ZW50QWNjb3VudENyZWF0ZWQiOiIvd2ViaG9vay9pbnRhcHAvYWNjLWFkZCIsImV2ZW50QWNjb3VudFVwZGF0ZWQiOiIvd2ViaG9vay9pbnRhcHAvYWNjLXVwZCIsImV2ZW50Q29udGFjdENyZWF0ZWQiOiIvd2ViaG9vay9pbnRhcHAvY250LWFkZCIsImV2ZW50Q29udGFjdFVwZGF0ZWQiOiIvd2ViaG9vay9pbnRhcHAvY250LXVwZCIsImV2ZW50UHJvZmlsZUNyZWF0ZWQiOiIvd2ViaG9vay9pbnRhcHAvdXNyLWFkZCIsImV2ZW50UHJvZmlsZVVwZGF0ZWQiOiIvd2ViaG9vay9pbnRhcHAvdXNyLXVwZCIsImV2ZW50TGVhZENyZWF0ZWQiOiIvd2ViaG9vay9pbnRhcHAvbGQtYWRkIiwiZXZlbnRMZWFkVXBkYXRlZCI6Ii93ZWJob29rL2ludGFwcC9sZC11cGQiLCJldmVudERlYWxDcmVhdGVkIjoiL3dlYmhvb2svaW50YXBwL29wcC1hZGQiLCJldmVudERlYWxVcGRhdGVkIjoiL3dlYmhvb2svaW50YXBwL29wcC11cGQifX0.cPowBZuDxoTPGjFBae4ToiaiHGn8n7ip72tabpSeGsg&showPoweredBy=false&allowMultipleConnections=","depth":20,"bounds":{"left":0.484375,"top":0.11736111,"width":0.4578125,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://ui.integration.app/embed/integrations/zohocrm/connect?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjFlY2U2NmM4LWZlYjEtNGRmMS1iMzIxLTIxNjA3ZGFmNDYyMyIsIm5hbWUiOiJEZXYgWm9obyBDUk0gY2xpZW50IiwiaXNzIjoiNjg3YTU5ZjctMjI3Ni00ODZjLTgzMDYtMTQ1MDdmZDc5N2FlIiwiZXhwIjoxNzc2NTEwODMyLCJmaWVsZHMiOnsiZW52IjoibG9jYWwiLCJ3ZWJob29rVXJsIjoiaHR0cHM6Ly9xYXRlc3Q6UWFZZU14MS02NDJuYkBsdWthc2submdyb2suaW8iLCJldmVudEFjY291bnREZWxldGVkIjoiL3dlYmhvb2svaW50YXBwL2FjYy1kZWwiLCJldmVudENvbnRhY3REZWxldGVkIjoiL3dlYmhvb2svaW50YXBwL2NudC1kZWwiLCJldmVudFByb2ZpbGVEZWxldGVkIjoiL3dlYmhvb2svaW50YXBwL3Vzci1kZWwiLCJldmVudExlYWREZWxldGVkIjoiL3dlYmhvb2svaW50YXBwL2xkLWRlbCIsImV2ZW50RGVhbERlbGV0ZWQiOiIvd2ViaG9vay9pbnRhcHAvb3BwLWRlbCIsImV2ZW50QWNjb3VudENyZWF0ZWQiOiIvd2ViaG9vay9pbnRhcHAvYWNjLWFkZCIsImV2ZW50QWNjb3VudFVwZGF0ZWQiOiIvd2ViaG9vay9pbnRhcHAvYWNjLXVwZCIsImV2ZW50Q29udGFjdENyZWF0ZWQiOiIvd2ViaG9vay9pbnRhcHAvY250LWFkZCIsImV2ZW50Q29udGFjdFVwZGF0ZWQiOiIvd2ViaG9vay9pbnRhcHAvY250LXVwZCIsImV2ZW50UHJvZmlsZUNyZWF0ZWQiOiIvd2ViaG9vay9pbnRhcHAvdXNyLWFkZCIsImV2ZW50UHJvZmlsZVVwZGF0ZWQiOiIvd2ViaG9vay9pbnRhcHAvdXNyLXVwZCIsImV2ZW50TGVhZENyZWF0ZWQiOiIvd2ViaG9vay9pbnRhcHAvbGQtYWRkIiwiZXZlbnRMZWFkVXBkYXRlZCI6Ii93ZWJob29rL2ludGFwcC9sZC11cGQiLCJldmVudERlYWxDcmVhdGVkIjoiL3dlYmhvb2svaW50YXBwL29wcC1hZGQiLCJldmVudERlYWxVcGRhdGVkIjoiL3dlYmhvb2svaW50YXBwL29wcC11cGQifX0.cPowBZuDxoTPGjFBae4ToiaiHGn8n7ip72tabpSeGsg&showPoweredBy=false&allowMultipleConnections=","depth":21,"bounds":{"left":0.484375,"top":0.11736111,"width":0.515625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[HTTP/2","depth":21,"bounds":{"left":0.94453126,"top":0.11736111,"width":0.020703126,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":22,"bounds":{"left":0.96601564,"top":0.11736111,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"226ms]","depth":21,"bounds":{"left":0.9746094,"top":0.11736111,"width":0.020703126,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show/hide message details.","depth":17,"bounds":{"left":0.46367186,"top":0.13125,"width":0.009375,"height":0.011111111},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"GET","depth":21,"bounds":{"left":0.47421876,"top":0.13194445,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://ui.integration.app/assets/index-DQ2tm1dS.css","depth":20,"bounds":{"left":0.484375,"top":0.13194445,"width":0.46289062,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://ui.integration.app/assets/index-DQ2tm1dS.css","depth":21,"bounds":{"left":0.484375,"top":0.13194445,"width":0.134375,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[HTTP/3","depth":21,"bounds":{"left":0.9496094,"top":0.13194445,"width":0.020703126,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":22,"bounds":{"left":0.9710938,"top":0.13194445,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0ms]","depth":21,"bounds":{"left":0.9796875,"top":0.13194445,"width":0.015625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show/hide message details.","depth":17,"bounds":{"left":0.46367186,"top":0.14583333,"width":0.009375,"height":0.011111111},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"GET","depth":21,"bounds":{"left":0.47421876,"top":0.14652778,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://ui.integration.app/assets/index-DCou__JW.js","depth":20,"bounds":{"left":0.484375,"top":0.14652778,"width":0.46289062,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://ui.integration.app/assets/index-DCou__JW.js","depth":21,"bounds":{"left":0.484375,"top":0.14652778,"width":0.13203125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[HTTP/3","depth":21,"bounds":{"left":0.9496094,"top":0.14652778,"width":0.020703126,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":22,"bounds":{"left":0.9710938,"top":0.14652778,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0ms]","depth":21,"bounds":{"left":0.9796875,"top":0.14652778,"width":0.015625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"None of the “sha384” hashes in the integrity attribute match the content of the subresource at “","depth":21,"bounds":{"left":0.471875,"top":0.16111112,"width":0.24804688,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://fonts.googleapis.com/css2?family=IBM+Plex+Serif&display=swap","depth":21,"bounds":{"left":0.7199219,"top":0.16111112,"width":0.17578125,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://fonts.googleapis.com/css2?family=IBM+Plex+Serif&display=swap","depth":22,"bounds":{"left":0.7199219,"top":0.16111112,"width":0.17578125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"”. The computed hash is “oxpr/SPifeVqNx5O/1ow9nS0QIt60XJIKkUcSrPclwH/ruMEWK7C1JNqlqUMCV5N”.","depth":21,"bounds":{"left":0.471875,"top":0.16111112,"width":0.5015625,"height":0.019444445},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"connect","depth":20,"bounds":{"left":0.97734374,"top":0.16111112,"width":0.01796875,"height":0.009722223},"help_text":"View source in Debugger → https://ui.integration.app/embed/integrations/zohocrm/connect","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"connect","depth":22,"bounds":{"left":0.97734374,"top":0.16111112,"width":0.01796875,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show/hide message details.","depth":17,"bounds":{"left":0.46367186,"top":0.18472221,"width":0.009375,"height":0.011111111},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"GET","depth":21,"bounds":{"left":0.47421876,"top":0.18541667,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://ui.integration.app/assets/RefreshConnectionPopup-P6RvdGCd.js","depth":20,"bounds":{"left":0.484375,"top":0.18541667,"width":0.46289062,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://ui.integration.app/assets/RefreshConnectionPopup-P6RvdGCd.js","depth":21,"bounds":{"left":0.484375,"top":0.18541667,"width":0.17578125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[HTTP/3","depth":21,"bounds":{"left":0.9496094,"top":0.18541667,"width":0.020703126,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":22,"bounds":{"left":0.9710938,"top":0.18541667,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0ms]","depth":21,"bounds":{"left":0.9796875,"top":0.18541667,"width":0.015625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show/hide message details.","depth":17,"bounds":{"left":0.46367186,"top":0.19930555,"width":0.009375,"height":0.011111111},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"GET","depth":21,"bounds":{"left":0.47421876,"top":0.2,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://ui.integration.app/assets/index-DRKPh6L6.js","depth":20,"bounds":{"left":0.484375,"top":0.2,"width":0.46289062,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://ui.integration.app/assets/index-DRKPh6L6.js","depth":21,"bounds":{"left":0.484375,"top":0.2,"width":0.13203125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[HTTP/3","depth":21,"bounds":{"left":0.9496094,"top":0.2,"width":0.020703126,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":22,"bounds":{"left":0.9710938,"top":0.2,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0ms]","depth":21,"bounds":{"left":0.9796875,"top":0.2,"width":0.015625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show/hide message details.","depth":17,"bounds":{"left":0.46367186,"top":0.21388888,"width":0.009375,"height":0.011111111},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"GET","depth":21,"bounds":{"left":0.47421876,"top":0.21458334,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://ui.integration.app/assets/RefreshConnectionPopup-_TEQhDXU.css","depth":20,"bounds":{"left":0.484375,"top":0.21458334,"width":0.46289062,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://ui.integration.app/assets/RefreshConnectionPopup-_TEQhDXU.css","depth":21,"bounds":{"left":0.484375,"top":0.21458334,"width":0.17851563,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[HTTP/3","depth":21,"bounds":{"left":0.9496094,"top":0.21458334,"width":0.020703126,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":22,"bounds":{"left":0.9710938,"top":0.21458334,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0ms]","depth":21,"bounds":{"left":0.9796875,"top":0.21458334,"width":0.015625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show/hide message details.","depth":17,"bounds":{"left":0.46367186,"top":0.22847222,"width":0.009375,"height":0.011111111},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"GET","depth":21,"bounds":{"left":0.47421876,"top":0.22916667,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://ui.integration.app/assets/IntegrationConnectionGuard-CgFvoH9G.js","depth":20,"bounds":{"left":0.484375,"top":0.22916667,"width":0.46289062,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://ui.integration.app/assets/IntegrationConnectionGuard-CgFvoH9G.js","depth":21,"bounds":{"left":0.484375,"top":0.22916667,"width":0.18632813,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[HTTP/3","depth":21,"bounds":{"left":0.9496094,"top":0.22916667,"width":0.020703126,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":22,"bounds":{"left":0.9710938,"top":0.22916667,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0ms]","depth":21,"bounds":{"left":0.9796875,"top":0.22916667,"width":0.015625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show/hide message details.","depth":17,"bounds":{"left":0.46367186,"top":0.24305555,"width":0.009375,"height":0.011111111},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"GET","depth":21,"bounds":{"left":0.47421876,"top":0.24375,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://ui.integration.app/assets/index-7Ka1N6pQ.js","depth":20,"bounds":{"left":0.484375,"top":0.24375,"width":0.46289062,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://ui.integration.app/assets/index-7Ka1N6pQ.js","depth":21,"bounds":{"left":0.484375,"top":0.24375,"width":0.13203125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[HTTP/3","depth":21,"bounds":{"left":0.9496094,"top":0.24375,"width":0.020703126,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":22,"bounds":{"left":0.9710938,"top":0.24375,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0ms]","depth":21,"bounds":{"left":0.9796875,"top":0.24375,"width":0.015625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show/hide message details.","depth":17,"bounds":{"left":0.46367186,"top":0.2576389,"width":0.009375,"height":0.011111111},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"GET","depth":21,"bounds":{"left":0.47421876,"top":0.25833333,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://ui.integration.app/assets/Routes-CDWw4D3f.js","depth":20,"bounds":{"left":0.484375,"top":0.25833333,"width":0.46289062,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://ui.integration.app/assets/Routes-CDWw4D3f.js","depth":21,"bounds":{"left":0.484375,"top":0.25833333,"width":0.134375,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[HTTP/3","depth":21,"bounds":{"left":0.9496094,"top":0.25833333,"width":0.020703126,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":22,"bounds":{"left":0.9710938,"top":0.25833333,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0ms]","depth":21,"bounds":{"left":0.9796875,"top":0.25833333,"width":0.015625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show/hide message details.","depth":17,"bounds":{"left":0.46367186,"top":0.27222222,"width":0.009375,"height":0.011111111},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"GET","depth":21,"bounds":{"left":0.47421876,"top":0.27291667,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://ui.integration.app/assets/index-Dg8yQbmF.js","depth":20,"bounds":{"left":0.484375,"top":0.27291667,"width":0.46289062,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://ui.integration.app/assets/index-Dg8yQbmF.js","depth":21,"bounds":{"left":0.484375,"top":0.27291667,"width":0.13203125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[HTTP/3","depth":21,"bounds":{"left":0.9496094,"top":0.27291667,"width":0.020703126,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":22,"bounds":{"left":0.9710938,"top":0.27291667,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0ms]","depth":21,"bounds":{"left":0.9796875,"top":0.27291667,"width":0.015625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show/hide message details.","depth":17,"bounds":{"left":0.46367186,"top":0.28680557,"width":0.009375,"height":0.011111111},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"GET","depth":21,"bounds":{"left":0.47421876,"top":0.2875,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://ui.integration.app/assets/ResetButton-B5vGAh3h.js","depth":20,"bounds":{"left":0.484375,"top":0.2875,"width":0.46289062,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://ui.integration.app/assets/ResetButton-B5vGAh3h.js","depth":21,"bounds":{"left":0.484375,"top":0.2875,"width":0.14765625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[HTTP/3","depth":21,"bounds":{"left":0.9496094,"top":0.2875,"width":0.020703126,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":22,"bounds":{"left":0.9710938,"top":0.2875,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0ms]","depth":21,"bounds":{"left":0.9796875,"top":0.2875,"width":0.015625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show/hide message details.","depth":17,"bounds":{"left":0.46367186,"top":0.3013889,"width":0.009375,"height":0.011111111},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"GET","depth":21,"bounds":{"left":0.47421876,"top":0.30208334,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://ui.integration.app/assets/flow-instance-context-CJr_s6BC.js","depth":20,"bounds":{"left":0.484375,"top":0.30208334,"width":0.46289062,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://ui.integration.app/assets/flow-instance-context-CJr_s6BC.js","depth":21,"bounds":{"left":0.484375,"top":0.30208334,"width":0.1734375,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[HTTP/3","depth":21,"bounds":{"left":0.9496094,"top":0.30208334,"width":0.020703126,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":22,"bounds":{"left":0.9710938,"top":0.30208334,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0ms]","depth":21,"bounds":{"left":0.9796875,"top":0.30208334,"width":0.015625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show/hide message details.","depth":17,"bounds":{"left":0.46367186,"top":0.3159722,"width":0.009375,"height":0.011111111},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"GET","depth":21,"bounds":{"left":0.47421876,"top":0.31666666,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://ui.integration.app/assets/index-BhMG5VAy.css","depth":20,"bounds":{"left":0.484375,"top":0.31666666,"width":0.46289062,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://ui.integration.app/assets/index-BhMG5VAy.css","depth":21,"bounds":{"left":0.484375,"top":0.31666666,"width":0.134375,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[HTTP/3","depth":21,"bounds":{"left":0.9496094,"top":0.31666666,"width":0.020703126,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":22,"bounds":{"left":0.9710938,"top":0.31666666,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0ms]","depth":21,"bounds":{"left":0.9796875,"top":0.31666666,"width":0.015625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show/hide message details.","depth":17,"bounds":{"left":0.46367186,"top":0.33055556,"width":0.009375,"height":0.011111111},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
435485411657637066
|
-308816509618605563
|
visual_change
|
accessibility
|
NULL
|
Workers | Datadog
Developers | HubSpot
Developers Workers | Datadog
Developers | HubSpot
Developers | HubSpot
Inbox (1,576) - [EMAIL] - Jiminny Mail
Inbox (1,576) - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
CloudWatch | eu-west-1
CloudWatch | eu-west-1
New Tab
New Tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
[JY-20692] Issue with reconnecting Zoho - Jira
[JY-20692] Issue with reconnecting Zoho - Jira
Jiminny
Jiminny
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Update your information
Update your information
GENERAL
TIMEZONE
Select option Europe/Sofia (UTC +03:00)
Select option
Europe/Sofia (UTC +03:00)
*
LANGUAGES SPOKEN DURING CALLS
DEFAULT SPOKEN LANGUAGE
Select option English (United Kingdom)
Select option
English (United Kingdom)
*
If the language isn't detected we'll default to this one
Add language
CONNECT/SYNC SETTINGS
Connect Zoho CRM
zohocrm Sign in with Zoho CRM
Sign in with Zoho CRM
Import Calendar Meetings
*
google Sign in with Google
Sign in with Google
Let's Get Started!
Zoho CRM Zoho CRM
Zoho CRM
Linking your Zoho CRM account
Linking your Zoho CRM account
Please select one of authentication options:
Connect via Membrane
Connect via Membrane
Connect via Membrane
OAuth 2.0
OAuth 2.0
OAuth 2.0
Close Dialog
Clear the Web Console output (⌘K, Ctrl+L)
Integr
Integr
230 hidden
Clear filter input
Errors
Warnings
Info
Logs
Debug
CSS
XHR
Requests
Console Settings
Navigated to https://app.dev.jiminny.com/dashboard
Show/hide message details.
XHR
GET
https://app.dev.jiminny.com/api/v1/integration-app-token
https://app.dev.jiminny.com/api/v1/integration-app-token
[HTTP/2
200
1450ms]
Show/hide message details.
GET
https://ui.integration.app/embed/integrations/zohocrm/connect?token=[JWT_TOKEN]&showPoweredBy=false&allowMultipleConnections=
https://ui.integration.app/embed/integrations/zohocrm/connect?token=[JWT_TOKEN]&showPoweredBy=false&allowMultipleConnections=
[HTTP/2
200
226ms]
Show/hide message details.
GET
https://ui.integration.app/assets/index-DQ2tm1dS.css
https://ui.integration.app/assets/index-DQ2tm1dS.css
[HTTP/3
200
0ms]
Show/hide message details.
GET
https://ui.integration.app/assets/index-DCou__JW.js
https://ui.integration.app/assets/index-DCou__JW.js
[HTTP/3
200
0ms]
None of the “sha384” hashes in the integrity attribute match the content of the subresource at “
https://fonts.googleapis.com/css2?family=IBM+Plex+Serif&display=swap
https://fonts.googleapis.com/css2?family=IBM+Plex+Serif&display=swap
”. The computed hash is “oxpr/SPifeVqNx5O/1ow9nS0QIt60XJIKkUcSrPclwH/ruMEWK7C1JNqlqUMCV5N”.
connect
connect
Show/hide message details.
GET
https://ui.integration.app/assets/RefreshConnectionPopup-P6RvdGCd.js
https://ui.integration.app/assets/RefreshConnectionPopup-P6RvdGCd.js
[HTTP/3
200
0ms]
Show/hide message details.
GET
https://ui.integration.app/assets/index-DRKPh6L6.js
https://ui.integration.app/assets/index-DRKPh6L6.js
[HTTP/3
200
0ms]
Show/hide message details.
GET
https://ui.integration.app/assets/RefreshConnectionPopup-_TEQhDXU.css
https://ui.integration.app/assets/RefreshConnectionPopup-_TEQhDXU.css
[HTTP/3
200
0ms]
Show/hide message details.
GET
https://ui.integration.app/assets/IntegrationConnectionGuard-CgFvoH9G.js
https://ui.integration.app/assets/IntegrationConnectionGuard-CgFvoH9G.js
[HTTP/3
200
0ms]
Show/hide message details.
GET
https://ui.integration.app/assets/index-7Ka1N6pQ.js
https://ui.integration.app/assets/index-7Ka1N6pQ.js
[HTTP/3
200
0ms]
Show/hide message details.
GET
https://ui.integration.app/assets/Routes-CDWw4D3f.js
https://ui.integration.app/assets/Routes-CDWw4D3f.js
[HTTP/3
200
0ms]
Show/hide message details.
GET
https://ui.integration.app/assets/index-Dg8yQbmF.js
https://ui.integration.app/assets/index-Dg8yQbmF.js
[HTTP/3
200
0ms]
Show/hide message details.
GET
https://ui.integration.app/assets/ResetButton-B5vGAh3h.js
https://ui.integration.app/assets/ResetButton-B5vGAh3h.js
[HTTP/3
200
0ms]
Show/hide message details.
GET
https://ui.integration.app/assets/flow-instance-context-CJr_s6BC.js
https://ui.integration.app/assets/flow-instance-context-CJr_s6BC.js
[HTTP/3
200
0ms]
Show/hide message details.
GET
https://ui.integration.app/assets/index-BhMG5VAy.css
https://ui.integration.app/assets/index-BhMG5VAy.css
[HTTP/3
200
0ms]
Show/hide message details....
|
NULL
|
|
47200
|
NULL
|
0
|
2026-04-17T11:13:58.138724+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776424438138_m1.jpg...
|
Firefox
|
Jiminny — Work
|
True
|
app.dev.jiminny.com/onboard
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Developers | HubSpot
Developers Workers | Datadog
Developers | HubSpot
Developers | HubSpot
Inbox (1,576) - [EMAIL] - Jiminny Mail
Inbox (1,576) - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
CloudWatch | eu-west-1
CloudWatch | eu-west-1
New Tab
New Tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
[JY-20692] Issue with reconnecting Zoho - Jira
[JY-20692] Issue with reconnecting Zoho - Jira
Jiminny
Jiminny
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Update your information
Update your information
GENERAL
TIMEZONE
Select option Europe/Sofia (UTC +03:00)
Select option
Europe/Sofia (UTC +03:00)
*
LANGUAGES SPOKEN DURING CALLS
DEFAULT SPOKEN LANGUAGE
Select option English (United Kingdom)
Select option
English (United Kingdom)
*
If the language isn't detected we'll default to this one
Add language
CONNECT/SYNC SETTINGS
Connect Zoho CRM
zohocrm Sign in with Zoho CRM
Sign in with Zoho CRM
Import Calendar Meetings
*
google Sign in with Google
Sign in with Google
Let's Get Started!
Clear the Web Console output (⌘K, Ctrl+L)
Integr
Integr
214 hidden
Clear filter input
Errors
Warnings
Info
Logs
Debug
CSS
XHR
Requests
Console Settings
Navigated to https://app.dev.jiminny.com/dashboard
Show/hide message details.
XHR
GET
https://app.dev.jiminny.com/api/v1/integration-app-token
https://app.dev.jiminny.com/api/v1/integration-app-token
[HTTP/2
200
1450ms]
Top
Switch to multi-line editor mode (Cmd + B)
...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Developers | HubSpot","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Developers | HubSpot","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Inbox (1,576) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox (1,576) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"120216 is your HubSpot Log In Code - integration-account@jiminny.com - Jiminny Mail","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"120216 is your HubSpot Log In Code - integration-account@jiminny.com - Jiminny Mail","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | eu-west-1","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | eu-west-1","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20692] Issue with reconnecting Zoho - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20692] Issue with reconnecting Zoho - Jira","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Jiminny","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Update your information","depth":9,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Update your information","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GENERAL","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"TIMEZONE","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Select option Europe/Sofia (UTC +03:00)","depth":10,"value":"Select option Europe/Sofia (UTC +03:00)","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Select option","depth":11,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Europe/Sofia (UTC +03:00)","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"*","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"LANGUAGES SPOKEN DURING CALLS","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"DEFAULT SPOKEN LANGUAGE","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Select option English (United Kingdom)","depth":10,"value":"Select option English (United Kingdom)","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Select option","depth":11,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"English (United Kingdom)","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"*","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the language isn't detected we'll default to this one","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add language","depth":11,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CONNECT/SYNC SETTINGS","depth":10,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Connect Zoho CRM","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"zohocrm Sign in with Zoho CRM","depth":10,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false},{"role":"AXStaticText","text":"Sign in with Zoho CRM","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Import Calendar Meetings","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"*","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"google Sign in with Google","depth":10,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sign in with Google","depth":11,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Let's Get Started!","depth":9,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Clear the Web Console output (⌘K, Ctrl+L)","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXTextField","text":"Integr","depth":15,"value":"Integr","role_description":"search text field","subrole":"AXSearchField","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Integr","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"214 hidden","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Clear filter input","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Errors","depth":15,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Warnings","depth":15,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Info","depth":15,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Logs","depth":15,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Debug","depth":15,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"CSS","depth":15,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"XHR","depth":15,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Requests","depth":15,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Console Settings","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Navigated to https://app.dev.jiminny.com/dashboard","depth":20,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show/hide message details.","depth":17,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"XHR","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://app.dev.jiminny.com/api/v1/integration-app-token","depth":20,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://app.dev.jiminny.com/api/v1/integration-app-token","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[HTTP/2","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":22,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1450ms]","depth":21,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Top","depth":16,"help_text":"Select evaluation context","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Switch to multi-line editor mode (Cmd + B)","depth":16,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-6936402857171103518
|
-7241824426406463412
|
click
|
accessibility
|
NULL
|
Workers | Datadog
Developers | HubSpot
Developers Workers | Datadog
Developers | HubSpot
Developers | HubSpot
Inbox (1,576) - [EMAIL] - Jiminny Mail
Inbox (1,576) - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
CloudWatch | eu-west-1
CloudWatch | eu-west-1
New Tab
New Tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
[JY-20692] Issue with reconnecting Zoho - Jira
[JY-20692] Issue with reconnecting Zoho - Jira
Jiminny
Jiminny
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Update your information
Update your information
GENERAL
TIMEZONE
Select option Europe/Sofia (UTC +03:00)
Select option
Europe/Sofia (UTC +03:00)
*
LANGUAGES SPOKEN DURING CALLS
DEFAULT SPOKEN LANGUAGE
Select option English (United Kingdom)
Select option
English (United Kingdom)
*
If the language isn't detected we'll default to this one
Add language
CONNECT/SYNC SETTINGS
Connect Zoho CRM
zohocrm Sign in with Zoho CRM
Sign in with Zoho CRM
Import Calendar Meetings
*
google Sign in with Google
Sign in with Google
Let's Get Started!
Clear the Web Console output (⌘K, Ctrl+L)
Integr
Integr
214 hidden
Clear filter input
Errors
Warnings
Info
Logs
Debug
CSS
XHR
Requests
Console Settings
Navigated to https://app.dev.jiminny.com/dashboard
Show/hide message details.
XHR
GET
https://app.dev.jiminny.com/api/v1/integration-app-token
https://app.dev.jiminny.com/api/v1/integration-app-token
[HTTP/2
200
1450ms]
Top
Switch to multi-line editor mode (Cmd + B)
...
|
47196
|
|
47110
|
NULL
|
0
|
2026-04-17T11:08:43.572469+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776424123572_m2.jpg...
|
NULL
|
NULL
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorToo PhpStormFileEditViewNavigateCodeLaravelRefactorToolsWindowHelpFV faVsco.s vT° JY-20692-fix-integration-app-[API_KEY].php-cs-fixer.dist.phpphp.phpstorm.meta.php=.phpunit.result.cacheE.prettierignoreE windsurfrulespip _lde_nelper.pnppnpce neloermoces.onepnp aruisani composer.isonO composer.lockdependency-checker.jsonO dev.jsonE ids.txt=infection.json.distM-INSALL.mdM+ INTERNAL WEBHOOK SETUPjiminny_storageM+ licenses.mdM MakefileOpackage-lock.json= phpstan.neon.dist= phpstan-baseline.neon< phpunit.xmlTa raw_sqLquery.sqlM+ README.mdsonar-project.properties= test.py<> Untitled Diagram.xmlI5 vetur.config.jsM-+ WEbnOOK HILIEKING_IMPLE› Iib External Librariesv E® Scratches and Consolesv D Database Consoles101819281030cachedStagesCcW165trait OpportunitySyncTraitA33 X2 X19 A Y 166private function resolveForecastCategory(?string $forecastCategory): stringf...1672 usages=169private function importExternalFieldData(array $properties, int $opportunityIa, 170171$crmFields = $this->getOpportunitySyncableFieldsO;=172$this->import0pportunityCrmFieldData(Sproperties, $crmFields, Sopportunityi17310331034103510481049105010511052=175176Lusaeesprivate function importOpportunityContacts(Opportunity Sopportunity, array $as:177=179/*** Sync opportunity contacts using differential approach180* This compares current vs new associations and only makes necessary changes1811821 usageprivate function syncOpportunityContactsDifferential(Opportunity $opportunity,10701 usageprivate function getcurrentContactCrmIds(Opportunity $opportunity): array{..., 187A console [EU]4 DEAL RISKS [EU]A DI [EU]dEuleUv& jiminny@localhost& console [jiminny@localr4 DI liiminnv@localhosti4 HS_local [jiminny@localA SF [jiminny@localhost]A zoho_dev [jiminny@loceY A PROD© AutomatedReportsService.phpC ReportController.php© SendReportJob.phplokenbullaer.onoc leamsetuocontroller.onp• Filesystem.php© Team.php© RequestGenerateReportJob.phpT InteractsWithPivotTable.phg© CreateHeldActivityEvent.phpOpportunitySyncTrait.phpC OpportunityUpdated.php© SendReportMailJob.phpphp api.php© TrackProviderlnstalledEvent.phpC Opportunity.phpC OpportunityStageUpdated.phpc) FventService?rovider.onm© RunOpportunityAiAnalysis.php( ImportBatchJobTrait.php© OpportunityPendingAiAnalysisAfterStageChanged.php© ProcessAiAutomationAnalysisResults.php(C) Service.php© ImportOpportunityBatch.phpД0/1107710781079108010811 usageprivate function logContactAssociationChanges(Opportunity Sopportunity,array $currentContactCrmIds,array $contactAssociations,array $contactsToAdd,array +concacustokemove): void {...}-189190191192services+,o₫|v D DatabasevdtuA console 1 s 59 msuni crm conticurations "s 391 msdjiminny@localhost4SF4 HS_localV A PROD4 console 1 s 806 msV A STAGINGA consoleOutputf # 16952 rows y+=S9TTy: Autoy1y3VLf6XLVLQRQd0jUSrKV7cXnoDDB6zgwKg0ozEBvu2HnCmKsNV…2 CIsImtpZCI6IjVmNWFhZGFkLTQwZDktND1kNy04ZWI2LTAzNmN1M...QM0AФW expires Y177642648417296136151776509176refresh_token_expires Y! provider Yu coodle<null> zoom-phone<null> integration-appShortcuts conflicts: Clone Caret Above and 1 more shortottings. // Modify Shortcuts // Don't Show Again (today 12:22)= custom.log< Hs local liminnyalocalnostA console [PROD]E laravel.logA SF [jiminny@localhost] XC scratch_1jsonA console (EU]© CrmEntityRepository.phpA console [(STAGING]V connect.vuefi crm_configurations (EU]V Onboard.vueXAutovPlaygroundSELECT * FROM automated_reports order by za desc;SELECT * FROMautomated_report_results order by id desc;select * from activity_searches where user_id = 143;select * from ask_anything_prompts;ma liminnvv09 A12 V2 V4 л VSELECT * FROM groups WHERE id = 1439;SELECT * FROM users WHERE group_id = 1439;select * from permissions; # 158select * from roles;select * from permission_role,select * from teams where id = 1;select * from groups g JOIN playbooks p 1.n<->1: on g.playbook_id = p.id where g.team_id = 1;select * from groups where id = 28;select * from playbooks where team_id = 1;select * from playbooks where id = 179;select * from playbook_categories where id = 1391;select * from users where id = 143;select * from crm_profiles where user_id = 143;select * from activities where crm_configuration_id = 39 and type = 'conference'and crm_provider_id IS NOT NULL ORDER by id desc;select * from activities where id = 422003; # 00U0400000pB6FpMACSELECT ar.ig, ar.wuig, ar.mediantxpe, ar.status, a.txReFROM automated_report_results arJOIN automated_reports a ON a.id = ar.repontaidWHERE a.txpe = 'ask_jiminnx"LIMIT 10;select * from teams where id = 3143;select * from crm_configurations where id = 500;select * from users where name = 'Integration Account'; # 1695SELECT * FROM social_accounts WHERE sociable_id = 1695;f Support Daily • in 52 mAL AutomatedReportsCommandTestv100% 145Fri 17 Apr 14:08:43CascadeAutomated Report RetrDebugging OpportunitUpdate Connection Loconnect.vue tront-end/src/components/connecnothing is there"cant see log'sThe console is full of CSP warnings and errors but none of your [IntegrationApp] logs. This means intedrat1onApponcL1ck Is never belno called at all — the burton click isn't reachine that tunction.Check:1. Is the button actually calling integrationApp0nClick? - the click handler might be bound to adifferent method or the component re-rendered2. Is the frontend bundle rebuilt? — you may be running a stale build that doesn't include your changesSearched.intecrationAoo@nClickintront-enc/src/coents/connect/connect.vue (2)The binding looks correct. The most likely issue is a stale build - your changes aren't compiled yet. Add aquick test: put a console.log before the IntegrationAppClient instantiation that's impossible to miss:Reao connectvue :158-|44• connect.vueNow after saving, hard refresh the browser ( Cmd+Shift+R ) to bust the cache. If you still don't see [IntegraLlonAdo intecracionAppuncllck called arter cllckine tne ourion, tne dev server nash t olckea ue tnechanges or the click event isn't reaching this component (e.g. another element is intercepting it).O ll .wAsk anything (284L)+ <> Code Claude Sonnet 4.6I state Yconnectedfull-refreshfull-refresheI auth_scope Ynttps://www.googleap1s.com/auch/user1nro.ema1l open1d nuups://www.googleap1s.com/auch/calendar nuups..phone:read:admin user:read:adminSHULLeCSV vДOB,Wretry_after YID creaShULD2026-04<nul><null>2024-12026-04W Windsurf Teams 192:20 0 UTF-84 spaces...
|
NULL
|
-7584938818329207593
|
NULL
|
visual_change
|
ocr
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorToo PhpStormFileEditViewNavigateCodeLaravelRefactorToolsWindowHelpFV faVsco.s vT° JY-20692-fix-integration-app-[API_KEY].php-cs-fixer.dist.phpphp.phpstorm.meta.php=.phpunit.result.cacheE.prettierignoreE windsurfrulespip _lde_nelper.pnppnpce neloermoces.onepnp aruisani composer.isonO composer.lockdependency-checker.jsonO dev.jsonE ids.txt=infection.json.distM-INSALL.mdM+ INTERNAL WEBHOOK SETUPjiminny_storageM+ licenses.mdM MakefileOpackage-lock.json= phpstan.neon.dist= phpstan-baseline.neon< phpunit.xmlTa raw_sqLquery.sqlM+ README.mdsonar-project.properties= test.py<> Untitled Diagram.xmlI5 vetur.config.jsM-+ WEbnOOK HILIEKING_IMPLE› Iib External Librariesv E® Scratches and Consolesv D Database Consoles101819281030cachedStagesCcW165trait OpportunitySyncTraitA33 X2 X19 A Y 166private function resolveForecastCategory(?string $forecastCategory): stringf...1672 usages=169private function importExternalFieldData(array $properties, int $opportunityIa, 170171$crmFields = $this->getOpportunitySyncableFieldsO;=172$this->import0pportunityCrmFieldData(Sproperties, $crmFields, Sopportunityi17310331034103510481049105010511052=175176Lusaeesprivate function importOpportunityContacts(Opportunity Sopportunity, array $as:177=179/*** Sync opportunity contacts using differential approach180* This compares current vs new associations and only makes necessary changes1811821 usageprivate function syncOpportunityContactsDifferential(Opportunity $opportunity,10701 usageprivate function getcurrentContactCrmIds(Opportunity $opportunity): array{..., 187A console [EU]4 DEAL RISKS [EU]A DI [EU]dEuleUv& jiminny@localhost& console [jiminny@localr4 DI liiminnv@localhosti4 HS_local [jiminny@localA SF [jiminny@localhost]A zoho_dev [jiminny@loceY A PROD© AutomatedReportsService.phpC ReportController.php© SendReportJob.phplokenbullaer.onoc leamsetuocontroller.onp• Filesystem.php© Team.php© RequestGenerateReportJob.phpT InteractsWithPivotTable.phg© CreateHeldActivityEvent.phpOpportunitySyncTrait.phpC OpportunityUpdated.php© SendReportMailJob.phpphp api.php© TrackProviderlnstalledEvent.phpC Opportunity.phpC OpportunityStageUpdated.phpc) FventService?rovider.onm© RunOpportunityAiAnalysis.php( ImportBatchJobTrait.php© OpportunityPendingAiAnalysisAfterStageChanged.php© ProcessAiAutomationAnalysisResults.php(C) Service.php© ImportOpportunityBatch.phpД0/1107710781079108010811 usageprivate function logContactAssociationChanges(Opportunity Sopportunity,array $currentContactCrmIds,array $contactAssociations,array $contactsToAdd,array +concacustokemove): void {...}-189190191192services+,o₫|v D DatabasevdtuA console 1 s 59 msuni crm conticurations "s 391 msdjiminny@localhost4SF4 HS_localV A PROD4 console 1 s 806 msV A STAGINGA consoleOutputf # 16952 rows y+=S9TTy: Autoy1y3VLf6XLVLQRQd0jUSrKV7cXnoDDB6zgwKg0ozEBvu2HnCmKsNV…2 CIsImtpZCI6IjVmNWFhZGFkLTQwZDktND1kNy04ZWI2LTAzNmN1M...QM0AФW expires Y177642648417296136151776509176refresh_token_expires Y! provider Yu coodle<null> zoom-phone<null> integration-appShortcuts conflicts: Clone Caret Above and 1 more shortottings. // Modify Shortcuts // Don't Show Again (today 12:22)= custom.log< Hs local liminnyalocalnostA console [PROD]E laravel.logA SF [jiminny@localhost] XC scratch_1jsonA console (EU]© CrmEntityRepository.phpA console [(STAGING]V connect.vuefi crm_configurations (EU]V Onboard.vueXAutovPlaygroundSELECT * FROM automated_reports order by za desc;SELECT * FROMautomated_report_results order by id desc;select * from activity_searches where user_id = 143;select * from ask_anything_prompts;ma liminnvv09 A12 V2 V4 л VSELECT * FROM groups WHERE id = 1439;SELECT * FROM users WHERE group_id = 1439;select * from permissions; # 158select * from roles;select * from permission_role,select * from teams where id = 1;select * from groups g JOIN playbooks p 1.n<->1: on g.playbook_id = p.id where g.team_id = 1;select * from groups where id = 28;select * from playbooks where team_id = 1;select * from playbooks where id = 179;select * from playbook_categories where id = 1391;select * from users where id = 143;select * from crm_profiles where user_id = 143;select * from activities where crm_configuration_id = 39 and type = 'conference'and crm_provider_id IS NOT NULL ORDER by id desc;select * from activities where id = 422003; # 00U0400000pB6FpMACSELECT ar.ig, ar.wuig, ar.mediantxpe, ar.status, a.txReFROM automated_report_results arJOIN automated_reports a ON a.id = ar.repontaidWHERE a.txpe = 'ask_jiminnx"LIMIT 10;select * from teams where id = 3143;select * from crm_configurations where id = 500;select * from users where name = 'Integration Account'; # 1695SELECT * FROM social_accounts WHERE sociable_id = 1695;f Support Daily • in 52 mAL AutomatedReportsCommandTestv100% 145Fri 17 Apr 14:08:43CascadeAutomated Report RetrDebugging OpportunitUpdate Connection Loconnect.vue tront-end/src/components/connecnothing is there"cant see log'sThe console is full of CSP warnings and errors but none of your [IntegrationApp] logs. This means intedrat1onApponcL1ck Is never belno called at all — the burton click isn't reachine that tunction.Check:1. Is the button actually calling integrationApp0nClick? - the click handler might be bound to adifferent method or the component re-rendered2. Is the frontend bundle rebuilt? — you may be running a stale build that doesn't include your changesSearched.intecrationAoo@nClickintront-enc/src/coents/connect/connect.vue (2)The binding looks correct. The most likely issue is a stale build - your changes aren't compiled yet. Add aquick test: put a console.log before the IntegrationAppClient instantiation that's impossible to miss:Reao connectvue :158-|44• connect.vueNow after saving, hard refresh the browser ( Cmd+Shift+R ) to bust the cache. If you still don't see [IntegraLlonAdo intecracionAppuncllck called arter cllckine tne ourion, tne dev server nash t olckea ue tnechanges or the click event isn't reaching this component (e.g. another element is intercepting it).O ll .wAsk anything (284L)+ <> Code Claude Sonnet 4.6I state Yconnectedfull-refreshfull-refresheI auth_scope Ynttps://www.googleap1s.com/auch/user1nro.ema1l open1d nuups://www.googleap1s.com/auch/calendar nuups..phone:read:admin user:read:adminSHULLeCSV vДOB,Wretry_after YID creaShULD2026-04<nul><null>2024-12026-04W Windsurf Teams 192:20 0 UTF-84 spaces...
|
47108
|
|
47109
|
NULL
|
0
|
2026-04-17T11:08:41.576899+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776424121576_m1.jpg...
|
NULL
|
NULL
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
iTerm2ShellEditViewSessionScriptsProfilesWindowHel iTerm2ShellEditViewSessionScriptsProfilesWindowHelpla6]Support Daily - in 52 m100% <47-zshAPP (-zsh)|X4-zshDOCKER• ₴1DEV (docker)O ₴2APP (-zsh)X3./public/vue-assets/assets/ondemand-DutwAJ6x.js:./public/vue-assets/assets/CrmLink-DKYsnHnx.js./public/vue-assets/assets/liquor-tree-COUefof4.js./public/vue-assets/assets/DealRiskList-CjPoT6NF.js:./public/vue-assets/assets/AskAnything-a4BpUaF5.js:./public/vue-assets/assets/lib-CwM9toD2.js./public/vue-assets/assets/AppFormField-CtfldUq7.js:./public/vue-assets/assets/deal-view-DhouIWLw.js../public/vue-assets/assets/exports-D1lmea40.js../public/vue-assets/assets/playlists-BWns-By9.js../public/vue-assets/assets/callScoringTemplates-zeRn40ul.js../public/vue-assets/assets/_copy0bject-USkOnlaQ.js../public/vue-assets/assets/pusher-znYCfz7U.js./public/vue-assets/assets/onboard-BENebgvU.js./public/vue-assets/assets/StatusBadge-CmcY3nfX.js./public/vue-assets/assets/kiosk-1vhk4_89.js./public/vue-assets/assets/preload-helper-DCvhahzG.js../public/vue-assets/assets/deal-insights-DN-kVyxK.js../public/vue-assets/assets/ListView-POU6dSu7.js:./public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js./public/vue-assets/assets/WelcomeLayout-B6wd32HG.js../public/vue-assets/assets/dashboard-DYH5MiIH.js:./public/vue-assets/assets/emoji-input-CSq87OVy.js../public/vue-assets/assets/AppButton-D3qMd0Dr.js../public/vue-assets/assets/sentry-c0Rhilsu.js:./public/vue-assets/assets/OrgSettingsLayout-ByQjX4wG.js./public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js./public/vue-assets/assets/playback-BH9MB7YC.js./public/vue-assets/assets/index.module-Bjlhgfdl.js./public/vue-assets/assets/intl-tel-input-BW4mv40Q.js./public/vue-assets/assets/team-insights-BqCZQtVc.js../public/vue-assets/assets/popper-CQwVcrX4.js../public/vue-assets/assets/PhoneField-CwCIoAYm.js./public/vue-assets/assets/live-XL9ZmWsU.js./public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BN0485xV.js../public/vue-assets/assets/index-CPycMtZF.js../public/vue-assets/assets/logged-in-layout-B_JDhk5y.js• ₴526.88kB27.91kB30.75kB34.39kB39.50kB39.69kB41.91kB43.22kB47.84kB48.28kB55.13kB61.28kB62.98kB63.26kB64.66kB79.60kB82.59kB94.84kB115.71kB117.59 kВ120.67kB128.71 kB129.28 kB133.44 kB164.28kB176.33kB180.40kB198.79kB218.14kB264.94 kВ298.57kB307.13kB343.99kB367.43 kB689.63kB825.23 kB1,402.70 kB• built in 28.48s[plugin builtin:vite-reporter](!) Some chunksare larger than 500 kB after minification. Consider:- Using dynamic import() to code-split the application- Use build.rolldown0ptions.output.codeSplitting to improve chunking: [URL_WITH_CREDENTIALS] 88APP...
|
NULL
|
2213734560777715800
|
NULL
|
click
|
ocr
|
NULL
|
iTerm2ShellEditViewSessionScriptsProfilesWindowHel iTerm2ShellEditViewSessionScriptsProfilesWindowHelpla6]Support Daily - in 52 m100% <47-zshAPP (-zsh)|X4-zshDOCKER• ₴1DEV (docker)O ₴2APP (-zsh)X3./public/vue-assets/assets/ondemand-DutwAJ6x.js:./public/vue-assets/assets/CrmLink-DKYsnHnx.js./public/vue-assets/assets/liquor-tree-COUefof4.js./public/vue-assets/assets/DealRiskList-CjPoT6NF.js:./public/vue-assets/assets/AskAnything-a4BpUaF5.js:./public/vue-assets/assets/lib-CwM9toD2.js./public/vue-assets/assets/AppFormField-CtfldUq7.js:./public/vue-assets/assets/deal-view-DhouIWLw.js../public/vue-assets/assets/exports-D1lmea40.js../public/vue-assets/assets/playlists-BWns-By9.js../public/vue-assets/assets/callScoringTemplates-zeRn40ul.js../public/vue-assets/assets/_copy0bject-USkOnlaQ.js../public/vue-assets/assets/pusher-znYCfz7U.js./public/vue-assets/assets/onboard-BENebgvU.js./public/vue-assets/assets/StatusBadge-CmcY3nfX.js./public/vue-assets/assets/kiosk-1vhk4_89.js./public/vue-assets/assets/preload-helper-DCvhahzG.js../public/vue-assets/assets/deal-insights-DN-kVyxK.js../public/vue-assets/assets/ListView-POU6dSu7.js:./public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js./public/vue-assets/assets/WelcomeLayout-B6wd32HG.js../public/vue-assets/assets/dashboard-DYH5MiIH.js:./public/vue-assets/assets/emoji-input-CSq87OVy.js../public/vue-assets/assets/AppButton-D3qMd0Dr.js../public/vue-assets/assets/sentry-c0Rhilsu.js:./public/vue-assets/assets/OrgSettingsLayout-ByQjX4wG.js./public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js./public/vue-assets/assets/playback-BH9MB7YC.js./public/vue-assets/assets/index.module-Bjlhgfdl.js./public/vue-assets/assets/intl-tel-input-BW4mv40Q.js./public/vue-assets/assets/team-insights-BqCZQtVc.js../public/vue-assets/assets/popper-CQwVcrX4.js../public/vue-assets/assets/PhoneField-CwCIoAYm.js./public/vue-assets/assets/live-XL9ZmWsU.js./public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BN0485xV.js../public/vue-assets/assets/index-CPycMtZF.js../public/vue-assets/assets/logged-in-layout-B_JDhk5y.js• ₴526.88kB27.91kB30.75kB34.39kB39.50kB39.69kB41.91kB43.22kB47.84kB48.28kB55.13kB61.28kB62.98kB63.26kB64.66kB79.60kB82.59kB94.84kB115.71kB117.59 kВ120.67kB128.71 kB129.28 kB133.44 kB164.28kB176.33kB180.40kB198.79kB218.14kB264.94 kВ298.57kB307.13kB343.99kB367.43 kB689.63kB825.23 kB1,402.70 kB• built in 28.48s[plugin builtin:vite-reporter](!) Some chunksare larger than 500 kB after minification. Consider:- Using dynamic import() to code-split the application- Use build.rolldown0ptions.output.codeSplitting to improve chunking: [URL_WITH_CREDENTIALS] 88APP...
|
NULL
|
|
47062
|
NULL
|
0
|
2026-04-17T11:03:21.026440+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776423801026_m2.jpg...
|
NULL
|
NULL
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileFditViewNavigateCodelaravelRefactonToo PhpStormFileFditViewNavigateCodelaravelRefactonToolsWindowHelpFV faVsco.s vT° JY-20692-fix-integration-app-[API_KEY] v>W testsU connect.lessV connect.vuedashboardW DeallnsightsD errorPages_ export-portalextension-installledinvitationvonconterenceMlavoutD LiveCoach› Locked>D login>D MeetingConsent_ mobilel onboardl•__mocks_•U_tests_V MobileAppDownk1 Onboard.lessV Onboard vueTs useProvidersSyncondemandplaybackD playlistsW Settingsshared_ SoftphoneCoach• Usegicons_leaminsightscomposaoes> M7 directives> M7 helpers> → locales>_ pluginsrouter• storeD utilsus main.jsTs types.a.tsJstypes.jsI Vite= owsersistE env defaultC AutomatedReportsService.phpC) SendReportJob.phpC SendReportMailJob.php= custom.loc= aravel.logc SF liminny@localhostU scratch_1,jsonconnect.vueV Onboard.vue XC ReportController.phpTokenBuilder.phpc leamsetuocontroller.onppnp apl.ono< Hs local liminnyalocalnost4 console [EUlC CrmEntityRepository.pho(iii) crm configurations [EU]© Filesystem.php© Team.phpC RequestGenerateReportJob.php© CreateHeldActivityEvent.php© TrackProviderInstalledEvent.phpOpportunitySyncTrait.phpC Opportunity.phpT InteractsWithPivotTable.phgOpoorunityupdaled.onoOpoonunitystadeupaated.onoc) FventService?rovider.onm(c OnnortunitvPendindAiAnalvsisatterstadechanded.omC RunOpportunityAiAnalysis.phpC ProcessAlAutomationAnalysiskesults.onv© ImportOpportunityBatch.php¿ console IPRODIIc console [STAGING]<script>379setup() (const crmconnectintegrationApp = async function O 1477const integrationApp = new IntegrationAppClientetoken: crmToken.value.V1V9 AVTImportBatchJobTrait.php(C) Service.phpcachedStagesCcW1018trait upportunitysynctraitA33 V2 V19private function resolverorecastcategory(?string pforecastCategory): stringi.• 5062 usagesprivate tunecion importexternalele uabatatarray epropertzes, zht sopportunity ru*509const connection = awalt integrationApp. integration(crmbetalls.name)ovenvevronnectlonssnowPoweredby: talse,aLLowulrloleconnectons. tauseF):1050§507510console.Log(' [IntegrationAn&openNewConnection resolved:', JSON.stringify(connection)):$crmFields = $this->getOpportunitySyncableFields:Sthis->inportOpportunitvCrmFieldDataSproperties. ScrmFields.lSOOOONUILE103310341035104810491056105111054Lusaees/**private function importupportunatycontacts (Opportunaty sopportunity, array sas:516* Sync opportunity contacts using differentzal approach* Inis compares current vs new associacions and only makes necessary changes=513=5171518519if (!connection | connection.disconnected === true | connection.connected === false) ishowsnackbarErrord"A connection with your cRM could not be established".);returnhtry 1consr saverequest = valt axos.oos col vnccoraron-doo-connecr1076107110771078107910861081108410841093SZ11 usage522private tunccion syncupporcunttyconcactsultterenclat upporcunicy gopportunity.523524525private function getCurrentContactCrmIds(Opportunity Sopportunity): array.....526— 52752815291550if (saveRequest.data &x saveRequest.data.success === true) ‹** If all is good refresh the page here *return location.reloadhlusageprivate function logcontactAssoclationchangesOpportunity sopportunityarray scurrenccontacuermias,array scontaccassoclatlons.array scontacusioAdd,array +concacustokemove0: void {...}chrow new trror(saverequest.daca.message):f catch (error) {console.looterror).snowonackoarerrorunoriauzerrror error.054=533534548=5495501const preparelntegrationAppConnection = async function O k...rAif (crmRequired) 1Prepare the crm token only if the CRM is requiredLocal CnandesShelfLogconsoeLog xChanges 10 files=.env.local app(© ActivityController.php app/Http/Controllers/APlV connect.vue front-end/src/components/connect(C JiminnyDebugCommand.php app/Console/Commandsphp logging.php contigV Onboard.vue front-end/src/components/onboard© Playbackservice.php app/ServicesC ReportController.php app/Http/Controllers/Webhook(C) SyncTolntercom.ohp app/.Jobs/Teampnp weo.ono rouresUnversioned Files 8 filesslae-by-side viewerDo not ignore -Mionlleht worasmboocterc_tront-enc/src/comoonents/onooard/onboard.vueshowrowereoby: tauseaLLoyuuroleconneccons. tause.7);if (iconnection connection.disconnected === truelshowSnackbarError"A connection with your CRM could not be established".):return;• Support Daily • in 57 mA100% 145Fri 17 Apr 14:03:20AutomatedRenortsCommandTestCascadeAutomated Report RetDebugging OpportunitUpdate Connection Lcconnect.vue tront-end/src/components/connecnothing is there"cant see log'sThe console is full of CSP warnings and errors but none of your [IntegrationApp] logs. This means intedrat1onApponcL1ck Is never belno called at all — the burton click isn't reachine that tunction.Check1. Is the button actually calling integrationApponclick? - the click handler might be bound to adifferent method or the component re-rendered2. Is the frontend bundle rebuilt? - you may be running a stale build that doesn't include your changesSearched.intecrationAoo@nClickin tront-enc/src/ccents/connect/connect.vue (2)The binding looks correct. The most likely issue is a stale build — your changes aren't compiled yet. Add aquick test: put a console.log before the IntegrationAppClient instantiation that's impossible to miss:Reao connectvue :158-|44connect.vueNow after saving, hard refresh the browser ( Cmd+Shift+R ) to bust the cache. If you still don't see (Integratlonado incecrac1onApouncllck called arter cllckine tne outton, tne dev server nasn't olckea up tnechanges or the click event isn't reaching this component (e.g. another element is intercepting it).WAsk anything (884L)+ <> Code Claude Sonnet 4.62 differencescurrent versionallowMultipleConnections: false,console.log'lIntegrationAppl openNewConnection resolved:', JSON.stringify(connection)):if (iconnection connection.disconnected === true connection.connected === false) <showSnackbarError("A connection with your CRM could not be established",return;W Windsurf Teams 510:95 V UTF-8f 2 spaces...
|
NULL
|
6534610863620137170
|
NULL
|
click
|
ocr
|
NULL
|
PhpStormFileFditViewNavigateCodelaravelRefactonToo PhpStormFileFditViewNavigateCodelaravelRefactonToolsWindowHelpFV faVsco.s vT° JY-20692-fix-integration-app-[API_KEY] v>W testsU connect.lessV connect.vuedashboardW DeallnsightsD errorPages_ export-portalextension-installledinvitationvonconterenceMlavoutD LiveCoach› Locked>D login>D MeetingConsent_ mobilel onboardl•__mocks_•U_tests_V MobileAppDownk1 Onboard.lessV Onboard vueTs useProvidersSyncondemandplaybackD playlistsW Settingsshared_ SoftphoneCoach• Usegicons_leaminsightscomposaoes> M7 directives> M7 helpers> → locales>_ pluginsrouter• storeD utilsus main.jsTs types.a.tsJstypes.jsI Vite= owsersistE env defaultC AutomatedReportsService.phpC) SendReportJob.phpC SendReportMailJob.php= custom.loc= aravel.logc SF liminny@localhostU scratch_1,jsonconnect.vueV Onboard.vue XC ReportController.phpTokenBuilder.phpc leamsetuocontroller.onppnp apl.ono< Hs local liminnyalocalnost4 console [EUlC CrmEntityRepository.pho(iii) crm configurations [EU]© Filesystem.php© Team.phpC RequestGenerateReportJob.php© CreateHeldActivityEvent.php© TrackProviderInstalledEvent.phpOpportunitySyncTrait.phpC Opportunity.phpT InteractsWithPivotTable.phgOpoorunityupdaled.onoOpoonunitystadeupaated.onoc) FventService?rovider.onm(c OnnortunitvPendindAiAnalvsisatterstadechanded.omC RunOpportunityAiAnalysis.phpC ProcessAlAutomationAnalysiskesults.onv© ImportOpportunityBatch.php¿ console IPRODIIc console [STAGING]<script>379setup() (const crmconnectintegrationApp = async function O 1477const integrationApp = new IntegrationAppClientetoken: crmToken.value.V1V9 AVTImportBatchJobTrait.php(C) Service.phpcachedStagesCcW1018trait upportunitysynctraitA33 V2 V19private function resolverorecastcategory(?string pforecastCategory): stringi.• 5062 usagesprivate tunecion importexternalele uabatatarray epropertzes, zht sopportunity ru*509const connection = awalt integrationApp. integration(crmbetalls.name)ovenvevronnectlonssnowPoweredby: talse,aLLowulrloleconnectons. tauseF):1050§507510console.Log(' [IntegrationAn&openNewConnection resolved:', JSON.stringify(connection)):$crmFields = $this->getOpportunitySyncableFields:Sthis->inportOpportunitvCrmFieldDataSproperties. ScrmFields.lSOOOONUILE103310341035104810491056105111054Lusaees/**private function importupportunatycontacts (Opportunaty sopportunity, array sas:516* Sync opportunity contacts using differentzal approach* Inis compares current vs new associacions and only makes necessary changes=513=5171518519if (!connection | connection.disconnected === true | connection.connected === false) ishowsnackbarErrord"A connection with your cRM could not be established".);returnhtry 1consr saverequest = valt axos.oos col vnccoraron-doo-connecr1076107110771078107910861081108410841093SZ11 usage522private tunccion syncupporcunttyconcactsultterenclat upporcunicy gopportunity.523524525private function getCurrentContactCrmIds(Opportunity Sopportunity): array.....526— 52752815291550if (saveRequest.data &x saveRequest.data.success === true) ‹** If all is good refresh the page here *return location.reloadhlusageprivate function logcontactAssoclationchangesOpportunity sopportunityarray scurrenccontacuermias,array scontaccassoclatlons.array scontacusioAdd,array +concacustokemove0: void {...}chrow new trror(saverequest.daca.message):f catch (error) {console.looterror).snowonackoarerrorunoriauzerrror error.054=533534548=5495501const preparelntegrationAppConnection = async function O k...rAif (crmRequired) 1Prepare the crm token only if the CRM is requiredLocal CnandesShelfLogconsoeLog xChanges 10 files=.env.local app(© ActivityController.php app/Http/Controllers/APlV connect.vue front-end/src/components/connect(C JiminnyDebugCommand.php app/Console/Commandsphp logging.php contigV Onboard.vue front-end/src/components/onboard© Playbackservice.php app/ServicesC ReportController.php app/Http/Controllers/Webhook(C) SyncTolntercom.ohp app/.Jobs/Teampnp weo.ono rouresUnversioned Files 8 filesslae-by-side viewerDo not ignore -Mionlleht worasmboocterc_tront-enc/src/comoonents/onooard/onboard.vueshowrowereoby: tauseaLLoyuuroleconneccons. tause.7);if (iconnection connection.disconnected === truelshowSnackbarError"A connection with your CRM could not be established".):return;• Support Daily • in 57 mA100% 145Fri 17 Apr 14:03:20AutomatedRenortsCommandTestCascadeAutomated Report RetDebugging OpportunitUpdate Connection Lcconnect.vue tront-end/src/components/connecnothing is there"cant see log'sThe console is full of CSP warnings and errors but none of your [IntegrationApp] logs. This means intedrat1onApponcL1ck Is never belno called at all — the burton click isn't reachine that tunction.Check1. Is the button actually calling integrationApponclick? - the click handler might be bound to adifferent method or the component re-rendered2. Is the frontend bundle rebuilt? - you may be running a stale build that doesn't include your changesSearched.intecrationAoo@nClickin tront-enc/src/ccents/connect/connect.vue (2)The binding looks correct. The most likely issue is a stale build — your changes aren't compiled yet. Add aquick test: put a console.log before the IntegrationAppClient instantiation that's impossible to miss:Reao connectvue :158-|44connect.vueNow after saving, hard refresh the browser ( Cmd+Shift+R ) to bust the cache. If you still don't see (Integratlonado incecrac1onApouncllck called arter cllckine tne outton, tne dev server nasn't olckea up tnechanges or the click event isn't reaching this component (e.g. another element is intercepting it).WAsk anything (884L)+ <> Code Claude Sonnet 4.62 differencescurrent versionallowMultipleConnections: false,console.log'lIntegrationAppl openNewConnection resolved:', JSON.stringify(connection)):if (iconnection connection.disconnected === true connection.connected === false) <showSnackbarError("A connection with your CRM could not be established",return;W Windsurf Teams 510:95 V UTF-8f 2 spaces...
|
NULL
|
|
47061
|
NULL
|
0
|
2026-04-17T11:03:21.057156+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776423801057_m1.jpg...
|
NULL
|
NULL
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
FirefoxFileEditViewHistoryBookmarksProfilesToolsWi FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelplab|Support Daily - in 57 m100% <47APP (-zsh)X4-zshDOCKER-881DEV (docker)O ₴2APP (-zsh)X3-zsh./public/vue-assets/assets/ondemand-DkNR1-pf.js:./public/vue-assets/assets/CrmLink-DKYsnHnx.js./public/vue-assets/assets/liquor-tree-COUefof4.js./public/vue-assets/assets/DealRiskList-BWvQyROv.js:./public/vue-assets/assets/AskAnything-BNRpAA8H.js:./public/vue-assets/assets/lib-CwM9toD2.js./public/vue-assets/assets/AppFormField-Cd83royC.js./public/vue-assets/assets/deal-view-BHTz2Ksy.js:./public/vue-assets/assets/exports-D1lmea40.js../public/vue-assets/assets/playlists-5wFR1ij2.js../public/vue-assets/assets/callScoringTemplates-zeRn40ul.js../public/vue-assets/assets/_copy0bject-USkOnlaQ.js../public/vue-assets/assets/pusher-znYCfz7U.js./public/vue-assets/assets/onboard-D1qld9L0.js./public/vue-assets/assets/StatusBadge-CbqQ5gnA.js./public/vue-assets/assets/kiosk-DSF1ebGq.js./public/vue-assets/assets/preload-helper-DCvhahzG.js../public/vue-assets/assets/deal-insights-LnukdLUQ.js../public/vue-assets/assets/ListView-Bcd0qibH.js:./public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js./public/vue-assets/assets/WelcomeLayout-B6wd32HG.js../public/vue-assets/assets/dashboard-C4k4MPim.js:./public/vue-assets/assets/emoji-input-CSq87OVy.js../public/vue-assets/assets/AppButton-D3qMdODr.js../public/vue-assets/assets/sentry-BQx81U9A.js:./public/vue-assets/assets/OrgSettingsLayout-BQgZ11_y.js./public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js./public/vue-assets/assets/playback-D_95E_To.js./public/vue-assets/assets/index.module-Bjlhgfdl.js./public/vue-assets/assets/intl-tel-input-BW4mv40Q.js../public/vue-assets/assets/team-insights-NuK2ryxe.js../public/vue-assets/assets/popper-CQwVcrX4.js../public/vue-assets/assets/PhoneField-CwCIoAYm.js./public/vue-assets/assets/live-C1SbBwo3.js../public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BN0485xV.js../public/vue-assets/assets/index-CAouXZsY.js:./public/vue-assets/assets/logged-in-layout-ehXyHVjH.js• ₴526.88kB27.91kB30.75kB34.39kB39.50kB39.69kB41.91kB43.22kB47.84kB48.28kB55.13kB61.28kB62.98kB63.11kB64.66kB79.60kB82.59kB94.84kB115.71kB117.59 kB120.67kB128.71kB129.28 kB133.44kB164.28kB176.33kB180.40kB198.79kB218.14kB264.94 kВ298.57kB307.13kB343.99kB367.43kB689.63kB825.23 kB1,402.70 kB[plugin builtin:vite-reporter](!) Some chunks are larger than 500 kB after minification. Consider:- Using dynamic import() to code-split the application- Use build.rolldown0ptions.output.codeSplitting to improve chunking: [URL_WITH_CREDENTIALS] 88APP...
|
NULL
|
-3177134468110149169
|
NULL
|
click
|
ocr
|
NULL
|
FirefoxFileEditViewHistoryBookmarksProfilesToolsWi FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelplab|Support Daily - in 57 m100% <47APP (-zsh)X4-zshDOCKER-881DEV (docker)O ₴2APP (-zsh)X3-zsh./public/vue-assets/assets/ondemand-DkNR1-pf.js:./public/vue-assets/assets/CrmLink-DKYsnHnx.js./public/vue-assets/assets/liquor-tree-COUefof4.js./public/vue-assets/assets/DealRiskList-BWvQyROv.js:./public/vue-assets/assets/AskAnything-BNRpAA8H.js:./public/vue-assets/assets/lib-CwM9toD2.js./public/vue-assets/assets/AppFormField-Cd83royC.js./public/vue-assets/assets/deal-view-BHTz2Ksy.js:./public/vue-assets/assets/exports-D1lmea40.js../public/vue-assets/assets/playlists-5wFR1ij2.js../public/vue-assets/assets/callScoringTemplates-zeRn40ul.js../public/vue-assets/assets/_copy0bject-USkOnlaQ.js../public/vue-assets/assets/pusher-znYCfz7U.js./public/vue-assets/assets/onboard-D1qld9L0.js./public/vue-assets/assets/StatusBadge-CbqQ5gnA.js./public/vue-assets/assets/kiosk-DSF1ebGq.js./public/vue-assets/assets/preload-helper-DCvhahzG.js../public/vue-assets/assets/deal-insights-LnukdLUQ.js../public/vue-assets/assets/ListView-Bcd0qibH.js:./public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js./public/vue-assets/assets/WelcomeLayout-B6wd32HG.js../public/vue-assets/assets/dashboard-C4k4MPim.js:./public/vue-assets/assets/emoji-input-CSq87OVy.js../public/vue-assets/assets/AppButton-D3qMdODr.js../public/vue-assets/assets/sentry-BQx81U9A.js:./public/vue-assets/assets/OrgSettingsLayout-BQgZ11_y.js./public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js./public/vue-assets/assets/playback-D_95E_To.js./public/vue-assets/assets/index.module-Bjlhgfdl.js./public/vue-assets/assets/intl-tel-input-BW4mv40Q.js../public/vue-assets/assets/team-insights-NuK2ryxe.js../public/vue-assets/assets/popper-CQwVcrX4.js../public/vue-assets/assets/PhoneField-CwCIoAYm.js./public/vue-assets/assets/live-C1SbBwo3.js../public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BN0485xV.js../public/vue-assets/assets/index-CAouXZsY.js:./public/vue-assets/assets/logged-in-layout-ehXyHVjH.js• ₴526.88kB27.91kB30.75kB34.39kB39.50kB39.69kB41.91kB43.22kB47.84kB48.28kB55.13kB61.28kB62.98kB63.11kB64.66kB79.60kB82.59kB94.84kB115.71kB117.59 kB120.67kB128.71kB129.28 kB133.44kB164.28kB176.33kB180.40kB198.79kB218.14kB264.94 kВ298.57kB307.13kB343.99kB367.43kB689.63kB825.23 kB1,402.70 kB[plugin builtin:vite-reporter](!) Some chunks are larger than 500 kB after minification. Consider:- Using dynamic import() to code-split the application- Use build.rolldown0ptions.output.codeSplitting to improve chunking: [URL_WITH_CREDENTIALS] 88APP...
|
NULL
|
|
46970
|
NULL
|
0
|
2026-04-17T10:58:19.608434+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776423499608_m2.jpg...
|
PhpStorm
|
faVsco.js – ~/jiminny/app/front-end/src/components faVsco.js – ~/jiminny/app/front-end/src/components/connect/connect.vue...
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20692-fix-integration- Project: faVsco.js, menu
JY-20692-fix-integration-app-[API_KEY], menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
cachedStages
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
2/4
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Code changed:
Hide
Sync Changes
Hide This Notification
33
2
19
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use HubSpot\Client\Crm\Deals\Model\CollectionResponseAssociatedId;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Models\Account;
use Exception;
use Jiminny\Component\DealInsights\Forecast\Forecast;
use Jiminny\Jobs\Crm\MatchActivitiesToNewOpportunity;
use Jiminny\Models\Contact;
use Jiminny\Models\Crm\BusinessProcess;
use Jiminny\Exceptions\CrmException;
use Jiminny\Models\Opportunity;
use Illuminate\Support\Collection;
use Jiminny\Models\Stage;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Crm\Hubspot\DealFieldsService;
use Jiminny\Services\Crm\Hubspot\OpportunitySyncStrategy\HubspotSingleSyncStrategy;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Services\Crm\OpportunitySyncStrategyResolver;
use Jiminny\Utils\CurrencyFormatter;
/**
* Optimized sync methods for better performance
* These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains
*/
trait OpportunitySyncTrait
{
private const int BATCH_SIZE = 100;
private const int BATCH_PROCESS_SIZE = 800;
protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;
protected CrmEntityRepository $crmEntityRepository;
protected DealFieldsService $dealFieldsService;
private ?array $cachedClosedDealStages = null;
private array $cachedBusinessProcesses = [];
private array $cachedStages = [];
public function syncOpportunities(array $parameters, ?string $strategy = null): int
{
$strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);
$parameters['config'] = $this->config;
$syncCount = 0;
$reportedTotal = 0;
$lastSyncedId = [];
try {
foreach ($strategies as $strategyName => $syncStrategy) {
$this->logger->info(
'[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .
$strategyName
);
$total = 0;
$lastId = null;
$buffer = [];
// HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies
foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {
$buffer[] = $hsOpportunity;
// process every 800 rows (fits < 1 000 association limit)
if (\count($buffer) >= self::BATCH_PROCESS_SIZE) {
$syncCount += $this->processOpportunityBatch($buffer);
$buffer = [];
}
}
// leftovers
if ($buffer) {
$syncCount += $this->processOpportunityBatch($buffer);
}
$reportedTotal += $total;
$lastSyncedId = $lastId;
}
} catch (\HubSpot\Client\Crm\Deals\ApiException | CrmException $e) {
$this->handleSyncException($e, $parameters);
}
$this->logger->info(
'[HubSpot] Synced opportunities',
[
'team' => $this->team->getId(),
'sync_count' => $syncCount,
'total' => $reportedTotal,
'last_synced_id' => $lastSyncedId,
]
);
return $reportedTotal;
}
private function handleSyncException(\Throwable $e, array $parameters): void
{
if (($parameters['since'] ?? null) instanceof Carbon) {
$parameters['since'] = $parameters['since']->toDateTimeString();
}
$parameters['config'] = $this->config->getId();
$this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [
'teamId' => $this->team->getUuid(),
'parameters' => $parameters,
'reason' => $e->getMessage(),
]);
}
/**
* @inheritdoc
*/
public function syncOpportunity(string $crmId): ?Opportunity
{
$strategy = $this->opportunitySyncStrategyResolver->resolve(
$this->config,
OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,
);
$parameters = [
'config' => $this->config,
'crm_id' => $crmId,
];
try {
if (! $strategy instanceof HubspotSingleSyncStrategy) {
throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');
}
$hsOpportunity = $strategy->fetchOpportunity($parameters);
} catch (\HubSpot\Client\Crm\Deals\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
$hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);
return $this->importOrUpdateOpportunity($hsOpportunity);
}
/**
* Process webhook-collected opportunity batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportOpportunityBatch jobs for batch processing.
*
* @return int Number of opportunity IDs dispatched to jobs
*/
public function batchSyncOpportunities(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,
$configId
);
}
/**
* Import a batch of opportunities by their CRM IDs.
* Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().
*
* @param array<string> $crmIds HubSpot deal CRM IDs
*
* @return array{success: array, failed_ids: array, errors?: array<string, string>}
*/
public function importOpportunityBatchByIds(array $crmIds): array
{
$fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);
$allDeals = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
$deals = $this->client->getOpportunitiesByIds($chunk, $fields);
foreach ($deals as $deal) {
$allDeals[] = $deal;
}
}
// IDs not returned by HubSpot are likely deleted or inaccessible deals.
// These are not failures — retrying won't bring them back.
$fetchedIds = array_map('strval', array_column($allDeals, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allDeals),
]);
}
if (empty($allDeals)) {
return ['success' => [], 'failed_ids' => []];
}
return $this->importOpportunityBatch($allDeals);
}
private function getClosedDealStages(): array
{
if ($this->cachedClosedDealStages !== null) {
return $this->cachedClosedDealStages;
}
$stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);
$data = [
'lost' => [],
'won' => [],
];
foreach ($stages as $stage) {
if ($stage->probability == 0.00) {
$data['lost'][] = $stage->crm_provider_id;
}
if ($stage->probability == 100.00) {
$data['won'][] = $stage->crm_provider_id;
}
}
$this->cachedClosedDealStages = $data;
return $data;
}
/**
* Import deals into the database with pre-fetched associations.
*
* API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT
* caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()
* where Laravel retries the whole job with backoff. After all retries exhausted,
* failed() requeues all IDs to Redis.
*
* The per-deal loop catches exceptions individually. A deal can end up in three states:
* - success: imported/updated successfully
* - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)
* These are permanent issues — retrying won't fix them.
* - skipped (null): missing dependencies (no account, unknown pipeline/stage).
* This is acceptable — the deal cannot be imported until those exist.
*/
private function importOpportunityBatch(array $deals): array
{
$syncedOpportunities = [
'success' => [],
'failed_ids' => [],
];
$dealIds = array_column($deals, 'id');
// Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the
// queue job retries the whole batch and eventually requeues all deal IDs back to Redis.
try {
$companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');
$contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');
$associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);
$existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(
$this->config,
array_map('strval', $dealIds)
);
$existingCrmIdSet = array_flip($existingCrmIds);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [
'teamId' => $this->team->getId(),
'dealCount' => count($dealIds),
'error' => $e->getMessage(),
]);
throw $e;
}
foreach ($deals as $deal) {
try {
$deal['associations'] = $this->prepareAssociationsForOpportunity(
$deal['id'],
$companyAssociations,
$contactAssociations,
$associationsData
);
$syncedOpportunity = $this->importOrUpdateOpportunity(
$deal,
isset($existingCrmIdSet[(string) $deal['id']])
);
if ($syncedOpportunity) {
$syncedOpportunities['success'][] = $syncedOpportunity;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [
'teamId' => $this->team->getId(),
'crmId' => $deal['id'],
'error' => $e->getMessage(),
]);
$syncedOpportunities['failed_ids'][] = $deal['id'];
$syncedOpportunities['errors'][$deal['id']] = $e->getMessage();
}
}
return $syncedOpportunities;
}
/**
* Prepare associated entities for opportunities with optimized batch processing
* Returns structured data with CRM ID to DB ID mappings for each opportunity
*/
private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array
{
// Step 1: Collect all unique company and contact IDs from associations
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
// Step 2: Batch sync missing entities and get CRM ID to DB ID mappings
$companyIdMappings = [];
$contactIdMappings = [];
if (! empty($allCompanyIds)) {
$companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);
}
if (! empty($allContactIds)) {
$contactIdMappings = $this->prepareAssociatedContacts($allContactIds);
}
return [
'company_id_mappings' => $companyIdMappings,
'contact_id_mappings' => $contactIdMappings,
];
}
/**
* Flatten association data to get unique IDs
*/
private function flattenAssociationIds(array $associations): array
{
$ids = [];
foreach ($associations as $dealAssociations) {
if (is_array($dealAssociations)) {
foreach ($dealAssociations as $id) {
$ids[$id] = true;
}
}
}
return array_keys($ids);
}
/**
* Batch sync missing accounts
*/
private function prepareAssociatedAccounts(array $companyIds): array
{
// Find which accounts already exist
$existingAccounts = $this->crmEntityRepository
->findAccountsByExternalIds($this->config, $companyIds);
$existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();
$existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {
return [$account->getCrmProviderId() => $account->getId()];
})->toArray();
$missingCompanyIds = array_diff($companyIds, $existingCompanyIds);
if (empty($missingCompanyIds)) {
return $existingAccountsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [
'teamId' => $this->team->getUuid(),
'total_companies' => count($companyIds),
'existing_companies' => count($existingCompanyIds),
'missing_companies' => count($missingCompanyIds),
]);
// we already have limit on opportunity ids count
// Initialize variable before try block
$syncedAccountsData = [];
try {
$syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [
'size' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
$syncedAccountsData = [];
}
return $existingAccountsData + $syncedAccountsData;
}
/**
* Prepare associated contacts - find existing and sync missing ones
* Returns mapping of CRM ID to DB ID
*/
private function prepareAssociatedContacts(array $contactIds): array
{
// Find which contacts already exist
$existingContacts = $this->crmEntityRepository
->findContactsByExternalIds($this->config, $contactIds);
$existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();
// Create mapping for existing contacts
$existingContactsData = $existingContacts->mapWithKeys(function ($contact) {
return [$contact->getCrmProviderId() => $contact->getId()];
})->toArray();
$missingContactIds = array_diff($contactIds, $existingContactIds);
if (empty($missingContactIds)) {
return $existingContactsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [
'teamId' => $this->team->getUuid(),
'total_contacts' => count($contactIds),
'existing_contacts' => count($existingContactIds),
'missing_contacts' => count($missingContactIds),
]);
// Sync missing contacts using batch API
try {
$syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [
'size' => count($missingContactIds),
'error' => $e->getMessage(),
]);
$syncedContactsData = [];
}
return $existingContactsData + $syncedContactsData;
}
private function batchSyncCrmObjects(string $objectType, array $crmIds): array
{
$syncObjects = [];
$crmObjectIds = array_values($crmIds);
foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {
try {
$objects = $objectType === 'companies' ?
$this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :
$this->client->getContactsByIds($chunk, $this->getContactFields());
foreach ($objects as $objectId => $objectData) {
$this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [
'requested_count' => count($chunk),
'synced_count' => count($objects),
]);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [
'ids' => $chunk,
'error' => $e->getMessage(),
]);
}
}
return $syncObjects;
}
private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void
{
try {
$object = $objectType === 'companies' ?
$this->importAccount($objectData) :
$this->importContact($objectData);
if ($object) {
$syncObjects[$object->getCrmProviderId()] = $object->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [
'id' => $objectId,
'error' => $e->getMessage(),
]);
}
}
/**
* Prepare associations for a single opportunity
*
* The return value is an array with the following structure:
* [
* 'companies' => [
* $companyCrmId => $companyId,
* ...
* ],
* 'contacts' => [
* $contactCrmId => $contactId,
* ...
* ],
* 'account_id' => $accountId,
* ]
*/
private function prepareAssociationsForOpportunity(
string $oppCrmId,
array $companyAssociations,
array $contactAssociations,
array $associationsData
): array {
$associations = [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
$oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];
foreach ($oppCompanyIds as $companyCrmId) {
if (isset($associationsData['company_id_mappings'][$companyCrmId])) {
$associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];
// Set primary account (first company becomes primary account)
if ($associations['account_id'] === null) {
$associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];
}
}
}
$oppContactIds = $contactAssociations[$oppCrmId] ?? [];
foreach ($oppContactIds as $contactCrmId) {
if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {
$associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];
}
}
return $associations;
}
/**
* Update only associations for an opportunity
*/
private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void
{
// Update contact associations
$this->importOpportunityContacts($opportunity, $associations['contacts']);
// Update company (account) associations
$this->updateOpportunityAccount($opportunity, $associations['account_id']);
}
/**
* Remove all contact associations from an opportunity
*/
private function removeAllOpportunityContacts(Opportunity $opportunity): void
{
$currentCount = (int) $opportunity->contacts()->count();
if ($currentCount > 0) {
$opportunity->contacts()->detach();
$this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_count' => $currentCount,
]);
}
}
private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void
{
if ($accountId === null) {
// No account ID provided - keep current account
return;
}
$currentAccountId = $opportunity->getAccountId();
// Only update if account has changed
if ($currentAccountId !== $accountId) {
$opportunity->account_id = $accountId;
$opportunity->save();
$this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [
'opportunity_id' => $opportunity->getId(),
'old_account_id' => $currentAccountId,
'new_account_id' => $accountId,
]);
}
}
/**
* Find existing opportunities by external IDs (OPTIMIZED VERSION)
* Uses batch query for better performance
*/
private function findExistingOpportunities(array $crmIds): Collection
{
return $this->crmEntityRepository
->findOpportunitiesByExternalIds($this->config, $crmIds);
}
private function processOpportunityBatch(array $opportunities): int
{
$syncedOpportunities = $this->importOpportunityBatch($opportunities);
return count($syncedOpportunities['success'] ?? []);
}
/**
* Convert single deal associations from HubSpot format to internal format
* Handles both HubSpot SDK objects and array formats
*
* @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed
*
* @return array Processed associations with DB IDs
*/
private function convertDealAssociations(array $opportunityAssociations): array
{
$associations = $this->initializeAssociationsStructure();
if (empty($opportunityAssociations)) {
return $associations;
}
$associationIds = $this->extractAssociationIds($opportunityAssociations);
$this->processCompanyAssociations($associationIds, $associations);
$this->processContactAssociations($associationIds, $associations);
return $associations;
}
private function initializeAssociationsStructure(): array
{
return [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
}
private function extractAssociationIds(array $opportunityAssociations): array
{
$associationIds = [];
foreach ($opportunityAssociations as $type => $associationData) {
if (! empty($associationData)) {
$associationIds[$type] = $this->convertSingleDealAssociations($associationData);
}
}
return $associationIds;
}
private function processCompanyAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['companies'])) {
return;
}
$companyId = $associationIds['companies'][0];
$account = $this->findOrSyncAccount($companyId);
if ($account instanceof Account) {
$associations['companies'][$companyId] = $account->getId();
$associations['account_id'] = $account->getId();
}
}
private function processContactAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['contacts'])) {
return;
}
foreach ($associationIds['contacts'] as $contactId) {
$contact = $this->findOrSyncContact($contactId);
if ($contact instanceof Contact) {
$associations['contacts'][$contactId] = $contact->getId();
}
}
}
private function findOrSyncAccount(string $companyId): ?Account
{
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);
if (! $account instanceof Account) {
$account = $this->syncAccount($companyId);
}
return $account;
}
private function findOrSyncContact(string $contactId): ?Contact
{
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);
if (! $contact instanceof Contact) {
$contact = $this->syncContact($contactId);
}
return $contact;
}
private function convertSingleDealAssociations($opportunityAssociations = null): array
{
$associationData = [];
if ($opportunityAssociations === null) {
return $associationData;
}
// Handle array input (from extractAssociationIds)
if (is_array($opportunityAssociations)) {
return $opportunityAssociations;
}
// Handle CollectionResponseAssociatedId object
if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {
foreach ($opportunityAssociations->getResults() as $association) {
$associationData[] = $association->getId();
}
}
return $associationData;
}
private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity
{
if (empty($crmData['properties'])) {
return null;
}
$crmId = (string) $crmData['id'];
$properties = $crmData['properties'];
$associations = $crmData['associations'] ?? [];
$opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(
$this->config,
$crmId
);
if ($opportunityExists) {
return $this->updateOpportunity($crmId, $properties, $associations);
} else {
return $this->createOpportunity($crmId, $properties, $associations);
}
}
/**
* Create new opportunity
*/
private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity
{
$accountId = $this->resolveAccountId($associations);
if (! $accountId) {
return null;
}
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
if (! $businessProcess) {
return null;
}
$stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);
if (! $stage) {
return null;
}
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->importOpportunityContacts($opportunity, $associations['contacts']);
if ($opportunity->wasRecentlyCreated) {
MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());
}
return $opportunity;
}
/**
* Update existing opportunity
*/
private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity
{
$accountId = $this->resolveAccountId($associations);
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
$stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->updateOpportunityAssociations($opportunity, $associations);
return $opportunity;
}
private function resolveAccountId(array $associations): ?int
{
if (! empty($associations['accountId'])) {
return $associations['accountId'];
}
if (empty($associations)) {
return null;
}
// we can't resolve multiple account ids (currently SDK returns one company)
foreach ($associations['companies'] as $accountId) {
return $accountId;
}
return null;
}
private function buildOpportunityData(
array $properties,
?int $accountId,
?BusinessProcess $businessProcess,
?Stage $stage
): array {
$ownerId = null;
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$name = 'Unknown';
if (isset($properties['dealname'])) {
$name = mb_strimwidth($properties['dealname'], 0, 128);
}
$amount = $this->resolveAmount($properties);
$currency = $properties['deal_currency_code'] ?? null;
$closeDate = null;
if (! empty($properties['closedate'])) {
$closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');
}
$remotelyCreatedAt = null;
if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {
$date = $this->parseCleanDatetime($properties['createdate']);
$remotelyCreatedAt = $date?->format('Y-m-d H:i:s');
}
$closedStages = $this->getClosedDealStages();
$isWon = in_array($properties['dealstage'], $closedStages['won']);
$isLost = in_array($properties['dealstage'], $closedStages['lost']);
$data = [
'team_id' => $this->team->getId(),
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => $name,
'value' => ! empty($amount) ? $amount : null,
'currency_code' => CurrencyFormatter::formatCode($currency),
'close_date' => $closeDate,
'is_closed' => $isWon || $isLost,
'is_won' => $isWon,
'remotely_created_at' => $remotelyCreatedAt,
'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),
'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),
];
if ($accountId) {
$data['account_id'] = $accountId;
}
if ($stage) {
$data['stage_id'] = $stage->id;
}
if ($businessProcess) {
$recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);
if ($recordType) {
$data['record_type_id'] = $recordType->id;
}
}
return $data;
}
private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess
{
if ($pipelineId === null) {
return null;
}
if (isset($this->cachedBusinessProcesses[$pipelineId])) {
return $this->cachedBusinessProcesses[$pipelineId];
}
$businessProcess = $this->getBusinessProcess($pipelineId);
if (! $businessProcess instanceof BusinessProcess) {
$this->importStages();
$businessProcess = $this->getBusinessProcess($pipelineId);
}
if (! $businessProcess instanceof BusinessProcess) {
$this->logger->info(
'[HubSpot] Deal is not attached to a pipeline',
[
'pipeline' => $pipelineId]
);
}
$this->cachedBusinessProcesses[$pipelineId] = $businessProcess;
return $businessProcess;
}
private function getBusinessProcess(string $pipelineId): ?BusinessProcess
{
return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);
}
private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage
{
if (empty($stageId)) {
return null;
}
$cacheKey = $businessProcess->getId() . ':' . $stageId;
if (isset($this->cachedStages[$cacheKey])) {
return $this->cachedStages[$cacheKey];
}
$stage = $this->crmEntityRepository->getPipelineStageByConditions(
$businessProcess,
[
'crm_provider_id' => $stageId,
'type' => Stage::TYPE_OPPORTUNITY,
]
);
if ($stage === null) {
$this->importStages(null, $stageId);
}
if ($stage === null) {
$this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);
}
$this->cachedStages[$cacheKey] = $stage;
return $stage;
}
private function resolveAmount(array $properties): ?string
{
$amount = null;
if (! empty($properties['amount'])) {
$amount = str_replace(',', '', $properties['amount']);
}
if ($this->config->hasDefaultCurrencyFieldSet()) {
$valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();
$amount = $properties[$valueFieldName] ?? $amount;
}
return $amount;
}
private function parseCleanDatetime(string $datetime): ?Carbon
{
// Treat pre-1980 values as invalid
$minValidDate = Carbon::parse('1980-01-01 00:00:00');
try {
$date = Carbon::parse($datetime);
if ($minValidDate->gt($date)) {
return null;
}
return $date;
} catch (Exception) {
return null; // On parse error, treat as null
}
}
private function resolveDealProbability(?string $stageProbability): int
{
if ($stageProbability === null) {
return 0;
}
$probability = (float) $stageProbability;
return $probability > 1 ? 0 : (int) ($probability * 100);
}
private function resolveForecastCategory(?string $forecastCategory): string
{
if (! $forecastCategory) {
return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;
}
$forecastCategory = str_replace('_', ' ', $forecastCategory);
return ucwords(strtolower($forecastCategory));
}
private function importExternalFieldData(array $properties, int $opportunityId): void
{
$crmFields = $this->getOpportunitySyncableFields();
$this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);
}
private function importOpportunityContacts(Opportunity $opportunity, array $associations): void
{
// Handle empty or missing contact associations
if (empty($associations)) {
// Remove all existing contact associations if none provided
$this->removeAllOpportunityContacts($opportunity);
return;
}
// Use differential sync approach for better performance and accuracy
$this->syncOpportunityContactsDifferential($opportunity, $associations);
}
/**
* Sync opportunity contacts using differential approach
* This compares current vs new associations and only makes necessary changes
*/
private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void
{
$currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);
$contactAssociationIds = array_keys($contactAssociations);
$contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);
$contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);
if (empty($contactsToAdd) && empty($contactsToRemove)) {
return;
}
$this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);
$this->removeContactAssociations($opportunity, $contactsToRemove);
$this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);
}
private function getCurrentContactCrmIds(Opportunity $opportunity): array
{
return $opportunity->contacts()
->pluck('contacts.crm_provider_id')
->toArray();
}
private function logContactAssociationChanges(
Opportunity $opportunity,
array $currentContactCrmIds,
array $contactAssociations,
array $contactsToAdd,
array $contactsToRemove
): void {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [
'opportunity_id' => $opportunity->getId(),
'current_contacts' => $currentContactCrmIds,
'new_contacts' => $contactAssociations,
'contacts_to_add' => $contactsToAdd,
'contacts_to_remove' => $contactsToRemove,
]);
}
private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void
{
if (empty($contactsToRemove)) {
return;
}
$contactsToDetach = $opportunity->contacts()
->whereIn('contacts.crm_provider_id', $contactsToRemove)
->pluck('contacts.id')
->toArray();
if (! empty($contactsToDetach)) {
$opportunity->contacts()->detach($contactsToDetach);
$this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_contact_crm_ids' => $contactsToRemove,
'removed_contact_count' => count($contactsToDetach),
]);
}
}
private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void
{
if (empty($contactsToAdd)) {
return;
}
$contactsAdded = [];
foreach ($contactsToAdd as $crmId) {
$id = $contactAssociations[$crmId];
if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {
$contactsAdded[] = $crmId;
}
}
$this->logAddedContacts($opportunity, $contactsAdded);
}
private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool
{
try {
$contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);
if (! $contact) {
return false;
}
return $this->performContactAttachment($opportunity, $contact, $crmId);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [
'opportunity_id' => $opportunity->getId(),
'contact_crm_id' => $crmId,
'error' => $e->getMessage(),
]);
return false;
}
}
private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool
{
try {
$opportunity->contacts()->attach($contact->getId(), [
'crm_provider_id' => $crmId,
]);
return true;
} catch (\Illuminate\Database\QueryException $e) {
if (str_contains($e->getMessage(), 'Duplicate entry')) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [
'contact_id' => $contact->getId(),
'contact_crm_id' => $crmId,
'opportunity_id' => $opportunity->getId(),
]);
return false;
}
throw $e;
}
}
private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void
{
if (! empty($contactsAdded)) {
$this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [
'opportunity_id' => $opportunity->getId(),
'contacts_to_add_count' => count($contactsAdded),
'added_contact_crm_ids' => $contactsAdded,
'added_contacts_count' => count($contactsAdded),
]);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
1
Previous Highlighted Error
Next Highlighted Error
<template>
<WelcomeLayout
title="Account disconnected"
textPosition="center"
:icon="faUnlink"
:class="$style.layout"
>
<div :class="$style.container" v-if="providersLoaded">
<p>
<strong>
It looks like your {{ localProvider.displayName }} account has become
disconnected
</strong>
</p>
<p :class="$style.small">Please re-connect to continue</p>
<p v-if="isInIframe">
We'll open the {{ localProvider.displayName }} authentication in a new
tab. Please return here and refresh the page once complete
</p>
<GoogleLikeButton
v-if="localProvider.viaIntegrationApp && crmTokenLoaded"
as="a"
:key="localProvider.name"
:brand-logo="localProvider.name"
:class="$style.connectButton"
@click="integrationAppOnClick"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
<GoogleLikeButton
v-if="!localProvider.viaIntegrationApp"
as="a"
:key="localProvider.name"
:href="`/auth/redirect/${localProvider.name}`"
:target="target"
:brand-logo="localProvider.name"
:class="$style.connectButton"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
</div>
<BuildInfo />
<KioskBanner />
</WelcomeLayout>
</template>
<script>
import window from "window";
import axios from "axios";
import { faUnlink } from "@fortawesome/pro-regular-svg-icons";
import isInIframe from "@/utils/isInIframe";
import BuildInfo from "@/components/layout/BuildInfo/BuildInfo.vue";
import KioskBanner from "@/components/shared/KioskBanner/KioskBanner.vue";
import WelcomeLayout from "@/components/layout/WelcomeLayout/WelcomeLayout.vue";
import GoogleLikeButton from "@/components/shared/Buttons/GoogleLikeButton.vue";
import { showSnackbarError, normalizeError } from "@/utils/index";
import { IntegrationAppClient } from "@integration-app/sdk";
export default {
name: "ConnectPage",
components: {
BuildInfo,
KioskBanner,
WelcomeLayout,
GoogleLikeButton,
},
data() {
return {
...window.connectData,
crmToken: null,
faUnlink,
isInIframe,
providers: [],
providersLoaded: false,
crmTokenLoaded: false,
};
},
computed: {
localProvider() {
return this.providers.find((e) => e.name === this.provider);
},
target() {
return this.isInIframe ? "_blank" : null;
},
},
created() {
this.getProviders();
},
mounted() {
this.showErrors();
},
watch: {
providersLoaded() {
if (this.providersLoaded) {
this.prepareIntegrationAppConnection();
}
},
},
methods: {
showErrors() {
if (!this.error) return;
showSnackbarError(this.error, undefined, undefined, false);
},
unwrapEntityResponse({ data }) {
return data.map(({ icon, name, displayName, viaIntegrationApp }) => {
return { icon, name, displayName, viaIntegrationApp };
});
},
async getProviders() {
try {
const response = await axios.get("/api/v1/connect-providers");
this.providers = this.unwrapEntityResponse(response);
this.providersLoaded = true;
} catch {
showSnackbarError(
"An error occurred, while loading form data (connect providers).",
);
}
},
async prepareIntegrationAppConnection() {
if (this.localProvider.viaIntegrationApp) {
try {
const response = await axios.get("/api/v1/integration-app-token");
this.crmToken = response.data.token;
this.crmTokenLoaded = true;
} catch (error) {
console.log(error);
showSnackbarError(
`An error occurred while preparing the page.
Try refreshing, if the error persists get in touch with the Jiminny team.`,
);
}
}
},
async integrationAppOnClick() {
console.log('[IntegrationApp] integrationAppOnClick called');
const integrationApp = new IntegrationAppClient({
token: this.crmToken,
});
const connection = await integrationApp
.integration(this.localProvider.name)
.openNewConnection({
showPoweredBy: false,
allowMultipleConnections: false,
}).catch((err) => {
console.log('[IntegrationApp] openNewConnection rejected:', err);
return null;
});
console.log('[IntegrationApp] openNewConnection resolved:', JSON.stringify(connection));
// [IntegrationApp] openNewConnection resolved: {
// "id":"69e0b41a67d0068c2ca0b48e",
// "name":"Zoho CRM",
// "userId":"1ece66c8-feb1-4df1-b321-21607daf4623",
// "tenantId":"69e0b3faef3e7b6248189289",
// "isTest":false,
// "connected":true,
// "state":"READY",
// "errors":[],
// "integrationId":"66fe6c913202f3a165e3c14d",
// "externalAppId":"6671653e7e2d642e4e41b0fa",
// "authOptionKey":"",
// "createdAt":"2026-04-16T10:04:10.420Z",
// "updatedAt":"2026-04-16T10:04:10.575Z",
// "retryAttempts":0,
// "isDeactivated":false
// }
if (connection && connection.disconnected !== true && connection.connected !== false) {
console.log('[IntegrationApp] connection condition matched');
try {
const saveRequest = await axios.post(
"/api/v1/integration-app-connect",
);
if (saveRequest.data && saveRequest.data.success === true) {
/** If all is good refresh the page here */
window.location = "/dashboard";
return;
}
throw new Error(saveRequest.data.message);
} catch (error) {
console.log(error);
showSnackbarError(normalizeError(error));
}
}
},
},
};
</script>
<style module lang="less" src="./connect.less"></style>...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.03046875,"top":0.017361112,"width":0.0453125,"height":0.022222223},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20692-fix-integration-app-token-auth-response-change, menu","depth":5,"bounds":{"left":0.07578125,"top":0.017361112,"width":0.15898438,"height":0.022222223},"help_text":"Git Branch: JY-20692-fix-integration-app-token-auth-response-change","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.78515625,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsCommandTest","depth":6,"bounds":{"left":0.803125,"top":0.017361112,"width":0.09765625,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsCommandTest'","depth":6,"bounds":{"left":0.9007813,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsCommandTest'","depth":6,"bounds":{"left":0.9140625,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9273437,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96015626,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9734375,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9867188,"top":0.017361112,"width":0.013281226,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show Replace Field","depth":4,"bounds":{"left":0.12382813,"top":0.22083333,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Search History","depth":3,"bounds":{"left":0.13867188,"top":0.22013889,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"cachedStages","depth":4,"bounds":{"left":0.1515625,"top":0.22013889,"width":0.0515625,"height":0.013888889},"value":"cachedStages","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"bounds":{"left":0.21367188,"top":0.22013889,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Match Case","depth":3,"bounds":{"left":0.22539063,"top":0.22013889,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Words","depth":3,"bounds":{"left":0.23554687,"top":0.22013889,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Regex","depth":3,"bounds":{"left":0.24570313,"top":0.22013889,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Replace History","depth":3,"bounds":{"left":0.23320313,"top":1.0,"width":0.00859375,"height":0.0},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Replace","depth":4,"role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"bounds":{"left":0.23320313,"top":1.0,"width":0.00859375,"height":0.0},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Preserve case","depth":3,"bounds":{"left":0.23320313,"top":1.0,"width":0.00859375,"height":0.0},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2/4","depth":4,"bounds":{"left":0.26171875,"top":0.21944444,"width":0.030078124,"height":0.015277778},"role_description":"text"},{"role":"AXButton","text":"Previous Occurrence","depth":4,"bounds":{"left":0.29179686,"top":0.21875,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Occurrence","depth":4,"bounds":{"left":0.30195314,"top":0.21875,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Filter Search Results","depth":4,"bounds":{"left":0.31210938,"top":0.21875,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open in Window, Multiple Cursors","depth":4,"bounds":{"left":0.32226562,"top":0.21875,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Click to highlight","depth":4,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":4,"bounds":{"left":0.38320312,"top":0.21875,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.049609374,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"33","depth":4,"bounds":{"left":0.3421875,"top":0.24583334,"width":0.012109375,"height":0.013194445},"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.35664064,"top":0.24583334,"width":0.009375,"height":0.013194445},"role_description":"text"},{"role":"AXStaticText","text":"19","depth":4,"bounds":{"left":0.3683594,"top":0.24583334,"width":0.011328125,"height":0.013194445},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.3816406,"top":0.24444444,"width":0.00859375,"height":0.015972223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.39023438,"top":0.24444444,"width":0.008203125,"height":0.015972223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\CollectionResponseAssociatedId;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Models\\Account;\nuse Exception;\nuse Jiminny\\Component\\DealInsights\\Forecast\\Forecast;\nuse Jiminny\\Jobs\\Crm\\MatchActivitiesToNewOpportunity;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Crm\\BusinessProcess;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Models\\Opportunity;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DealFieldsService;\nuse Jiminny\\Services\\Crm\\Hubspot\\OpportunitySyncStrategy\\HubspotSingleSyncStrategy;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Services\\Crm\\OpportunitySyncStrategyResolver;\nuse Jiminny\\Utils\\CurrencyFormatter;\n\n/**\n * Optimized sync methods for better performance\n * These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains\n */\ntrait OpportunitySyncTrait\n{\n private const int BATCH_SIZE = 100;\n private const int BATCH_PROCESS_SIZE = 800;\n\n protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;\n protected CrmEntityRepository $crmEntityRepository;\n protected DealFieldsService $dealFieldsService;\n\n private ?array $cachedClosedDealStages = null;\n private array $cachedBusinessProcesses = [];\n private array $cachedStages = [];\n\n public function syncOpportunities(array $parameters, ?string $strategy = null): int\n {\n $strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);\n $parameters['config'] = $this->config;\n $syncCount = 0;\n $reportedTotal = 0;\n $lastSyncedId = [];\n\n try {\n foreach ($strategies as $strategyName => $syncStrategy) {\n $this->logger->info(\n '[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .\n $strategyName\n );\n\n $total = 0;\n $lastId = null;\n $buffer = [];\n\n // HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies\n foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {\n $buffer[] = $hsOpportunity;\n\n // process every 800 rows (fits < 1 000 association limit)\n if (\\count($buffer) >= self::BATCH_PROCESS_SIZE) {\n $syncCount += $this->processOpportunityBatch($buffer);\n $buffer = [];\n }\n }\n\n // leftovers\n if ($buffer) {\n $syncCount += $this->processOpportunityBatch($buffer);\n }\n\n $reportedTotal += $total;\n $lastSyncedId = $lastId;\n }\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException | CrmException $e) {\n $this->handleSyncException($e, $parameters);\n }\n\n $this->logger->info(\n '[HubSpot] Synced opportunities',\n [\n 'team' => $this->team->getId(),\n 'sync_count' => $syncCount,\n 'total' => $reportedTotal,\n 'last_synced_id' => $lastSyncedId,\n ]\n );\n\n return $reportedTotal;\n }\n\n private function handleSyncException(\\Throwable $e, array $parameters): void\n {\n if (($parameters['since'] ?? null) instanceof Carbon) {\n $parameters['since'] = $parameters['since']->toDateTimeString();\n }\n $parameters['config'] = $this->config->getId();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [\n 'teamId' => $this->team->getUuid(),\n 'parameters' => $parameters,\n 'reason' => $e->getMessage(),\n ]);\n }\n\n /**\n * @inheritdoc\n */\n public function syncOpportunity(string $crmId): ?Opportunity\n {\n $strategy = $this->opportunitySyncStrategyResolver->resolve(\n $this->config,\n OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,\n );\n\n $parameters = [\n 'config' => $this->config,\n 'crm_id' => $crmId,\n ];\n\n try {\n if (! $strategy instanceof HubspotSingleSyncStrategy) {\n throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');\n }\n\n $hsOpportunity = $strategy->fetchOpportunity($parameters);\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);\n\n return $this->importOrUpdateOpportunity($hsOpportunity);\n }\n\n /**\n * Process webhook-collected opportunity batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportOpportunityBatch jobs for batch processing.\n *\n * @return int Number of opportunity IDs dispatched to jobs\n */\n public function batchSyncOpportunities(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,\n $configId\n );\n }\n\n /**\n * Import a batch of opportunities by their CRM IDs.\n * Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().\n *\n * @param array<string> $crmIds HubSpot deal CRM IDs\n *\n * @return array{success: array, failed_ids: array, errors?: array<string, string>}\n */\n public function importOpportunityBatchByIds(array $crmIds): array\n {\n $fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);\n\n $allDeals = [];\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n $deals = $this->client->getOpportunitiesByIds($chunk, $fields);\n foreach ($deals as $deal) {\n $allDeals[] = $deal;\n }\n }\n\n // IDs not returned by HubSpot are likely deleted or inaccessible deals.\n // These are not failures — retrying won't bring them back.\n $fetchedIds = array_map('strval', array_column($allDeals, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allDeals),\n ]);\n }\n\n if (empty($allDeals)) {\n return ['success' => [], 'failed_ids' => []];\n }\n\n return $this->importOpportunityBatch($allDeals);\n }\n\n private function getClosedDealStages(): array\n {\n if ($this->cachedClosedDealStages !== null) {\n return $this->cachedClosedDealStages;\n }\n\n $stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);\n $data = [\n 'lost' => [],\n 'won' => [],\n ];\n\n foreach ($stages as $stage) {\n if ($stage->probability == 0.00) {\n $data['lost'][] = $stage->crm_provider_id;\n }\n if ($stage->probability == 100.00) {\n $data['won'][] = $stage->crm_provider_id;\n }\n }\n\n $this->cachedClosedDealStages = $data;\n\n return $data;\n }\n\n /**\n * Import deals into the database with pre-fetched associations.\n *\n * API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT\n * caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()\n * where Laravel retries the whole job with backoff. After all retries exhausted,\n * failed() requeues all IDs to Redis.\n *\n * The per-deal loop catches exceptions individually. A deal can end up in three states:\n * - success: imported/updated successfully\n * - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)\n * These are permanent issues — retrying won't fix them.\n * - skipped (null): missing dependencies (no account, unknown pipeline/stage).\n * This is acceptable — the deal cannot be imported until those exist.\n */\n private function importOpportunityBatch(array $deals): array\n {\n $syncedOpportunities = [\n 'success' => [],\n 'failed_ids' => [],\n ];\n $dealIds = array_column($deals, 'id');\n\n // Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the\n // queue job retries the whole batch and eventually requeues all deal IDs back to Redis.\n try {\n $companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');\n $contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');\n\n $associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);\n\n $existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(\n $this->config,\n array_map('strval', $dealIds)\n );\n $existingCrmIdSet = array_flip($existingCrmIds);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [\n 'teamId' => $this->team->getId(),\n 'dealCount' => count($dealIds),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n foreach ($deals as $deal) {\n try {\n $deal['associations'] = $this->prepareAssociationsForOpportunity(\n $deal['id'],\n $companyAssociations,\n $contactAssociations,\n $associationsData\n );\n\n $syncedOpportunity = $this->importOrUpdateOpportunity(\n $deal,\n isset($existingCrmIdSet[(string) $deal['id']])\n );\n if ($syncedOpportunity) {\n $syncedOpportunities['success'][] = $syncedOpportunity;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $deal['id'],\n 'error' => $e->getMessage(),\n ]);\n $syncedOpportunities['failed_ids'][] = $deal['id'];\n $syncedOpportunities['errors'][$deal['id']] = $e->getMessage();\n }\n }\n\n return $syncedOpportunities;\n }\n\n /**\n * Prepare associated entities for opportunities with optimized batch processing\n * Returns structured data with CRM ID to DB ID mappings for each opportunity\n */\n private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array\n {\n // Step 1: Collect all unique company and contact IDs from associations\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n\n // Step 2: Batch sync missing entities and get CRM ID to DB ID mappings\n $companyIdMappings = [];\n $contactIdMappings = [];\n\n if (! empty($allCompanyIds)) {\n $companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);\n }\n\n if (! empty($allContactIds)) {\n $contactIdMappings = $this->prepareAssociatedContacts($allContactIds);\n }\n\n return [\n 'company_id_mappings' => $companyIdMappings,\n 'contact_id_mappings' => $contactIdMappings,\n ];\n }\n\n /**\n * Flatten association data to get unique IDs\n */\n private function flattenAssociationIds(array $associations): array\n {\n $ids = [];\n foreach ($associations as $dealAssociations) {\n if (is_array($dealAssociations)) {\n foreach ($dealAssociations as $id) {\n $ids[$id] = true;\n }\n }\n }\n\n return array_keys($ids);\n }\n\n /**\n * Batch sync missing accounts\n */\n private function prepareAssociatedAccounts(array $companyIds): array\n {\n // Find which accounts already exist\n $existingAccounts = $this->crmEntityRepository\n ->findAccountsByExternalIds($this->config, $companyIds);\n\n $existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();\n\n $existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {\n return [$account->getCrmProviderId() => $account->getId()];\n })->toArray();\n\n $missingCompanyIds = array_diff($companyIds, $existingCompanyIds);\n\n if (empty($missingCompanyIds)) {\n return $existingAccountsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [\n 'teamId' => $this->team->getUuid(),\n 'total_companies' => count($companyIds),\n 'existing_companies' => count($existingCompanyIds),\n 'missing_companies' => count($missingCompanyIds),\n ]);\n\n // we already have limit on opportunity ids count\n // Initialize variable before try block\n $syncedAccountsData = [];\n\n try {\n $syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [\n 'size' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedAccountsData = [];\n }\n\n return $existingAccountsData + $syncedAccountsData;\n }\n\n /**\n * Prepare associated contacts - find existing and sync missing ones\n * Returns mapping of CRM ID to DB ID\n */\n private function prepareAssociatedContacts(array $contactIds): array\n {\n // Find which contacts already exist\n $existingContacts = $this->crmEntityRepository\n ->findContactsByExternalIds($this->config, $contactIds);\n\n $existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();\n\n // Create mapping for existing contacts\n $existingContactsData = $existingContacts->mapWithKeys(function ($contact) {\n return [$contact->getCrmProviderId() => $contact->getId()];\n })->toArray();\n\n $missingContactIds = array_diff($contactIds, $existingContactIds);\n\n if (empty($missingContactIds)) {\n return $existingContactsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [\n 'teamId' => $this->team->getUuid(),\n 'total_contacts' => count($contactIds),\n 'existing_contacts' => count($existingContactIds),\n 'missing_contacts' => count($missingContactIds),\n ]);\n\n // Sync missing contacts using batch API\n try {\n $syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [\n 'size' => count($missingContactIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedContactsData = [];\n }\n\n return $existingContactsData + $syncedContactsData;\n }\n\n private function batchSyncCrmObjects(string $objectType, array $crmIds): array\n {\n $syncObjects = [];\n $crmObjectIds = array_values($crmIds);\n\n foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {\n try {\n $objects = $objectType === 'companies' ?\n $this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :\n $this->client->getContactsByIds($chunk, $this->getContactFields());\n\n foreach ($objects as $objectId => $objectData) {\n $this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [\n 'requested_count' => count($chunk),\n 'synced_count' => count($objects),\n ]);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [\n 'ids' => $chunk,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncObjects;\n }\n\n private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void\n {\n try {\n $object = $objectType === 'companies' ?\n $this->importAccount($objectData) :\n $this->importContact($objectData);\n\n if ($object) {\n $syncObjects[$object->getCrmProviderId()] = $object->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [\n 'id' => $objectId,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n /**\n * Prepare associations for a single opportunity\n *\n * The return value is an array with the following structure:\n * [\n * 'companies' => [\n * $companyCrmId => $companyId,\n * ...\n * ],\n * 'contacts' => [\n * $contactCrmId => $contactId,\n * ...\n * ],\n * 'account_id' => $accountId,\n * ]\n */\n private function prepareAssociationsForOpportunity(\n string $oppCrmId,\n array $companyAssociations,\n array $contactAssociations,\n array $associationsData\n ): array {\n $associations = [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n\n $oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];\n foreach ($oppCompanyIds as $companyCrmId) {\n if (isset($associationsData['company_id_mappings'][$companyCrmId])) {\n $associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];\n\n // Set primary account (first company becomes primary account)\n if ($associations['account_id'] === null) {\n $associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];\n }\n }\n }\n\n $oppContactIds = $contactAssociations[$oppCrmId] ?? [];\n foreach ($oppContactIds as $contactCrmId) {\n if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {\n $associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];\n }\n }\n\n return $associations;\n }\n\n /**\n * Update only associations for an opportunity\n */\n private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void\n {\n // Update contact associations\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n // Update company (account) associations\n $this->updateOpportunityAccount($opportunity, $associations['account_id']);\n }\n\n /**\n * Remove all contact associations from an opportunity\n */\n private function removeAllOpportunityContacts(Opportunity $opportunity): void\n {\n $currentCount = (int) $opportunity->contacts()->count();\n\n if ($currentCount > 0) {\n $opportunity->contacts()->detach();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_count' => $currentCount,\n ]);\n }\n }\n\n private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void\n {\n if ($accountId === null) {\n // No account ID provided - keep current account\n return;\n }\n\n $currentAccountId = $opportunity->getAccountId();\n\n // Only update if account has changed\n if ($currentAccountId !== $accountId) {\n $opportunity->account_id = $accountId;\n $opportunity->save();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [\n 'opportunity_id' => $opportunity->getId(),\n 'old_account_id' => $currentAccountId,\n 'new_account_id' => $accountId,\n ]);\n }\n }\n\n /**\n * Find existing opportunities by external IDs (OPTIMIZED VERSION)\n * Uses batch query for better performance\n */\n private function findExistingOpportunities(array $crmIds): Collection\n {\n return $this->crmEntityRepository\n ->findOpportunitiesByExternalIds($this->config, $crmIds);\n }\n\n private function processOpportunityBatch(array $opportunities): int\n {\n $syncedOpportunities = $this->importOpportunityBatch($opportunities);\n\n return count($syncedOpportunities['success'] ?? []);\n }\n\n /**\n * Convert single deal associations from HubSpot format to internal format\n * Handles both HubSpot SDK objects and array formats\n *\n * @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed\n *\n * @return array Processed associations with DB IDs\n */\n private function convertDealAssociations(array $opportunityAssociations): array\n {\n $associations = $this->initializeAssociationsStructure();\n\n if (empty($opportunityAssociations)) {\n return $associations;\n }\n\n $associationIds = $this->extractAssociationIds($opportunityAssociations);\n\n $this->processCompanyAssociations($associationIds, $associations);\n $this->processContactAssociations($associationIds, $associations);\n\n return $associations;\n }\n\n private function initializeAssociationsStructure(): array\n {\n return [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n }\n\n private function extractAssociationIds(array $opportunityAssociations): array\n {\n $associationIds = [];\n\n foreach ($opportunityAssociations as $type => $associationData) {\n if (! empty($associationData)) {\n $associationIds[$type] = $this->convertSingleDealAssociations($associationData);\n }\n }\n\n return $associationIds;\n }\n\n private function processCompanyAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['companies'])) {\n return;\n }\n\n $companyId = $associationIds['companies'][0];\n $account = $this->findOrSyncAccount($companyId);\n\n if ($account instanceof Account) {\n $associations['companies'][$companyId] = $account->getId();\n $associations['account_id'] = $account->getId();\n }\n }\n\n private function processContactAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['contacts'])) {\n return;\n }\n\n foreach ($associationIds['contacts'] as $contactId) {\n $contact = $this->findOrSyncContact($contactId);\n\n if ($contact instanceof Contact) {\n $associations['contacts'][$contactId] = $contact->getId();\n }\n }\n }\n\n private function findOrSyncAccount(string $companyId): ?Account\n {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);\n\n if (! $account instanceof Account) {\n $account = $this->syncAccount($companyId);\n }\n\n return $account;\n }\n\n private function findOrSyncContact(string $contactId): ?Contact\n {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);\n\n if (! $contact instanceof Contact) {\n $contact = $this->syncContact($contactId);\n }\n\n return $contact;\n }\n\n private function convertSingleDealAssociations($opportunityAssociations = null): array\n {\n $associationData = [];\n\n if ($opportunityAssociations === null) {\n return $associationData;\n }\n\n // Handle array input (from extractAssociationIds)\n if (is_array($opportunityAssociations)) {\n return $opportunityAssociations;\n }\n\n // Handle CollectionResponseAssociatedId object\n if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {\n foreach ($opportunityAssociations->getResults() as $association) {\n $associationData[] = $association->getId();\n }\n }\n\n return $associationData;\n }\n\n private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity\n {\n if (empty($crmData['properties'])) {\n return null;\n }\n\n $crmId = (string) $crmData['id'];\n $properties = $crmData['properties'];\n $associations = $crmData['associations'] ?? [];\n\n $opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(\n $this->config,\n $crmId\n );\n\n if ($opportunityExists) {\n return $this->updateOpportunity($crmId, $properties, $associations);\n } else {\n return $this->createOpportunity($crmId, $properties, $associations);\n }\n }\n\n /**\n * Create new opportunity\n */\n private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n if (! $accountId) {\n return null;\n }\n\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n if (! $businessProcess) {\n return null;\n }\n\n $stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);\n if (! $stage) {\n return null;\n }\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n if ($opportunity->wasRecentlyCreated) {\n MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());\n }\n\n return $opportunity;\n }\n\n /**\n * Update existing opportunity\n */\n private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n $stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->updateOpportunityAssociations($opportunity, $associations);\n\n return $opportunity;\n }\n\n private function resolveAccountId(array $associations): ?int\n {\n if (! empty($associations['accountId'])) {\n return $associations['accountId'];\n }\n\n if (empty($associations)) {\n return null;\n }\n\n // we can't resolve multiple account ids (currently SDK returns one company)\n foreach ($associations['companies'] as $accountId) {\n return $accountId;\n }\n\n return null;\n }\n\n private function buildOpportunityData(\n array $properties,\n ?int $accountId,\n ?BusinessProcess $businessProcess,\n ?Stage $stage\n ): array {\n $ownerId = null;\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $name = 'Unknown';\n if (isset($properties['dealname'])) {\n $name = mb_strimwidth($properties['dealname'], 0, 128);\n }\n\n $amount = $this->resolveAmount($properties);\n $currency = $properties['deal_currency_code'] ?? null;\n\n $closeDate = null;\n if (! empty($properties['closedate'])) {\n $closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');\n }\n\n $remotelyCreatedAt = null;\n if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {\n $date = $this->parseCleanDatetime($properties['createdate']);\n $remotelyCreatedAt = $date?->format('Y-m-d H:i:s');\n }\n\n $closedStages = $this->getClosedDealStages();\n $isWon = in_array($properties['dealstage'], $closedStages['won']);\n $isLost = in_array($properties['dealstage'], $closedStages['lost']);\n\n $data = [\n 'team_id' => $this->team->getId(),\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'value' => ! empty($amount) ? $amount : null,\n 'currency_code' => CurrencyFormatter::formatCode($currency),\n 'close_date' => $closeDate,\n 'is_closed' => $isWon || $isLost,\n 'is_won' => $isWon,\n 'remotely_created_at' => $remotelyCreatedAt,\n 'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),\n 'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),\n ];\n\n if ($accountId) {\n $data['account_id'] = $accountId;\n }\n\n if ($stage) {\n $data['stage_id'] = $stage->id;\n }\n\n if ($businessProcess) {\n $recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);\n if ($recordType) {\n $data['record_type_id'] = $recordType->id;\n }\n }\n\n return $data;\n }\n\n private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess\n {\n if ($pipelineId === null) {\n return null;\n }\n\n if (isset($this->cachedBusinessProcesses[$pipelineId])) {\n return $this->cachedBusinessProcesses[$pipelineId];\n }\n\n $businessProcess = $this->getBusinessProcess($pipelineId);\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->importStages();\n $businessProcess = $this->getBusinessProcess($pipelineId);\n }\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->logger->info(\n '[HubSpot] Deal is not attached to a pipeline',\n [\n 'pipeline' => $pipelineId]\n );\n }\n\n $this->cachedBusinessProcesses[$pipelineId] = $businessProcess;\n\n return $businessProcess;\n }\n\n private function getBusinessProcess(string $pipelineId): ?BusinessProcess\n {\n return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);\n }\n\n private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage\n {\n if (empty($stageId)) {\n return null;\n }\n\n $cacheKey = $businessProcess->getId() . ':' . $stageId;\n if (isset($this->cachedStages[$cacheKey])) {\n return $this->cachedStages[$cacheKey];\n }\n\n $stage = $this->crmEntityRepository->getPipelineStageByConditions(\n $businessProcess,\n [\n 'crm_provider_id' => $stageId,\n 'type' => Stage::TYPE_OPPORTUNITY,\n ]\n );\n\n if ($stage === null) {\n $this->importStages(null, $stageId);\n }\n\n if ($stage === null) {\n $this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);\n }\n\n $this->cachedStages[$cacheKey] = $stage;\n\n return $stage;\n }\n\n private function resolveAmount(array $properties): ?string\n {\n $amount = null;\n if (! empty($properties['amount'])) {\n $amount = str_replace(',', '', $properties['amount']);\n }\n\n if ($this->config->hasDefaultCurrencyFieldSet()) {\n $valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();\n $amount = $properties[$valueFieldName] ?? $amount;\n }\n\n return $amount;\n }\n\n private function parseCleanDatetime(string $datetime): ?Carbon\n {\n // Treat pre-1980 values as invalid\n $minValidDate = Carbon::parse('1980-01-01 00:00:00');\n\n try {\n $date = Carbon::parse($datetime);\n\n if ($minValidDate->gt($date)) {\n return null;\n }\n\n return $date;\n } catch (Exception) {\n return null; // On parse error, treat as null\n }\n }\n\n private function resolveDealProbability(?string $stageProbability): int\n {\n if ($stageProbability === null) {\n return 0;\n }\n\n $probability = (float) $stageProbability;\n\n return $probability > 1 ? 0 : (int) ($probability * 100);\n }\n\n private function resolveForecastCategory(?string $forecastCategory): string\n {\n if (! $forecastCategory) {\n return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;\n }\n\n $forecastCategory = str_replace('_', ' ', $forecastCategory);\n\n return ucwords(strtolower($forecastCategory));\n }\n\n private function importExternalFieldData(array $properties, int $opportunityId): void\n {\n $crmFields = $this->getOpportunitySyncableFields();\n $this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);\n }\n\n private function importOpportunityContacts(Opportunity $opportunity, array $associations): void\n {\n // Handle empty or missing contact associations\n if (empty($associations)) {\n // Remove all existing contact associations if none provided\n $this->removeAllOpportunityContacts($opportunity);\n\n return;\n }\n\n // Use differential sync approach for better performance and accuracy\n $this->syncOpportunityContactsDifferential($opportunity, $associations);\n }\n\n /**\n * Sync opportunity contacts using differential approach\n * This compares current vs new associations and only makes necessary changes\n */\n private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void\n {\n $currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);\n $contactAssociationIds = array_keys($contactAssociations);\n\n $contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);\n $contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);\n\n if (empty($contactsToAdd) && empty($contactsToRemove)) {\n return;\n }\n\n $this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);\n\n $this->removeContactAssociations($opportunity, $contactsToRemove);\n $this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);\n }\n\n private function getCurrentContactCrmIds(Opportunity $opportunity): array\n {\n return $opportunity->contacts()\n ->pluck('contacts.crm_provider_id')\n ->toArray();\n }\n\n private function logContactAssociationChanges(\n Opportunity $opportunity,\n array $currentContactCrmIds,\n array $contactAssociations,\n array $contactsToAdd,\n array $contactsToRemove\n ): void {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [\n 'opportunity_id' => $opportunity->getId(),\n 'current_contacts' => $currentContactCrmIds,\n 'new_contacts' => $contactAssociations,\n 'contacts_to_add' => $contactsToAdd,\n 'contacts_to_remove' => $contactsToRemove,\n ]);\n }\n\n private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void\n {\n if (empty($contactsToRemove)) {\n return;\n }\n\n $contactsToDetach = $opportunity->contacts()\n ->whereIn('contacts.crm_provider_id', $contactsToRemove)\n ->pluck('contacts.id')\n ->toArray();\n\n if (! empty($contactsToDetach)) {\n $opportunity->contacts()->detach($contactsToDetach);\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_contact_crm_ids' => $contactsToRemove,\n 'removed_contact_count' => count($contactsToDetach),\n ]);\n }\n }\n\n private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void\n {\n if (empty($contactsToAdd)) {\n return;\n }\n\n $contactsAdded = [];\n foreach ($contactsToAdd as $crmId) {\n $id = $contactAssociations[$crmId];\n\n if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {\n $contactsAdded[] = $crmId;\n }\n }\n\n $this->logAddedContacts($opportunity, $contactsAdded);\n }\n\n private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);\n\n if (! $contact) {\n return false;\n }\n\n return $this->performContactAttachment($opportunity, $contact, $crmId);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [\n 'opportunity_id' => $opportunity->getId(),\n 'contact_crm_id' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool\n {\n try {\n $opportunity->contacts()->attach($contact->getId(), [\n 'crm_provider_id' => $crmId,\n ]);\n\n return true;\n } catch (\\Illuminate\\Database\\QueryException $e) {\n if (str_contains($e->getMessage(), 'Duplicate entry')) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [\n 'contact_id' => $contact->getId(),\n 'contact_crm_id' => $crmId,\n 'opportunity_id' => $opportunity->getId(),\n ]);\n\n return false;\n }\n\n throw $e;\n }\n }\n\n private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void\n {\n if (! empty($contactsAdded)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'contacts_to_add_count' => count($contactsAdded),\n 'added_contact_crm_ids' => $contactsAdded,\n 'added_contacts_count' => count($contactsAdded),\n ]);\n }\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\CollectionResponseAssociatedId;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Models\\Account;\nuse Exception;\nuse Jiminny\\Component\\DealInsights\\Forecast\\Forecast;\nuse Jiminny\\Jobs\\Crm\\MatchActivitiesToNewOpportunity;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Crm\\BusinessProcess;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Models\\Opportunity;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DealFieldsService;\nuse Jiminny\\Services\\Crm\\Hubspot\\OpportunitySyncStrategy\\HubspotSingleSyncStrategy;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Services\\Crm\\OpportunitySyncStrategyResolver;\nuse Jiminny\\Utils\\CurrencyFormatter;\n\n/**\n * Optimized sync methods for better performance\n * These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains\n */\ntrait OpportunitySyncTrait\n{\n private const int BATCH_SIZE = 100;\n private const int BATCH_PROCESS_SIZE = 800;\n\n protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;\n protected CrmEntityRepository $crmEntityRepository;\n protected DealFieldsService $dealFieldsService;\n\n private ?array $cachedClosedDealStages = null;\n private array $cachedBusinessProcesses = [];\n private array $cachedStages = [];\n\n public function syncOpportunities(array $parameters, ?string $strategy = null): int\n {\n $strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);\n $parameters['config'] = $this->config;\n $syncCount = 0;\n $reportedTotal = 0;\n $lastSyncedId = [];\n\n try {\n foreach ($strategies as $strategyName => $syncStrategy) {\n $this->logger->info(\n '[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .\n $strategyName\n );\n\n $total = 0;\n $lastId = null;\n $buffer = [];\n\n // HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies\n foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {\n $buffer[] = $hsOpportunity;\n\n // process every 800 rows (fits < 1 000 association limit)\n if (\\count($buffer) >= self::BATCH_PROCESS_SIZE) {\n $syncCount += $this->processOpportunityBatch($buffer);\n $buffer = [];\n }\n }\n\n // leftovers\n if ($buffer) {\n $syncCount += $this->processOpportunityBatch($buffer);\n }\n\n $reportedTotal += $total;\n $lastSyncedId = $lastId;\n }\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException | CrmException $e) {\n $this->handleSyncException($e, $parameters);\n }\n\n $this->logger->info(\n '[HubSpot] Synced opportunities',\n [\n 'team' => $this->team->getId(),\n 'sync_count' => $syncCount,\n 'total' => $reportedTotal,\n 'last_synced_id' => $lastSyncedId,\n ]\n );\n\n return $reportedTotal;\n }\n\n private function handleSyncException(\\Throwable $e, array $parameters): void\n {\n if (($parameters['since'] ?? null) instanceof Carbon) {\n $parameters['since'] = $parameters['since']->toDateTimeString();\n }\n $parameters['config'] = $this->config->getId();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [\n 'teamId' => $this->team->getUuid(),\n 'parameters' => $parameters,\n 'reason' => $e->getMessage(),\n ]);\n }\n\n /**\n * @inheritdoc\n */\n public function syncOpportunity(string $crmId): ?Opportunity\n {\n $strategy = $this->opportunitySyncStrategyResolver->resolve(\n $this->config,\n OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,\n );\n\n $parameters = [\n 'config' => $this->config,\n 'crm_id' => $crmId,\n ];\n\n try {\n if (! $strategy instanceof HubspotSingleSyncStrategy) {\n throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');\n }\n\n $hsOpportunity = $strategy->fetchOpportunity($parameters);\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);\n\n return $this->importOrUpdateOpportunity($hsOpportunity);\n }\n\n /**\n * Process webhook-collected opportunity batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportOpportunityBatch jobs for batch processing.\n *\n * @return int Number of opportunity IDs dispatched to jobs\n */\n public function batchSyncOpportunities(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,\n $configId\n );\n }\n\n /**\n * Import a batch of opportunities by their CRM IDs.\n * Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().\n *\n * @param array<string> $crmIds HubSpot deal CRM IDs\n *\n * @return array{success: array, failed_ids: array, errors?: array<string, string>}\n */\n public function importOpportunityBatchByIds(array $crmIds): array\n {\n $fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);\n\n $allDeals = [];\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n $deals = $this->client->getOpportunitiesByIds($chunk, $fields);\n foreach ($deals as $deal) {\n $allDeals[] = $deal;\n }\n }\n\n // IDs not returned by HubSpot are likely deleted or inaccessible deals.\n // These are not failures — retrying won't bring them back.\n $fetchedIds = array_map('strval', array_column($allDeals, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allDeals),\n ]);\n }\n\n if (empty($allDeals)) {\n return ['success' => [], 'failed_ids' => []];\n }\n\n return $this->importOpportunityBatch($allDeals);\n }\n\n private function getClosedDealStages(): array\n {\n if ($this->cachedClosedDealStages !== null) {\n return $this->cachedClosedDealStages;\n }\n\n $stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);\n $data = [\n 'lost' => [],\n 'won' => [],\n ];\n\n foreach ($stages as $stage) {\n if ($stage->probability == 0.00) {\n $data['lost'][] = $stage->crm_provider_id;\n }\n if ($stage->probability == 100.00) {\n $data['won'][] = $stage->crm_provider_id;\n }\n }\n\n $this->cachedClosedDealStages = $data;\n\n return $data;\n }\n\n /**\n * Import deals into the database with pre-fetched associations.\n *\n * API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT\n * caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()\n * where Laravel retries the whole job with backoff. After all retries exhausted,\n * failed() requeues all IDs to Redis.\n *\n * The per-deal loop catches exceptions individually. A deal can end up in three states:\n * - success: imported/updated successfully\n * - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)\n * These are permanent issues — retrying won't fix them.\n * - skipped (null): missing dependencies (no account, unknown pipeline/stage).\n * This is acceptable — the deal cannot be imported until those exist.\n */\n private function importOpportunityBatch(array $deals): array\n {\n $syncedOpportunities = [\n 'success' => [],\n 'failed_ids' => [],\n ];\n $dealIds = array_column($deals, 'id');\n\n // Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the\n // queue job retries the whole batch and eventually requeues all deal IDs back to Redis.\n try {\n $companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');\n $contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');\n\n $associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);\n\n $existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(\n $this->config,\n array_map('strval', $dealIds)\n );\n $existingCrmIdSet = array_flip($existingCrmIds);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [\n 'teamId' => $this->team->getId(),\n 'dealCount' => count($dealIds),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n foreach ($deals as $deal) {\n try {\n $deal['associations'] = $this->prepareAssociationsForOpportunity(\n $deal['id'],\n $companyAssociations,\n $contactAssociations,\n $associationsData\n );\n\n $syncedOpportunity = $this->importOrUpdateOpportunity(\n $deal,\n isset($existingCrmIdSet[(string) $deal['id']])\n );\n if ($syncedOpportunity) {\n $syncedOpportunities['success'][] = $syncedOpportunity;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $deal['id'],\n 'error' => $e->getMessage(),\n ]);\n $syncedOpportunities['failed_ids'][] = $deal['id'];\n $syncedOpportunities['errors'][$deal['id']] = $e->getMessage();\n }\n }\n\n return $syncedOpportunities;\n }\n\n /**\n * Prepare associated entities for opportunities with optimized batch processing\n * Returns structured data with CRM ID to DB ID mappings for each opportunity\n */\n private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array\n {\n // Step 1: Collect all unique company and contact IDs from associations\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n\n // Step 2: Batch sync missing entities and get CRM ID to DB ID mappings\n $companyIdMappings = [];\n $contactIdMappings = [];\n\n if (! empty($allCompanyIds)) {\n $companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);\n }\n\n if (! empty($allContactIds)) {\n $contactIdMappings = $this->prepareAssociatedContacts($allContactIds);\n }\n\n return [\n 'company_id_mappings' => $companyIdMappings,\n 'contact_id_mappings' => $contactIdMappings,\n ];\n }\n\n /**\n * Flatten association data to get unique IDs\n */\n private function flattenAssociationIds(array $associations): array\n {\n $ids = [];\n foreach ($associations as $dealAssociations) {\n if (is_array($dealAssociations)) {\n foreach ($dealAssociations as $id) {\n $ids[$id] = true;\n }\n }\n }\n\n return array_keys($ids);\n }\n\n /**\n * Batch sync missing accounts\n */\n private function prepareAssociatedAccounts(array $companyIds): array\n {\n // Find which accounts already exist\n $existingAccounts = $this->crmEntityRepository\n ->findAccountsByExternalIds($this->config, $companyIds);\n\n $existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();\n\n $existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {\n return [$account->getCrmProviderId() => $account->getId()];\n })->toArray();\n\n $missingCompanyIds = array_diff($companyIds, $existingCompanyIds);\n\n if (empty($missingCompanyIds)) {\n return $existingAccountsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [\n 'teamId' => $this->team->getUuid(),\n 'total_companies' => count($companyIds),\n 'existing_companies' => count($existingCompanyIds),\n 'missing_companies' => count($missingCompanyIds),\n ]);\n\n // we already have limit on opportunity ids count\n // Initialize variable before try block\n $syncedAccountsData = [];\n\n try {\n $syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [\n 'size' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedAccountsData = [];\n }\n\n return $existingAccountsData + $syncedAccountsData;\n }\n\n /**\n * Prepare associated contacts - find existing and sync missing ones\n * Returns mapping of CRM ID to DB ID\n */\n private function prepareAssociatedContacts(array $contactIds): array\n {\n // Find which contacts already exist\n $existingContacts = $this->crmEntityRepository\n ->findContactsByExternalIds($this->config, $contactIds);\n\n $existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();\n\n // Create mapping for existing contacts\n $existingContactsData = $existingContacts->mapWithKeys(function ($contact) {\n return [$contact->getCrmProviderId() => $contact->getId()];\n })->toArray();\n\n $missingContactIds = array_diff($contactIds, $existingContactIds);\n\n if (empty($missingContactIds)) {\n return $existingContactsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [\n 'teamId' => $this->team->getUuid(),\n 'total_contacts' => count($contactIds),\n 'existing_contacts' => count($existingContactIds),\n 'missing_contacts' => count($missingContactIds),\n ]);\n\n // Sync missing contacts using batch API\n try {\n $syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [\n 'size' => count($missingContactIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedContactsData = [];\n }\n\n return $existingContactsData + $syncedContactsData;\n }\n\n private function batchSyncCrmObjects(string $objectType, array $crmIds): array\n {\n $syncObjects = [];\n $crmObjectIds = array_values($crmIds);\n\n foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {\n try {\n $objects = $objectType === 'companies' ?\n $this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :\n $this->client->getContactsByIds($chunk, $this->getContactFields());\n\n foreach ($objects as $objectId => $objectData) {\n $this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [\n 'requested_count' => count($chunk),\n 'synced_count' => count($objects),\n ]);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [\n 'ids' => $chunk,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncObjects;\n }\n\n private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void\n {\n try {\n $object = $objectType === 'companies' ?\n $this->importAccount($objectData) :\n $this->importContact($objectData);\n\n if ($object) {\n $syncObjects[$object->getCrmProviderId()] = $object->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [\n 'id' => $objectId,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n /**\n * Prepare associations for a single opportunity\n *\n * The return value is an array with the following structure:\n * [\n * 'companies' => [\n * $companyCrmId => $companyId,\n * ...\n * ],\n * 'contacts' => [\n * $contactCrmId => $contactId,\n * ...\n * ],\n * 'account_id' => $accountId,\n * ]\n */\n private function prepareAssociationsForOpportunity(\n string $oppCrmId,\n array $companyAssociations,\n array $contactAssociations,\n array $associationsData\n ): array {\n $associations = [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n\n $oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];\n foreach ($oppCompanyIds as $companyCrmId) {\n if (isset($associationsData['company_id_mappings'][$companyCrmId])) {\n $associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];\n\n // Set primary account (first company becomes primary account)\n if ($associations['account_id'] === null) {\n $associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];\n }\n }\n }\n\n $oppContactIds = $contactAssociations[$oppCrmId] ?? [];\n foreach ($oppContactIds as $contactCrmId) {\n if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {\n $associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];\n }\n }\n\n return $associations;\n }\n\n /**\n * Update only associations for an opportunity\n */\n private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void\n {\n // Update contact associations\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n // Update company (account) associations\n $this->updateOpportunityAccount($opportunity, $associations['account_id']);\n }\n\n /**\n * Remove all contact associations from an opportunity\n */\n private function removeAllOpportunityContacts(Opportunity $opportunity): void\n {\n $currentCount = (int) $opportunity->contacts()->count();\n\n if ($currentCount > 0) {\n $opportunity->contacts()->detach();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_count' => $currentCount,\n ]);\n }\n }\n\n private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void\n {\n if ($accountId === null) {\n // No account ID provided - keep current account\n return;\n }\n\n $currentAccountId = $opportunity->getAccountId();\n\n // Only update if account has changed\n if ($currentAccountId !== $accountId) {\n $opportunity->account_id = $accountId;\n $opportunity->save();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [\n 'opportunity_id' => $opportunity->getId(),\n 'old_account_id' => $currentAccountId,\n 'new_account_id' => $accountId,\n ]);\n }\n }\n\n /**\n * Find existing opportunities by external IDs (OPTIMIZED VERSION)\n * Uses batch query for better performance\n */\n private function findExistingOpportunities(array $crmIds): Collection\n {\n return $this->crmEntityRepository\n ->findOpportunitiesByExternalIds($this->config, $crmIds);\n }\n\n private function processOpportunityBatch(array $opportunities): int\n {\n $syncedOpportunities = $this->importOpportunityBatch($opportunities);\n\n return count($syncedOpportunities['success'] ?? []);\n }\n\n /**\n * Convert single deal associations from HubSpot format to internal format\n * Handles both HubSpot SDK objects and array formats\n *\n * @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed\n *\n * @return array Processed associations with DB IDs\n */\n private function convertDealAssociations(array $opportunityAssociations): array\n {\n $associations = $this->initializeAssociationsStructure();\n\n if (empty($opportunityAssociations)) {\n return $associations;\n }\n\n $associationIds = $this->extractAssociationIds($opportunityAssociations);\n\n $this->processCompanyAssociations($associationIds, $associations);\n $this->processContactAssociations($associationIds, $associations);\n\n return $associations;\n }\n\n private function initializeAssociationsStructure(): array\n {\n return [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n }\n\n private function extractAssociationIds(array $opportunityAssociations): array\n {\n $associationIds = [];\n\n foreach ($opportunityAssociations as $type => $associationData) {\n if (! empty($associationData)) {\n $associationIds[$type] = $this->convertSingleDealAssociations($associationData);\n }\n }\n\n return $associationIds;\n }\n\n private function processCompanyAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['companies'])) {\n return;\n }\n\n $companyId = $associationIds['companies'][0];\n $account = $this->findOrSyncAccount($companyId);\n\n if ($account instanceof Account) {\n $associations['companies'][$companyId] = $account->getId();\n $associations['account_id'] = $account->getId();\n }\n }\n\n private function processContactAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['contacts'])) {\n return;\n }\n\n foreach ($associationIds['contacts'] as $contactId) {\n $contact = $this->findOrSyncContact($contactId);\n\n if ($contact instanceof Contact) {\n $associations['contacts'][$contactId] = $contact->getId();\n }\n }\n }\n\n private function findOrSyncAccount(string $companyId): ?Account\n {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);\n\n if (! $account instanceof Account) {\n $account = $this->syncAccount($companyId);\n }\n\n return $account;\n }\n\n private function findOrSyncContact(string $contactId): ?Contact\n {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);\n\n if (! $contact instanceof Contact) {\n $contact = $this->syncContact($contactId);\n }\n\n return $contact;\n }\n\n private function convertSingleDealAssociations($opportunityAssociations = null): array\n {\n $associationData = [];\n\n if ($opportunityAssociations === null) {\n return $associationData;\n }\n\n // Handle array input (from extractAssociationIds)\n if (is_array($opportunityAssociations)) {\n return $opportunityAssociations;\n }\n\n // Handle CollectionResponseAssociatedId object\n if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {\n foreach ($opportunityAssociations->getResults() as $association) {\n $associationData[] = $association->getId();\n }\n }\n\n return $associationData;\n }\n\n private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity\n {\n if (empty($crmData['properties'])) {\n return null;\n }\n\n $crmId = (string) $crmData['id'];\n $properties = $crmData['properties'];\n $associations = $crmData['associations'] ?? [];\n\n $opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(\n $this->config,\n $crmId\n );\n\n if ($opportunityExists) {\n return $this->updateOpportunity($crmId, $properties, $associations);\n } else {\n return $this->createOpportunity($crmId, $properties, $associations);\n }\n }\n\n /**\n * Create new opportunity\n */\n private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n if (! $accountId) {\n return null;\n }\n\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n if (! $businessProcess) {\n return null;\n }\n\n $stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);\n if (! $stage) {\n return null;\n }\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n if ($opportunity->wasRecentlyCreated) {\n MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());\n }\n\n return $opportunity;\n }\n\n /**\n * Update existing opportunity\n */\n private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n $stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->updateOpportunityAssociations($opportunity, $associations);\n\n return $opportunity;\n }\n\n private function resolveAccountId(array $associations): ?int\n {\n if (! empty($associations['accountId'])) {\n return $associations['accountId'];\n }\n\n if (empty($associations)) {\n return null;\n }\n\n // we can't resolve multiple account ids (currently SDK returns one company)\n foreach ($associations['companies'] as $accountId) {\n return $accountId;\n }\n\n return null;\n }\n\n private function buildOpportunityData(\n array $properties,\n ?int $accountId,\n ?BusinessProcess $businessProcess,\n ?Stage $stage\n ): array {\n $ownerId = null;\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $name = 'Unknown';\n if (isset($properties['dealname'])) {\n $name = mb_strimwidth($properties['dealname'], 0, 128);\n }\n\n $amount = $this->resolveAmount($properties);\n $currency = $properties['deal_currency_code'] ?? null;\n\n $closeDate = null;\n if (! empty($properties['closedate'])) {\n $closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');\n }\n\n $remotelyCreatedAt = null;\n if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {\n $date = $this->parseCleanDatetime($properties['createdate']);\n $remotelyCreatedAt = $date?->format('Y-m-d H:i:s');\n }\n\n $closedStages = $this->getClosedDealStages();\n $isWon = in_array($properties['dealstage'], $closedStages['won']);\n $isLost = in_array($properties['dealstage'], $closedStages['lost']);\n\n $data = [\n 'team_id' => $this->team->getId(),\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'value' => ! empty($amount) ? $amount : null,\n 'currency_code' => CurrencyFormatter::formatCode($currency),\n 'close_date' => $closeDate,\n 'is_closed' => $isWon || $isLost,\n 'is_won' => $isWon,\n 'remotely_created_at' => $remotelyCreatedAt,\n 'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),\n 'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),\n ];\n\n if ($accountId) {\n $data['account_id'] = $accountId;\n }\n\n if ($stage) {\n $data['stage_id'] = $stage->id;\n }\n\n if ($businessProcess) {\n $recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);\n if ($recordType) {\n $data['record_type_id'] = $recordType->id;\n }\n }\n\n return $data;\n }\n\n private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess\n {\n if ($pipelineId === null) {\n return null;\n }\n\n if (isset($this->cachedBusinessProcesses[$pipelineId])) {\n return $this->cachedBusinessProcesses[$pipelineId];\n }\n\n $businessProcess = $this->getBusinessProcess($pipelineId);\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->importStages();\n $businessProcess = $this->getBusinessProcess($pipelineId);\n }\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->logger->info(\n '[HubSpot] Deal is not attached to a pipeline',\n [\n 'pipeline' => $pipelineId]\n );\n }\n\n $this->cachedBusinessProcesses[$pipelineId] = $businessProcess;\n\n return $businessProcess;\n }\n\n private function getBusinessProcess(string $pipelineId): ?BusinessProcess\n {\n return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);\n }\n\n private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage\n {\n if (empty($stageId)) {\n return null;\n }\n\n $cacheKey = $businessProcess->getId() . ':' . $stageId;\n if (isset($this->cachedStages[$cacheKey])) {\n return $this->cachedStages[$cacheKey];\n }\n\n $stage = $this->crmEntityRepository->getPipelineStageByConditions(\n $businessProcess,\n [\n 'crm_provider_id' => $stageId,\n 'type' => Stage::TYPE_OPPORTUNITY,\n ]\n );\n\n if ($stage === null) {\n $this->importStages(null, $stageId);\n }\n\n if ($stage === null) {\n $this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);\n }\n\n $this->cachedStages[$cacheKey] = $stage;\n\n return $stage;\n }\n\n private function resolveAmount(array $properties): ?string\n {\n $amount = null;\n if (! empty($properties['amount'])) {\n $amount = str_replace(',', '', $properties['amount']);\n }\n\n if ($this->config->hasDefaultCurrencyFieldSet()) {\n $valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();\n $amount = $properties[$valueFieldName] ?? $amount;\n }\n\n return $amount;\n }\n\n private function parseCleanDatetime(string $datetime): ?Carbon\n {\n // Treat pre-1980 values as invalid\n $minValidDate = Carbon::parse('1980-01-01 00:00:00');\n\n try {\n $date = Carbon::parse($datetime);\n\n if ($minValidDate->gt($date)) {\n return null;\n }\n\n return $date;\n } catch (Exception) {\n return null; // On parse error, treat as null\n }\n }\n\n private function resolveDealProbability(?string $stageProbability): int\n {\n if ($stageProbability === null) {\n return 0;\n }\n\n $probability = (float) $stageProbability;\n\n return $probability > 1 ? 0 : (int) ($probability * 100);\n }\n\n private function resolveForecastCategory(?string $forecastCategory): string\n {\n if (! $forecastCategory) {\n return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;\n }\n\n $forecastCategory = str_replace('_', ' ', $forecastCategory);\n\n return ucwords(strtolower($forecastCategory));\n }\n\n private function importExternalFieldData(array $properties, int $opportunityId): void\n {\n $crmFields = $this->getOpportunitySyncableFields();\n $this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);\n }\n\n private function importOpportunityContacts(Opportunity $opportunity, array $associations): void\n {\n // Handle empty or missing contact associations\n if (empty($associations)) {\n // Remove all existing contact associations if none provided\n $this->removeAllOpportunityContacts($opportunity);\n\n return;\n }\n\n // Use differential sync approach for better performance and accuracy\n $this->syncOpportunityContactsDifferential($opportunity, $associations);\n }\n\n /**\n * Sync opportunity contacts using differential approach\n * This compares current vs new associations and only makes necessary changes\n */\n private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void\n {\n $currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);\n $contactAssociationIds = array_keys($contactAssociations);\n\n $contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);\n $contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);\n\n if (empty($contactsToAdd) && empty($contactsToRemove)) {\n return;\n }\n\n $this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);\n\n $this->removeContactAssociations($opportunity, $contactsToRemove);\n $this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);\n }\n\n private function getCurrentContactCrmIds(Opportunity $opportunity): array\n {\n return $opportunity->contacts()\n ->pluck('contacts.crm_provider_id')\n ->toArray();\n }\n\n private function logContactAssociationChanges(\n Opportunity $opportunity,\n array $currentContactCrmIds,\n array $contactAssociations,\n array $contactsToAdd,\n array $contactsToRemove\n ): void {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [\n 'opportunity_id' => $opportunity->getId(),\n 'current_contacts' => $currentContactCrmIds,\n 'new_contacts' => $contactAssociations,\n 'contacts_to_add' => $contactsToAdd,\n 'contacts_to_remove' => $contactsToRemove,\n ]);\n }\n\n private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void\n {\n if (empty($contactsToRemove)) {\n return;\n }\n\n $contactsToDetach = $opportunity->contacts()\n ->whereIn('contacts.crm_provider_id', $contactsToRemove)\n ->pluck('contacts.id')\n ->toArray();\n\n if (! empty($contactsToDetach)) {\n $opportunity->contacts()->detach($contactsToDetach);\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_contact_crm_ids' => $contactsToRemove,\n 'removed_contact_count' => count($contactsToDetach),\n ]);\n }\n }\n\n private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void\n {\n if (empty($contactsToAdd)) {\n return;\n }\n\n $contactsAdded = [];\n foreach ($contactsToAdd as $crmId) {\n $id = $contactAssociations[$crmId];\n\n if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {\n $contactsAdded[] = $crmId;\n }\n }\n\n $this->logAddedContacts($opportunity, $contactsAdded);\n }\n\n private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);\n\n if (! $contact) {\n return false;\n }\n\n return $this->performContactAttachment($opportunity, $contact, $crmId);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [\n 'opportunity_id' => $opportunity->getId(),\n 'contact_crm_id' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool\n {\n try {\n $opportunity->contacts()->attach($contact->getId(), [\n 'crm_provider_id' => $crmId,\n ]);\n\n return true;\n } catch (\\Illuminate\\Database\\QueryException $e) {\n if (str_contains($e->getMessage(), 'Duplicate entry')) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [\n 'contact_id' => $contact->getId(),\n 'contact_crm_id' => $crmId,\n 'opportunity_id' => $opportunity->getId(),\n ]);\n\n return false;\n }\n\n throw $e;\n }\n }\n\n private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void\n {\n if (! empty($contactsAdded)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'contacts_to_add_count' => count($contactsAdded),\n 'added_contact_crm_ids' => $contactsAdded,\n 'added_contacts_count' => count($contactsAdded),\n ]);\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.049609374,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.69921875,"top":0.10902778,"width":0.00859375,"height":0.013194445},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.7097656,"top":0.10763889,"width":0.00859375,"height":0.015972223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.71835935,"top":0.10763889,"width":0.008203125,"height":0.015972223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<template>\n <WelcomeLayout\n title=\"Account disconnected\"\n textPosition=\"center\"\n :icon=\"faUnlink\"\n :class=\"$style.layout\"\n >\n <div :class=\"$style.container\" v-if=\"providersLoaded\">\n <p>\n <strong>\n It looks like your {{ localProvider.displayName }} account has become\n disconnected\n </strong>\n </p>\n <p :class=\"$style.small\">Please re-connect to continue</p>\n <p v-if=\"isInIframe\">\n We'll open the {{ localProvider.displayName }} authentication in a new\n tab. Please return here and refresh the page once complete\n </p>\n\n <GoogleLikeButton\n v-if=\"localProvider.viaIntegrationApp && crmTokenLoaded\"\n as=\"a\"\n :key=\"localProvider.name\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n @click=\"integrationAppOnClick\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n <GoogleLikeButton\n v-if=\"!localProvider.viaIntegrationApp\"\n as=\"a\"\n :key=\"localProvider.name\"\n :href=\"`/auth/redirect/${localProvider.name}`\"\n :target=\"target\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n </div>\n <BuildInfo />\n\n <KioskBanner />\n </WelcomeLayout>\n</template>\n\n<script>\nimport window from \"window\";\nimport axios from \"axios\";\nimport { faUnlink } from \"@fortawesome/pro-regular-svg-icons\";\nimport isInIframe from \"@/utils/isInIframe\";\nimport BuildInfo from \"@/components/layout/BuildInfo/BuildInfo.vue\";\nimport KioskBanner from \"@/components/shared/KioskBanner/KioskBanner.vue\";\nimport WelcomeLayout from \"@/components/layout/WelcomeLayout/WelcomeLayout.vue\";\nimport GoogleLikeButton from \"@/components/shared/Buttons/GoogleLikeButton.vue\";\nimport { showSnackbarError, normalizeError } from \"@/utils/index\";\nimport { IntegrationAppClient } from \"@integration-app/sdk\";\n\nexport default {\n name: \"ConnectPage\",\n components: {\n BuildInfo,\n KioskBanner,\n WelcomeLayout,\n GoogleLikeButton,\n },\n data() {\n return {\n ...window.connectData,\n crmToken: null,\n faUnlink,\n isInIframe,\n providers: [],\n providersLoaded: false,\n crmTokenLoaded: false,\n };\n },\n computed: {\n localProvider() {\n return this.providers.find((e) => e.name === this.provider);\n },\n target() {\n return this.isInIframe ? \"_blank\" : null;\n },\n },\n created() {\n this.getProviders();\n },\n mounted() {\n this.showErrors();\n },\n watch: {\n providersLoaded() {\n if (this.providersLoaded) {\n this.prepareIntegrationAppConnection();\n }\n },\n },\n methods: {\n showErrors() {\n if (!this.error) return;\n\n showSnackbarError(this.error, undefined, undefined, false);\n },\n unwrapEntityResponse({ data }) {\n return data.map(({ icon, name, displayName, viaIntegrationApp }) => {\n return { icon, name, displayName, viaIntegrationApp };\n });\n },\n async getProviders() {\n try {\n const response = await axios.get(\"/api/v1/connect-providers\");\n this.providers = this.unwrapEntityResponse(response);\n this.providersLoaded = true;\n } catch {\n showSnackbarError(\n \"An error occurred, while loading form data (connect providers).\",\n );\n }\n },\n async prepareIntegrationAppConnection() {\n if (this.localProvider.viaIntegrationApp) {\n try {\n const response = await axios.get(\"/api/v1/integration-app-token\");\n this.crmToken = response.data.token;\n this.crmTokenLoaded = true;\n } catch (error) {\n console.log(error);\n showSnackbarError(\n `An error occurred while preparing the page.\n Try refreshing, if the error persists get in touch with the Jiminny team.`,\n );\n }\n }\n },\n async integrationAppOnClick() {\n console.log('[IntegrationApp] integrationAppOnClick called');\n const integrationApp = new IntegrationAppClient({\n token: this.crmToken,\n });\n\n const connection = await integrationApp\n .integration(this.localProvider.name)\n .openNewConnection({\n showPoweredBy: false,\n allowMultipleConnections: false,\n }).catch((err) => {\n console.log('[IntegrationApp] openNewConnection rejected:', err);\n return null;\n });\n\n console.log('[IntegrationApp] openNewConnection resolved:', JSON.stringify(connection));\n\n // [IntegrationApp] openNewConnection resolved: {\n // \"id\":\"69e0b41a67d0068c2ca0b48e\",\n // \"name\":\"Zoho CRM\",\n // \"userId\":\"1ece66c8-feb1-4df1-b321-21607daf4623\",\n // \"tenantId\":\"69e0b3faef3e7b6248189289\",\n // \"isTest\":false,\n // \"connected\":true,\n // \"state\":\"READY\",\n // \"errors\":[],\n // \"integrationId\":\"66fe6c913202f3a165e3c14d\",\n // \"externalAppId\":\"6671653e7e2d642e4e41b0fa\",\n // \"authOptionKey\":\"\",\n // \"createdAt\":\"2026-04-16T10:04:10.420Z\",\n // \"updatedAt\":\"2026-04-16T10:04:10.575Z\",\n // \"retryAttempts\":0,\n // \"isDeactivated\":false\n // }\n\n if (connection && connection.disconnected !== true && connection.connected !== false) {\n console.log('[IntegrationApp] connection condition matched');\n try {\n const saveRequest = await axios.post(\n \"/api/v1/integration-app-connect\",\n );\n if (saveRequest.data && saveRequest.data.success === true) {\n /** If all is good refresh the page here */\n window.location = \"/dashboard\";\n return;\n }\n\n throw new Error(saveRequest.data.message);\n } catch (error) {\n console.log(error);\n showSnackbarError(normalizeError(error));\n }\n }\n },\n },\n};\n</script>\n\n<style module lang=\"less\" src=\"./connect.less\"></style>","depth":4,"value":"<template>\n <WelcomeLayout\n title=\"Account disconnected\"\n textPosition=\"center\"\n :icon=\"faUnlink\"\n :class=\"$style.layout\"\n >\n <div :class=\"$style.container\" v-if=\"providersLoaded\">\n <p>\n <strong>\n It looks like your {{ localProvider.displayName }} account has become\n disconnected\n </strong>\n </p>\n <p :class=\"$style.small\">Please re-connect to continue</p>\n <p v-if=\"isInIframe\">\n We'll open the {{ localProvider.displayName }} authentication in a new\n tab. Please return here and refresh the page once complete\n </p>\n\n <GoogleLikeButton\n v-if=\"localProvider.viaIntegrationApp && crmTokenLoaded\"\n as=\"a\"\n :key=\"localProvider.name\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n @click=\"integrationAppOnClick\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n <GoogleLikeButton\n v-if=\"!localProvider.viaIntegrationApp\"\n as=\"a\"\n :key=\"localProvider.name\"\n :href=\"`/auth/redirect/${localProvider.name}`\"\n :target=\"target\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n </div>\n <BuildInfo />\n\n <KioskBanner />\n </WelcomeLayout>\n</template>\n\n<script>\nimport window from \"window\";\nimport axios from \"axios\";\nimport { faUnlink } from \"@fortawesome/pro-regular-svg-icons\";\nimport isInIframe from \"@/utils/isInIframe\";\nimport BuildInfo from \"@/components/layout/BuildInfo/BuildInfo.vue\";\nimport KioskBanner from \"@/components/shared/KioskBanner/KioskBanner.vue\";\nimport WelcomeLayout from \"@/components/layout/WelcomeLayout/WelcomeLayout.vue\";\nimport GoogleLikeButton from \"@/components/shared/Buttons/GoogleLikeButton.vue\";\nimport { showSnackbarError, normalizeError } from \"@/utils/index\";\nimport { IntegrationAppClient } from \"@integration-app/sdk\";\n\nexport default {\n name: \"ConnectPage\",\n components: {\n BuildInfo,\n KioskBanner,\n WelcomeLayout,\n GoogleLikeButton,\n },\n data() {\n return {\n ...window.connectData,\n crmToken: null,\n faUnlink,\n isInIframe,\n providers: [],\n providersLoaded: false,\n crmTokenLoaded: false,\n };\n },\n computed: {\n localProvider() {\n return this.providers.find((e) => e.name === this.provider);\n },\n target() {\n return this.isInIframe ? \"_blank\" : null;\n },\n },\n created() {\n this.getProviders();\n },\n mounted() {\n this.showErrors();\n },\n watch: {\n providersLoaded() {\n if (this.providersLoaded) {\n this.prepareIntegrationAppConnection();\n }\n },\n },\n methods: {\n showErrors() {\n if (!this.error) return;\n\n showSnackbarError(this.error, undefined, undefined, false);\n },\n unwrapEntityResponse({ data }) {\n return data.map(({ icon, name, displayName, viaIntegrationApp }) => {\n return { icon, name, displayName, viaIntegrationApp };\n });\n },\n async getProviders() {\n try {\n const response = await axios.get(\"/api/v1/connect-providers\");\n this.providers = this.unwrapEntityResponse(response);\n this.providersLoaded = true;\n } catch {\n showSnackbarError(\n \"An error occurred, while loading form data (connect providers).\",\n );\n }\n },\n async prepareIntegrationAppConnection() {\n if (this.localProvider.viaIntegrationApp) {\n try {\n const response = await axios.get(\"/api/v1/integration-app-token\");\n this.crmToken = response.data.token;\n this.crmTokenLoaded = true;\n } catch (error) {\n console.log(error);\n showSnackbarError(\n `An error occurred while preparing the page.\n Try refreshing, if the error persists get in touch with the Jiminny team.`,\n );\n }\n }\n },\n async integrationAppOnClick() {\n console.log('[IntegrationApp] integrationAppOnClick called');\n const integrationApp = new IntegrationAppClient({\n token: this.crmToken,\n });\n\n const connection = await integrationApp\n .integration(this.localProvider.name)\n .openNewConnection({\n showPoweredBy: false,\n allowMultipleConnections: false,\n }).catch((err) => {\n console.log('[IntegrationApp] openNewConnection rejected:', err);\n return null;\n });\n\n console.log('[IntegrationApp] openNewConnection resolved:', JSON.stringify(connection));\n\n // [IntegrationApp] openNewConnection resolved: {\n // \"id\":\"69e0b41a67d0068c2ca0b48e\",\n // \"name\":\"Zoho CRM\",\n // \"userId\":\"1ece66c8-feb1-4df1-b321-21607daf4623\",\n // \"tenantId\":\"69e0b3faef3e7b6248189289\",\n // \"isTest\":false,\n // \"connected\":true,\n // \"state\":\"READY\",\n // \"errors\":[],\n // \"integrationId\":\"66fe6c913202f3a165e3c14d\",\n // \"externalAppId\":\"6671653e7e2d642e4e41b0fa\",\n // \"authOptionKey\":\"\",\n // \"createdAt\":\"2026-04-16T10:04:10.420Z\",\n // \"updatedAt\":\"2026-04-16T10:04:10.575Z\",\n // \"retryAttempts\":0,\n // \"isDeactivated\":false\n // }\n\n if (connection && connection.disconnected !== true && connection.connected !== false) {\n console.log('[IntegrationApp] connection condition matched');\n try {\n const saveRequest = await axios.post(\n \"/api/v1/integration-app-connect\",\n );\n if (saveRequest.data && saveRequest.data.success === true) {\n /** If all is good refresh the page here */\n window.location = \"/dashboard\";\n return;\n }\n\n throw new Error(saveRequest.data.message);\n } catch (error) {\n console.log(error);\n showSnackbarError(normalizeError(error));\n }\n }\n },\n },\n};\n</script>\n\n<style module lang=\"less\" src=\"./connect.less\"></style>","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false}]...
|
8951690351009007186
|
-8754547338898009690
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20692-fix-integration- Project: faVsco.js, menu
JY-20692-fix-integration-app-[API_KEY], menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
cachedStages
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
2/4
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Code changed:
Hide
Sync Changes
Hide This Notification
33
2
19
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use HubSpot\Client\Crm\Deals\Model\CollectionResponseAssociatedId;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Models\Account;
use Exception;
use Jiminny\Component\DealInsights\Forecast\Forecast;
use Jiminny\Jobs\Crm\MatchActivitiesToNewOpportunity;
use Jiminny\Models\Contact;
use Jiminny\Models\Crm\BusinessProcess;
use Jiminny\Exceptions\CrmException;
use Jiminny\Models\Opportunity;
use Illuminate\Support\Collection;
use Jiminny\Models\Stage;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Crm\Hubspot\DealFieldsService;
use Jiminny\Services\Crm\Hubspot\OpportunitySyncStrategy\HubspotSingleSyncStrategy;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Services\Crm\OpportunitySyncStrategyResolver;
use Jiminny\Utils\CurrencyFormatter;
/**
* Optimized sync methods for better performance
* These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains
*/
trait OpportunitySyncTrait
{
private const int BATCH_SIZE = 100;
private const int BATCH_PROCESS_SIZE = 800;
protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;
protected CrmEntityRepository $crmEntityRepository;
protected DealFieldsService $dealFieldsService;
private ?array $cachedClosedDealStages = null;
private array $cachedBusinessProcesses = [];
private array $cachedStages = [];
public function syncOpportunities(array $parameters, ?string $strategy = null): int
{
$strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);
$parameters['config'] = $this->config;
$syncCount = 0;
$reportedTotal = 0;
$lastSyncedId = [];
try {
foreach ($strategies as $strategyName => $syncStrategy) {
$this->logger->info(
'[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .
$strategyName
);
$total = 0;
$lastId = null;
$buffer = [];
// HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies
foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {
$buffer[] = $hsOpportunity;
// process every 800 rows (fits < 1 000 association limit)
if (\count($buffer) >= self::BATCH_PROCESS_SIZE) {
$syncCount += $this->processOpportunityBatch($buffer);
$buffer = [];
}
}
// leftovers
if ($buffer) {
$syncCount += $this->processOpportunityBatch($buffer);
}
$reportedTotal += $total;
$lastSyncedId = $lastId;
}
} catch (\HubSpot\Client\Crm\Deals\ApiException | CrmException $e) {
$this->handleSyncException($e, $parameters);
}
$this->logger->info(
'[HubSpot] Synced opportunities',
[
'team' => $this->team->getId(),
'sync_count' => $syncCount,
'total' => $reportedTotal,
'last_synced_id' => $lastSyncedId,
]
);
return $reportedTotal;
}
private function handleSyncException(\Throwable $e, array $parameters): void
{
if (($parameters['since'] ?? null) instanceof Carbon) {
$parameters['since'] = $parameters['since']->toDateTimeString();
}
$parameters['config'] = $this->config->getId();
$this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [
'teamId' => $this->team->getUuid(),
'parameters' => $parameters,
'reason' => $e->getMessage(),
]);
}
/**
* @inheritdoc
*/
public function syncOpportunity(string $crmId): ?Opportunity
{
$strategy = $this->opportunitySyncStrategyResolver->resolve(
$this->config,
OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,
);
$parameters = [
'config' => $this->config,
'crm_id' => $crmId,
];
try {
if (! $strategy instanceof HubspotSingleSyncStrategy) {
throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');
}
$hsOpportunity = $strategy->fetchOpportunity($parameters);
} catch (\HubSpot\Client\Crm\Deals\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
$hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);
return $this->importOrUpdateOpportunity($hsOpportunity);
}
/**
* Process webhook-collected opportunity batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportOpportunityBatch jobs for batch processing.
*
* @return int Number of opportunity IDs dispatched to jobs
*/
public function batchSyncOpportunities(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,
$configId
);
}
/**
* Import a batch of opportunities by their CRM IDs.
* Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().
*
* @param array<string> $crmIds HubSpot deal CRM IDs
*
* @return array{success: array, failed_ids: array, errors?: array<string, string>}
*/
public function importOpportunityBatchByIds(array $crmIds): array
{
$fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);
$allDeals = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
$deals = $this->client->getOpportunitiesByIds($chunk, $fields);
foreach ($deals as $deal) {
$allDeals[] = $deal;
}
}
// IDs not returned by HubSpot are likely deleted or inaccessible deals.
// These are not failures — retrying won't bring them back.
$fetchedIds = array_map('strval', array_column($allDeals, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allDeals),
]);
}
if (empty($allDeals)) {
return ['success' => [], 'failed_ids' => []];
}
return $this->importOpportunityBatch($allDeals);
}
private function getClosedDealStages(): array
{
if ($this->cachedClosedDealStages !== null) {
return $this->cachedClosedDealStages;
}
$stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);
$data = [
'lost' => [],
'won' => [],
];
foreach ($stages as $stage) {
if ($stage->probability == 0.00) {
$data['lost'][] = $stage->crm_provider_id;
}
if ($stage->probability == 100.00) {
$data['won'][] = $stage->crm_provider_id;
}
}
$this->cachedClosedDealStages = $data;
return $data;
}
/**
* Import deals into the database with pre-fetched associations.
*
* API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT
* caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()
* where Laravel retries the whole job with backoff. After all retries exhausted,
* failed() requeues all IDs to Redis.
*
* The per-deal loop catches exceptions individually. A deal can end up in three states:
* - success: imported/updated successfully
* - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)
* These are permanent issues — retrying won't fix them.
* - skipped (null): missing dependencies (no account, unknown pipeline/stage).
* This is acceptable — the deal cannot be imported until those exist.
*/
private function importOpportunityBatch(array $deals): array
{
$syncedOpportunities = [
'success' => [],
'failed_ids' => [],
];
$dealIds = array_column($deals, 'id');
// Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the
// queue job retries the whole batch and eventually requeues all deal IDs back to Redis.
try {
$companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');
$contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');
$associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);
$existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(
$this->config,
array_map('strval', $dealIds)
);
$existingCrmIdSet = array_flip($existingCrmIds);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [
'teamId' => $this->team->getId(),
'dealCount' => count($dealIds),
'error' => $e->getMessage(),
]);
throw $e;
}
foreach ($deals as $deal) {
try {
$deal['associations'] = $this->prepareAssociationsForOpportunity(
$deal['id'],
$companyAssociations,
$contactAssociations,
$associationsData
);
$syncedOpportunity = $this->importOrUpdateOpportunity(
$deal,
isset($existingCrmIdSet[(string) $deal['id']])
);
if ($syncedOpportunity) {
$syncedOpportunities['success'][] = $syncedOpportunity;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [
'teamId' => $this->team->getId(),
'crmId' => $deal['id'],
'error' => $e->getMessage(),
]);
$syncedOpportunities['failed_ids'][] = $deal['id'];
$syncedOpportunities['errors'][$deal['id']] = $e->getMessage();
}
}
return $syncedOpportunities;
}
/**
* Prepare associated entities for opportunities with optimized batch processing
* Returns structured data with CRM ID to DB ID mappings for each opportunity
*/
private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array
{
// Step 1: Collect all unique company and contact IDs from associations
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
// Step 2: Batch sync missing entities and get CRM ID to DB ID mappings
$companyIdMappings = [];
$contactIdMappings = [];
if (! empty($allCompanyIds)) {
$companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);
}
if (! empty($allContactIds)) {
$contactIdMappings = $this->prepareAssociatedContacts($allContactIds);
}
return [
'company_id_mappings' => $companyIdMappings,
'contact_id_mappings' => $contactIdMappings,
];
}
/**
* Flatten association data to get unique IDs
*/
private function flattenAssociationIds(array $associations): array
{
$ids = [];
foreach ($associations as $dealAssociations) {
if (is_array($dealAssociations)) {
foreach ($dealAssociations as $id) {
$ids[$id] = true;
}
}
}
return array_keys($ids);
}
/**
* Batch sync missing accounts
*/
private function prepareAssociatedAccounts(array $companyIds): array
{
// Find which accounts already exist
$existingAccounts = $this->crmEntityRepository
->findAccountsByExternalIds($this->config, $companyIds);
$existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();
$existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {
return [$account->getCrmProviderId() => $account->getId()];
})->toArray();
$missingCompanyIds = array_diff($companyIds, $existingCompanyIds);
if (empty($missingCompanyIds)) {
return $existingAccountsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [
'teamId' => $this->team->getUuid(),
'total_companies' => count($companyIds),
'existing_companies' => count($existingCompanyIds),
'missing_companies' => count($missingCompanyIds),
]);
// we already have limit on opportunity ids count
// Initialize variable before try block
$syncedAccountsData = [];
try {
$syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [
'size' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
$syncedAccountsData = [];
}
return $existingAccountsData + $syncedAccountsData;
}
/**
* Prepare associated contacts - find existing and sync missing ones
* Returns mapping of CRM ID to DB ID
*/
private function prepareAssociatedContacts(array $contactIds): array
{
// Find which contacts already exist
$existingContacts = $this->crmEntityRepository
->findContactsByExternalIds($this->config, $contactIds);
$existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();
// Create mapping for existing contacts
$existingContactsData = $existingContacts->mapWithKeys(function ($contact) {
return [$contact->getCrmProviderId() => $contact->getId()];
})->toArray();
$missingContactIds = array_diff($contactIds, $existingContactIds);
if (empty($missingContactIds)) {
return $existingContactsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [
'teamId' => $this->team->getUuid(),
'total_contacts' => count($contactIds),
'existing_contacts' => count($existingContactIds),
'missing_contacts' => count($missingContactIds),
]);
// Sync missing contacts using batch API
try {
$syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [
'size' => count($missingContactIds),
'error' => $e->getMessage(),
]);
$syncedContactsData = [];
}
return $existingContactsData + $syncedContactsData;
}
private function batchSyncCrmObjects(string $objectType, array $crmIds): array
{
$syncObjects = [];
$crmObjectIds = array_values($crmIds);
foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {
try {
$objects = $objectType === 'companies' ?
$this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :
$this->client->getContactsByIds($chunk, $this->getContactFields());
foreach ($objects as $objectId => $objectData) {
$this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [
'requested_count' => count($chunk),
'synced_count' => count($objects),
]);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [
'ids' => $chunk,
'error' => $e->getMessage(),
]);
}
}
return $syncObjects;
}
private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void
{
try {
$object = $objectType === 'companies' ?
$this->importAccount($objectData) :
$this->importContact($objectData);
if ($object) {
$syncObjects[$object->getCrmProviderId()] = $object->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [
'id' => $objectId,
'error' => $e->getMessage(),
]);
}
}
/**
* Prepare associations for a single opportunity
*
* The return value is an array with the following structure:
* [
* 'companies' => [
* $companyCrmId => $companyId,
* ...
* ],
* 'contacts' => [
* $contactCrmId => $contactId,
* ...
* ],
* 'account_id' => $accountId,
* ]
*/
private function prepareAssociationsForOpportunity(
string $oppCrmId,
array $companyAssociations,
array $contactAssociations,
array $associationsData
): array {
$associations = [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
$oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];
foreach ($oppCompanyIds as $companyCrmId) {
if (isset($associationsData['company_id_mappings'][$companyCrmId])) {
$associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];
// Set primary account (first company becomes primary account)
if ($associations['account_id'] === null) {
$associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];
}
}
}
$oppContactIds = $contactAssociations[$oppCrmId] ?? [];
foreach ($oppContactIds as $contactCrmId) {
if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {
$associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];
}
}
return $associations;
}
/**
* Update only associations for an opportunity
*/
private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void
{
// Update contact associations
$this->importOpportunityContacts($opportunity, $associations['contacts']);
// Update company (account) associations
$this->updateOpportunityAccount($opportunity, $associations['account_id']);
}
/**
* Remove all contact associations from an opportunity
*/
private function removeAllOpportunityContacts(Opportunity $opportunity): void
{
$currentCount = (int) $opportunity->contacts()->count();
if ($currentCount > 0) {
$opportunity->contacts()->detach();
$this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_count' => $currentCount,
]);
}
}
private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void
{
if ($accountId === null) {
// No account ID provided - keep current account
return;
}
$currentAccountId = $opportunity->getAccountId();
// Only update if account has changed
if ($currentAccountId !== $accountId) {
$opportunity->account_id = $accountId;
$opportunity->save();
$this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [
'opportunity_id' => $opportunity->getId(),
'old_account_id' => $currentAccountId,
'new_account_id' => $accountId,
]);
}
}
/**
* Find existing opportunities by external IDs (OPTIMIZED VERSION)
* Uses batch query for better performance
*/
private function findExistingOpportunities(array $crmIds): Collection
{
return $this->crmEntityRepository
->findOpportunitiesByExternalIds($this->config, $crmIds);
}
private function processOpportunityBatch(array $opportunities): int
{
$syncedOpportunities = $this->importOpportunityBatch($opportunities);
return count($syncedOpportunities['success'] ?? []);
}
/**
* Convert single deal associations from HubSpot format to internal format
* Handles both HubSpot SDK objects and array formats
*
* @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed
*
* @return array Processed associations with DB IDs
*/
private function convertDealAssociations(array $opportunityAssociations): array
{
$associations = $this->initializeAssociationsStructure();
if (empty($opportunityAssociations)) {
return $associations;
}
$associationIds = $this->extractAssociationIds($opportunityAssociations);
$this->processCompanyAssociations($associationIds, $associations);
$this->processContactAssociations($associationIds, $associations);
return $associations;
}
private function initializeAssociationsStructure(): array
{
return [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
}
private function extractAssociationIds(array $opportunityAssociations): array
{
$associationIds = [];
foreach ($opportunityAssociations as $type => $associationData) {
if (! empty($associationData)) {
$associationIds[$type] = $this->convertSingleDealAssociations($associationData);
}
}
return $associationIds;
}
private function processCompanyAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['companies'])) {
return;
}
$companyId = $associationIds['companies'][0];
$account = $this->findOrSyncAccount($companyId);
if ($account instanceof Account) {
$associations['companies'][$companyId] = $account->getId();
$associations['account_id'] = $account->getId();
}
}
private function processContactAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['contacts'])) {
return;
}
foreach ($associationIds['contacts'] as $contactId) {
$contact = $this->findOrSyncContact($contactId);
if ($contact instanceof Contact) {
$associations['contacts'][$contactId] = $contact->getId();
}
}
}
private function findOrSyncAccount(string $companyId): ?Account
{
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);
if (! $account instanceof Account) {
$account = $this->syncAccount($companyId);
}
return $account;
}
private function findOrSyncContact(string $contactId): ?Contact
{
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);
if (! $contact instanceof Contact) {
$contact = $this->syncContact($contactId);
}
return $contact;
}
private function convertSingleDealAssociations($opportunityAssociations = null): array
{
$associationData = [];
if ($opportunityAssociations === null) {
return $associationData;
}
// Handle array input (from extractAssociationIds)
if (is_array($opportunityAssociations)) {
return $opportunityAssociations;
}
// Handle CollectionResponseAssociatedId object
if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {
foreach ($opportunityAssociations->getResults() as $association) {
$associationData[] = $association->getId();
}
}
return $associationData;
}
private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity
{
if (empty($crmData['properties'])) {
return null;
}
$crmId = (string) $crmData['id'];
$properties = $crmData['properties'];
$associations = $crmData['associations'] ?? [];
$opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(
$this->config,
$crmId
);
if ($opportunityExists) {
return $this->updateOpportunity($crmId, $properties, $associations);
} else {
return $this->createOpportunity($crmId, $properties, $associations);
}
}
/**
* Create new opportunity
*/
private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity
{
$accountId = $this->resolveAccountId($associations);
if (! $accountId) {
return null;
}
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
if (! $businessProcess) {
return null;
}
$stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);
if (! $stage) {
return null;
}
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->importOpportunityContacts($opportunity, $associations['contacts']);
if ($opportunity->wasRecentlyCreated) {
MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());
}
return $opportunity;
}
/**
* Update existing opportunity
*/
private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity
{
$accountId = $this->resolveAccountId($associations);
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
$stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->updateOpportunityAssociations($opportunity, $associations);
return $opportunity;
}
private function resolveAccountId(array $associations): ?int
{
if (! empty($associations['accountId'])) {
return $associations['accountId'];
}
if (empty($associations)) {
return null;
}
// we can't resolve multiple account ids (currently SDK returns one company)
foreach ($associations['companies'] as $accountId) {
return $accountId;
}
return null;
}
private function buildOpportunityData(
array $properties,
?int $accountId,
?BusinessProcess $businessProcess,
?Stage $stage
): array {
$ownerId = null;
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$name = 'Unknown';
if (isset($properties['dealname'])) {
$name = mb_strimwidth($properties['dealname'], 0, 128);
}
$amount = $this->resolveAmount($properties);
$currency = $properties['deal_currency_code'] ?? null;
$closeDate = null;
if (! empty($properties['closedate'])) {
$closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');
}
$remotelyCreatedAt = null;
if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {
$date = $this->parseCleanDatetime($properties['createdate']);
$remotelyCreatedAt = $date?->format('Y-m-d H:i:s');
}
$closedStages = $this->getClosedDealStages();
$isWon = in_array($properties['dealstage'], $closedStages['won']);
$isLost = in_array($properties['dealstage'], $closedStages['lost']);
$data = [
'team_id' => $this->team->getId(),
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => $name,
'value' => ! empty($amount) ? $amount : null,
'currency_code' => CurrencyFormatter::formatCode($currency),
'close_date' => $closeDate,
'is_closed' => $isWon || $isLost,
'is_won' => $isWon,
'remotely_created_at' => $remotelyCreatedAt,
'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),
'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),
];
if ($accountId) {
$data['account_id'] = $accountId;
}
if ($stage) {
$data['stage_id'] = $stage->id;
}
if ($businessProcess) {
$recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);
if ($recordType) {
$data['record_type_id'] = $recordType->id;
}
}
return $data;
}
private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess
{
if ($pipelineId === null) {
return null;
}
if (isset($this->cachedBusinessProcesses[$pipelineId])) {
return $this->cachedBusinessProcesses[$pipelineId];
}
$businessProcess = $this->getBusinessProcess($pipelineId);
if (! $businessProcess instanceof BusinessProcess) {
$this->importStages();
$businessProcess = $this->getBusinessProcess($pipelineId);
}
if (! $businessProcess instanceof BusinessProcess) {
$this->logger->info(
'[HubSpot] Deal is not attached to a pipeline',
[
'pipeline' => $pipelineId]
);
}
$this->cachedBusinessProcesses[$pipelineId] = $businessProcess;
return $businessProcess;
}
private function getBusinessProcess(string $pipelineId): ?BusinessProcess
{
return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);
}
private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage
{
if (empty($stageId)) {
return null;
}
$cacheKey = $businessProcess->getId() . ':' . $stageId;
if (isset($this->cachedStages[$cacheKey])) {
return $this->cachedStages[$cacheKey];
}
$stage = $this->crmEntityRepository->getPipelineStageByConditions(
$businessProcess,
[
'crm_provider_id' => $stageId,
'type' => Stage::TYPE_OPPORTUNITY,
]
);
if ($stage === null) {
$this->importStages(null, $stageId);
}
if ($stage === null) {
$this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);
}
$this->cachedStages[$cacheKey] = $stage;
return $stage;
}
private function resolveAmount(array $properties): ?string
{
$amount = null;
if (! empty($properties['amount'])) {
$amount = str_replace(',', '', $properties['amount']);
}
if ($this->config->hasDefaultCurrencyFieldSet()) {
$valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();
$amount = $properties[$valueFieldName] ?? $amount;
}
return $amount;
}
private function parseCleanDatetime(string $datetime): ?Carbon
{
// Treat pre-1980 values as invalid
$minValidDate = Carbon::parse('1980-01-01 00:00:00');
try {
$date = Carbon::parse($datetime);
if ($minValidDate->gt($date)) {
return null;
}
return $date;
} catch (Exception) {
return null; // On parse error, treat as null
}
}
private function resolveDealProbability(?string $stageProbability): int
{
if ($stageProbability === null) {
return 0;
}
$probability = (float) $stageProbability;
return $probability > 1 ? 0 : (int) ($probability * 100);
}
private function resolveForecastCategory(?string $forecastCategory): string
{
if (! $forecastCategory) {
return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;
}
$forecastCategory = str_replace('_', ' ', $forecastCategory);
return ucwords(strtolower($forecastCategory));
}
private function importExternalFieldData(array $properties, int $opportunityId): void
{
$crmFields = $this->getOpportunitySyncableFields();
$this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);
}
private function importOpportunityContacts(Opportunity $opportunity, array $associations): void
{
// Handle empty or missing contact associations
if (empty($associations)) {
// Remove all existing contact associations if none provided
$this->removeAllOpportunityContacts($opportunity);
return;
}
// Use differential sync approach for better performance and accuracy
$this->syncOpportunityContactsDifferential($opportunity, $associations);
}
/**
* Sync opportunity contacts using differential approach
* This compares current vs new associations and only makes necessary changes
*/
private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void
{
$currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);
$contactAssociationIds = array_keys($contactAssociations);
$contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);
$contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);
if (empty($contactsToAdd) && empty($contactsToRemove)) {
return;
}
$this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);
$this->removeContactAssociations($opportunity, $contactsToRemove);
$this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);
}
private function getCurrentContactCrmIds(Opportunity $opportunity): array
{
return $opportunity->contacts()
->pluck('contacts.crm_provider_id')
->toArray();
}
private function logContactAssociationChanges(
Opportunity $opportunity,
array $currentContactCrmIds,
array $contactAssociations,
array $contactsToAdd,
array $contactsToRemove
): void {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [
'opportunity_id' => $opportunity->getId(),
'current_contacts' => $currentContactCrmIds,
'new_contacts' => $contactAssociations,
'contacts_to_add' => $contactsToAdd,
'contacts_to_remove' => $contactsToRemove,
]);
}
private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void
{
if (empty($contactsToRemove)) {
return;
}
$contactsToDetach = $opportunity->contacts()
->whereIn('contacts.crm_provider_id', $contactsToRemove)
->pluck('contacts.id')
->toArray();
if (! empty($contactsToDetach)) {
$opportunity->contacts()->detach($contactsToDetach);
$this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_contact_crm_ids' => $contactsToRemove,
'removed_contact_count' => count($contactsToDetach),
]);
}
}
private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void
{
if (empty($contactsToAdd)) {
return;
}
$contactsAdded = [];
foreach ($contactsToAdd as $crmId) {
$id = $contactAssociations[$crmId];
if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {
$contactsAdded[] = $crmId;
}
}
$this->logAddedContacts($opportunity, $contactsAdded);
}
private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool
{
try {
$contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);
if (! $contact) {
return false;
}
return $this->performContactAttachment($opportunity, $contact, $crmId);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [
'opportunity_id' => $opportunity->getId(),
'contact_crm_id' => $crmId,
'error' => $e->getMessage(),
]);
return false;
}
}
private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool
{
try {
$opportunity->contacts()->attach($contact->getId(), [
'crm_provider_id' => $crmId,
]);
return true;
} catch (\Illuminate\Database\QueryException $e) {
if (str_contains($e->getMessage(), 'Duplicate entry')) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [
'contact_id' => $contact->getId(),
'contact_crm_id' => $crmId,
'opportunity_id' => $opportunity->getId(),
]);
return false;
}
throw $e;
}
}
private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void
{
if (! empty($contactsAdded)) {
$this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [
'opportunity_id' => $opportunity->getId(),
'contacts_to_add_count' => count($contactsAdded),
'added_contact_crm_ids' => $contactsAdded,
'added_contacts_count' => count($contactsAdded),
]);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
1
Previous Highlighted Error
Next Highlighted Error
<template>
<WelcomeLayout
title="Account disconnected"
textPosition="center"
:icon="faUnlink"
:class="$style.layout"
>
<div :class="$style.container" v-if="providersLoaded">
<p>
<strong>
It looks like your {{ localProvider.displayName }} account has become
disconnected
</strong>
</p>
<p :class="$style.small">Please re-connect to continue</p>
<p v-if="isInIframe">
We'll open the {{ localProvider.displayName }} authentication in a new
tab. Please return here and refresh the page once complete
</p>
<GoogleLikeButton
v-if="localProvider.viaIntegrationApp && crmTokenLoaded"
as="a"
:key="localProvider.name"
:brand-logo="localProvider.name"
:class="$style.connectButton"
@click="integrationAppOnClick"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
<GoogleLikeButton
v-if="!localProvider.viaIntegrationApp"
as="a"
:key="localProvider.name"
:href="`/auth/redirect/${localProvider.name}`"
:target="target"
:brand-logo="localProvider.name"
:class="$style.connectButton"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
</div>
<BuildInfo />
<KioskBanner />
</WelcomeLayout>
</template>
<script>
import window from "window";
import axios from "axios";
import { faUnlink } from "@fortawesome/pro-regular-svg-icons";
import isInIframe from "@/utils/isInIframe";
import BuildInfo from "@/components/layout/BuildInfo/BuildInfo.vue";
import KioskBanner from "@/components/shared/KioskBanner/KioskBanner.vue";
import WelcomeLayout from "@/components/layout/WelcomeLayout/WelcomeLayout.vue";
import GoogleLikeButton from "@/components/shared/Buttons/GoogleLikeButton.vue";
import { showSnackbarError, normalizeError } from "@/utils/index";
import { IntegrationAppClient } from "@integration-app/sdk";
export default {
name: "ConnectPage",
components: {
BuildInfo,
KioskBanner,
WelcomeLayout,
GoogleLikeButton,
},
data() {
return {
...window.connectData,
crmToken: null,
faUnlink,
isInIframe,
providers: [],
providersLoaded: false,
crmTokenLoaded: false,
};
},
computed: {
localProvider() {
return this.providers.find((e) => e.name === this.provider);
},
target() {
return this.isInIframe ? "_blank" : null;
},
},
created() {
this.getProviders();
},
mounted() {
this.showErrors();
},
watch: {
providersLoaded() {
if (this.providersLoaded) {
this.prepareIntegrationAppConnection();
}
},
},
methods: {
showErrors() {
if (!this.error) return;
showSnackbarError(this.error, undefined, undefined, false);
},
unwrapEntityResponse({ data }) {
return data.map(({ icon, name, displayName, viaIntegrationApp }) => {
return { icon, name, displayName, viaIntegrationApp };
});
},
async getProviders() {
try {
const response = await axios.get("/api/v1/connect-providers");
this.providers = this.unwrapEntityResponse(response);
this.providersLoaded = true;
} catch {
showSnackbarError(
"An error occurred, while loading form data (connect providers).",
);
}
},
async prepareIntegrationAppConnection() {
if (this.localProvider.viaIntegrationApp) {
try {
const response = await axios.get("/api/v1/integration-app-token");
this.crmToken = response.data.token;
this.crmTokenLoaded = true;
} catch (error) {
console.log(error);
showSnackbarError(
`An error occurred while preparing the page.
Try refreshing, if the error persists get in touch with the Jiminny team.`,
);
}
}
},
async integrationAppOnClick() {
console.log('[IntegrationApp] integrationAppOnClick called');
const integrationApp = new IntegrationAppClient({
token: this.crmToken,
});
const connection = await integrationApp
.integration(this.localProvider.name)
.openNewConnection({
showPoweredBy: false,
allowMultipleConnections: false,
}).catch((err) => {
console.log('[IntegrationApp] openNewConnection rejected:', err);
return null;
});
console.log('[IntegrationApp] openNewConnection resolved:', JSON.stringify(connection));
// [IntegrationApp] openNewConnection resolved: {
// "id":"69e0b41a67d0068c2ca0b48e",
// "name":"Zoho CRM",
// "userId":"1ece66c8-feb1-4df1-b321-21607daf4623",
// "tenantId":"69e0b3faef3e7b6248189289",
// "isTest":false,
// "connected":true,
// "state":"READY",
// "errors":[],
// "integrationId":"66fe6c913202f3a165e3c14d",
// "externalAppId":"6671653e7e2d642e4e41b0fa",
// "authOptionKey":"",
// "createdAt":"2026-04-16T10:04:10.420Z",
// "updatedAt":"2026-04-16T10:04:10.575Z",
// "retryAttempts":0,
// "isDeactivated":false
// }
if (connection && connection.disconnected !== true && connection.connected !== false) {
console.log('[IntegrationApp] connection condition matched');
try {
const saveRequest = await axios.post(
"/api/v1/integration-app-connect",
);
if (saveRequest.data && saveRequest.data.success === true) {
/** If all is good refresh the page here */
window.location = "/dashboard";
return;
}
throw new Error(saveRequest.data.message);
} catch (error) {
console.log(error);
showSnackbarError(normalizeError(error));
}
}
},
},
};
</script>
<style module lang="less" src="./connect.less"></style>...
|
NULL
|
|
46969
|
NULL
|
0
|
2026-04-17T10:58:18.924024+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776423498924_m1.jpg...
|
PhpStorm
|
faVsco.js – ~/jiminny/app/front-end/src/components faVsco.js – ~/jiminny/app/front-end/src/components/connect/connect.vue...
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20692-fix-integration- Project: faVsco.js, menu
JY-20692-fix-integration-app-[API_KEY], menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
cachedStages
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
2/4
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Code changed:
Hide
Sync Changes
Hide This Notification
33
2
19
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use HubSpot\Client\Crm\Deals\Model\CollectionResponseAssociatedId;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Models\Account;
use Exception;
use Jiminny\Component\DealInsights\Forecast\Forecast;
use Jiminny\Jobs\Crm\MatchActivitiesToNewOpportunity;
use Jiminny\Models\Contact;
use Jiminny\Models\Crm\BusinessProcess;
use Jiminny\Exceptions\CrmException;
use Jiminny\Models\Opportunity;
use Illuminate\Support\Collection;
use Jiminny\Models\Stage;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Crm\Hubspot\DealFieldsService;
use Jiminny\Services\Crm\Hubspot\OpportunitySyncStrategy\HubspotSingleSyncStrategy;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Services\Crm\OpportunitySyncStrategyResolver;
use Jiminny\Utils\CurrencyFormatter;
/**
* Optimized sync methods for better performance
* These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains
*/
trait OpportunitySyncTrait
{
private const int BATCH_SIZE = 100;
private const int BATCH_PROCESS_SIZE = 800;
protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;
protected CrmEntityRepository $crmEntityRepository;
protected DealFieldsService $dealFieldsService;
private ?array $cachedClosedDealStages = null;
private array $cachedBusinessProcesses = [];
private array $cachedStages = [];
public function syncOpportunities(array $parameters, ?string $strategy = null): int
{
$strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);
$parameters['config'] = $this->config;
$syncCount = 0;
$reportedTotal = 0;
$lastSyncedId = [];
try {
foreach ($strategies as $strategyName => $syncStrategy) {
$this->logger->info(
'[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .
$strategyName
);
$total = 0;
$lastId = null;
$buffer = [];
// HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies
foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {
$buffer[] = $hsOpportunity;
// process every 800 rows (fits < 1 000 association limit)
if (\count($buffer) >= self::BATCH_PROCESS_SIZE) {
$syncCount += $this->processOpportunityBatch($buffer);
$buffer = [];
}
}
// leftovers
if ($buffer) {
$syncCount += $this->processOpportunityBatch($buffer);
}
$reportedTotal += $total;
$lastSyncedId = $lastId;
}
} catch (\HubSpot\Client\Crm\Deals\ApiException | CrmException $e) {
$this->handleSyncException($e, $parameters);
}
$this->logger->info(
'[HubSpot] Synced opportunities',
[
'team' => $this->team->getId(),
'sync_count' => $syncCount,
'total' => $reportedTotal,
'last_synced_id' => $lastSyncedId,
]
);
return $reportedTotal;
}
private function handleSyncException(\Throwable $e, array $parameters): void
{
if (($parameters['since'] ?? null) instanceof Carbon) {
$parameters['since'] = $parameters['since']->toDateTimeString();
}
$parameters['config'] = $this->config->getId();
$this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [
'teamId' => $this->team->getUuid(),
'parameters' => $parameters,
'reason' => $e->getMessage(),
]);
}
/**
* @inheritdoc
*/
public function syncOpportunity(string $crmId): ?Opportunity
{
$strategy = $this->opportunitySyncStrategyResolver->resolve(
$this->config,
OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,
);
$parameters = [
'config' => $this->config,
'crm_id' => $crmId,
];
try {
if (! $strategy instanceof HubspotSingleSyncStrategy) {
throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');
}
$hsOpportunity = $strategy->fetchOpportunity($parameters);
} catch (\HubSpot\Client\Crm\Deals\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
$hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);
return $this->importOrUpdateOpportunity($hsOpportunity);
}
/**
* Process webhook-collected opportunity batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportOpportunityBatch jobs for batch processing.
*
* @return int Number of opportunity IDs dispatched to jobs
*/
public function batchSyncOpportunities(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,
$configId
);
}
/**
* Import a batch of opportunities by their CRM IDs.
* Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().
*
* @param array<string> $crmIds HubSpot deal CRM IDs
*
* @return array{success: array, failed_ids: array, errors?: array<string, string>}
*/
public function importOpportunityBatchByIds(array $crmIds): array
{
$fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);
$allDeals = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
$deals = $this->client->getOpportunitiesByIds($chunk, $fields);
foreach ($deals as $deal) {
$allDeals[] = $deal;
}
}
// IDs not returned by HubSpot are likely deleted or inaccessible deals.
// These are not failures — retrying won't bring them back.
$fetchedIds = array_map('strval', array_column($allDeals, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allDeals),
]);
}
if (empty($allDeals)) {
return ['success' => [], 'failed_ids' => []];
}
return $this->importOpportunityBatch($allDeals);
}
private function getClosedDealStages(): array
{
if ($this->cachedClosedDealStages !== null) {
return $this->cachedClosedDealStages;
}
$stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);
$data = [
'lost' => [],
'won' => [],
];
foreach ($stages as $stage) {
if ($stage->probability == 0.00) {
$data['lost'][] = $stage->crm_provider_id;
}
if ($stage->probability == 100.00) {
$data['won'][] = $stage->crm_provider_id;
}
}
$this->cachedClosedDealStages = $data;
return $data;
}
/**
* Import deals into the database with pre-fetched associations.
*
* API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT
* caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()
* where Laravel retries the whole job with backoff. After all retries exhausted,
* failed() requeues all IDs to Redis.
*
* The per-deal loop catches exceptions individually. A deal can end up in three states:
* - success: imported/updated successfully
* - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)
* These are permanent issues — retrying won't fix them.
* - skipped (null): missing dependencies (no account, unknown pipeline/stage).
* This is acceptable — the deal cannot be imported until those exist.
*/
private function importOpportunityBatch(array $deals): array
{
$syncedOpportunities = [
'success' => [],
'failed_ids' => [],
];
$dealIds = array_column($deals, 'id');
// Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the
// queue job retries the whole batch and eventually requeues all deal IDs back to Redis.
try {
$companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');
$contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');
$associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);
$existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(
$this->config,
array_map('strval', $dealIds)
);
$existingCrmIdSet = array_flip($existingCrmIds);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [
'teamId' => $this->team->getId(),
'dealCount' => count($dealIds),
'error' => $e->getMessage(),
]);
throw $e;
}
foreach ($deals as $deal) {
try {
$deal['associations'] = $this->prepareAssociationsForOpportunity(
$deal['id'],
$companyAssociations,
$contactAssociations,
$associationsData
);
$syncedOpportunity = $this->importOrUpdateOpportunity(
$deal,
isset($existingCrmIdSet[(string) $deal['id']])
);
if ($syncedOpportunity) {
$syncedOpportunities['success'][] = $syncedOpportunity;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [
'teamId' => $this->team->getId(),
'crmId' => $deal['id'],
'error' => $e->getMessage(),
]);
$syncedOpportunities['failed_ids'][] = $deal['id'];
$syncedOpportunities['errors'][$deal['id']] = $e->getMessage();
}
}
return $syncedOpportunities;
}
/**
* Prepare associated entities for opportunities with optimized batch processing
* Returns structured data with CRM ID to DB ID mappings for each opportunity
*/
private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array
{
// Step 1: Collect all unique company and contact IDs from associations
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
// Step 2: Batch sync missing entities and get CRM ID to DB ID mappings
$companyIdMappings = [];
$contactIdMappings = [];
if (! empty($allCompanyIds)) {
$companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);
}
if (! empty($allContactIds)) {
$contactIdMappings = $this->prepareAssociatedContacts($allContactIds);
}
return [
'company_id_mappings' => $companyIdMappings,
'contact_id_mappings' => $contactIdMappings,
];
}
/**
* Flatten association data to get unique IDs
*/
private function flattenAssociationIds(array $associations): array
{
$ids = [];
foreach ($associations as $dealAssociations) {
if (is_array($dealAssociations)) {
foreach ($dealAssociations as $id) {
$ids[$id] = true;
}
}
}
return array_keys($ids);
}
/**
* Batch sync missing accounts
*/
private function prepareAssociatedAccounts(array $companyIds): array
{
// Find which accounts already exist
$existingAccounts = $this->crmEntityRepository
->findAccountsByExternalIds($this->config, $companyIds);
$existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();
$existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {
return [$account->getCrmProviderId() => $account->getId()];
})->toArray();
$missingCompanyIds = array_diff($companyIds, $existingCompanyIds);
if (empty($missingCompanyIds)) {
return $existingAccountsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [
'teamId' => $this->team->getUuid(),
'total_companies' => count($companyIds),
'existing_companies' => count($existingCompanyIds),
'missing_companies' => count($missingCompanyIds),
]);
// we already have limit on opportunity ids count
// Initialize variable before try block
$syncedAccountsData = [];
try {
$syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [
'size' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
$syncedAccountsData = [];
}
return $existingAccountsData + $syncedAccountsData;
}
/**
* Prepare associated contacts - find existing and sync missing ones
* Returns mapping of CRM ID to DB ID
*/
private function prepareAssociatedContacts(array $contactIds): array
{
// Find which contacts already exist
$existingContacts = $this->crmEntityRepository
->findContactsByExternalIds($this->config, $contactIds);
$existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();
// Create mapping for existing contacts
$existingContactsData = $existingContacts->mapWithKeys(function ($contact) {
return [$contact->getCrmProviderId() => $contact->getId()];
})->toArray();
$missingContactIds = array_diff($contactIds, $existingContactIds);
if (empty($missingContactIds)) {
return $existingContactsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [
'teamId' => $this->team->getUuid(),
'total_contacts' => count($contactIds),
'existing_contacts' => count($existingContactIds),
'missing_contacts' => count($missingContactIds),
]);
// Sync missing contacts using batch API
try {
$syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [
'size' => count($missingContactIds),
'error' => $e->getMessage(),
]);
$syncedContactsData = [];
}
return $existingContactsData + $syncedContactsData;
}
private function batchSyncCrmObjects(string $objectType, array $crmIds): array
{
$syncObjects = [];
$crmObjectIds = array_values($crmIds);
foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {
try {
$objects = $objectType === 'companies' ?
$this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :
$this->client->getContactsByIds($chunk, $this->getContactFields());
foreach ($objects as $objectId => $objectData) {
$this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [
'requested_count' => count($chunk),
'synced_count' => count($objects),
]);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [
'ids' => $chunk,
'error' => $e->getMessage(),
]);
}
}
return $syncObjects;
}
private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void
{
try {
$object = $objectType === 'companies' ?
$this->importAccount($objectData) :
$this->importContact($objectData);
if ($object) {
$syncObjects[$object->getCrmProviderId()] = $object->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [
'id' => $objectId,
'error' => $e->getMessage(),
]);
}
}
/**
* Prepare associations for a single opportunity
*
* The return value is an array with the following structure:
* [
* 'companies' => [
* $companyCrmId => $companyId,
* ...
* ],
* 'contacts' => [
* $contactCrmId => $contactId,
* ...
* ],
* 'account_id' => $accountId,
* ]
*/
private function prepareAssociationsForOpportunity(
string $oppCrmId,
array $companyAssociations,
array $contactAssociations,
array $associationsData
): array {
$associations = [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
$oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];
foreach ($oppCompanyIds as $companyCrmId) {
if (isset($associationsData['company_id_mappings'][$companyCrmId])) {
$associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];
// Set primary account (first company becomes primary account)
if ($associations['account_id'] === null) {
$associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];
}
}
}
$oppContactIds = $contactAssociations[$oppCrmId] ?? [];
foreach ($oppContactIds as $contactCrmId) {
if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {
$associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];
}
}
return $associations;
}
/**
* Update only associations for an opportunity
*/
private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void
{
// Update contact associations
$this->importOpportunityContacts($opportunity, $associations['contacts']);
// Update company (account) associations
$this->updateOpportunityAccount($opportunity, $associations['account_id']);
}
/**
* Remove all contact associations from an opportunity
*/
private function removeAllOpportunityContacts(Opportunity $opportunity): void
{
$currentCount = (int) $opportunity->contacts()->count();
if ($currentCount > 0) {
$opportunity->contacts()->detach();
$this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_count' => $currentCount,
]);
}
}
private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void
{
if ($accountId === null) {
// No account ID provided - keep current account
return;
}
$currentAccountId = $opportunity->getAccountId();
// Only update if account has changed
if ($currentAccountId !== $accountId) {
$opportunity->account_id = $accountId;
$opportunity->save();
$this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [
'opportunity_id' => $opportunity->getId(),
'old_account_id' => $currentAccountId,
'new_account_id' => $accountId,
]);
}
}
/**
* Find existing opportunities by external IDs (OPTIMIZED VERSION)
* Uses batch query for better performance
*/
private function findExistingOpportunities(array $crmIds): Collection
{
return $this->crmEntityRepository
->findOpportunitiesByExternalIds($this->config, $crmIds);
}
private function processOpportunityBatch(array $opportunities): int
{
$syncedOpportunities = $this->importOpportunityBatch($opportunities);
return count($syncedOpportunities['success'] ?? []);
}
/**
* Convert single deal associations from HubSpot format to internal format
* Handles both HubSpot SDK objects and array formats
*
* @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed
*
* @return array Processed associations with DB IDs
*/
private function convertDealAssociations(array $opportunityAssociations): array
{
$associations = $this->initializeAssociationsStructure();
if (empty($opportunityAssociations)) {
return $associations;
}
$associationIds = $this->extractAssociationIds($opportunityAssociations);
$this->processCompanyAssociations($associationIds, $associations);
$this->processContactAssociations($associationIds, $associations);
return $associations;
}
private function initializeAssociationsStructure(): array
{
return [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
}
private function extractAssociationIds(array $opportunityAssociations): array
{
$associationIds = [];
foreach ($opportunityAssociations as $type => $associationData) {
if (! empty($associationData)) {
$associationIds[$type] = $this->convertSingleDealAssociations($associationData);
}
}
return $associationIds;
}
private function processCompanyAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['companies'])) {
return;
}
$companyId = $associationIds['companies'][0];
$account = $this->findOrSyncAccount($companyId);
if ($account instanceof Account) {
$associations['companies'][$companyId] = $account->getId();
$associations['account_id'] = $account->getId();
}
}
private function processContactAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['contacts'])) {
return;
}
foreach ($associationIds['contacts'] as $contactId) {
$contact = $this->findOrSyncContact($contactId);
if ($contact instanceof Contact) {
$associations['contacts'][$contactId] = $contact->getId();
}
}
}
private function findOrSyncAccount(string $companyId): ?Account
{
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);
if (! $account instanceof Account) {
$account = $this->syncAccount($companyId);
}
return $account;
}
private function findOrSyncContact(string $contactId): ?Contact
{
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);
if (! $contact instanceof Contact) {
$contact = $this->syncContact($contactId);
}
return $contact;
}
private function convertSingleDealAssociations($opportunityAssociations = null): array
{
$associationData = [];
if ($opportunityAssociations === null) {
return $associationData;
}
// Handle array input (from extractAssociationIds)
if (is_array($opportunityAssociations)) {
return $opportunityAssociations;
}
// Handle CollectionResponseAssociatedId object
if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {
foreach ($opportunityAssociations->getResults() as $association) {
$associationData[] = $association->getId();
}
}
return $associationData;
}
private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity
{
if (empty($crmData['properties'])) {
return null;
}
$crmId = (string) $crmData['id'];
$properties = $crmData['properties'];
$associations = $crmData['associations'] ?? [];
$opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(
$this->config,
$crmId
);
if ($opportunityExists) {
return $this->updateOpportunity($crmId, $properties, $associations);
} else {
return $this->createOpportunity($crmId, $properties, $associations);
}
}
/**
* Create new opportunity
*/
private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity
{
$accountId = $this->resolveAccountId($associations);
if (! $accountId) {
return null;
}
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
if (! $businessProcess) {
return null;
}
$stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);
if (! $stage) {
return null;
}
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->importOpportunityContacts($opportunity, $associations['contacts']);
if ($opportunity->wasRecentlyCreated) {
MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());
}
return $opportunity;
}
/**
* Update existing opportunity
*/
private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity
{
$accountId = $this->resolveAccountId($associations);
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
$stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->updateOpportunityAssociations($opportunity, $associations);
return $opportunity;
}
private function resolveAccountId(array $associations): ?int
{
if (! empty($associations['accountId'])) {
return $associations['accountId'];
}
if (empty($associations)) {
return null;
}
// we can't resolve multiple account ids (currently SDK returns one company)
foreach ($associations['companies'] as $accountId) {
return $accountId;
}
return null;
}
private function buildOpportunityData(
array $properties,
?int $accountId,
?BusinessProcess $businessProcess,
?Stage $stage
): array {
$ownerId = null;
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$name = 'Unknown';
if (isset($properties['dealname'])) {
$name = mb_strimwidth($properties['dealname'], 0, 128);
}
$amount = $this->resolveAmount($properties);
$currency = $properties['deal_currency_code'] ?? null;
$closeDate = null;
if (! empty($properties['closedate'])) {
$closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');
}
$remotelyCreatedAt = null;
if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {
$date = $this->parseCleanDatetime($properties['createdate']);
$remotelyCreatedAt = $date?->format('Y-m-d H:i:s');
}
$closedStages = $this->getClosedDealStages();
$isWon = in_array($properties['dealstage'], $closedStages['won']);
$isLost = in_array($properties['dealstage'], $closedStages['lost']);
$data = [
'team_id' => $this->team->getId(),
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => $name,
'value' => ! empty($amount) ? $amount : null,
'currency_code' => CurrencyFormatter::formatCode($currency),
'close_date' => $closeDate,
'is_closed' => $isWon || $isLost,
'is_won' => $isWon,
'remotely_created_at' => $remotelyCreatedAt,
'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),
'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),
];
if ($accountId) {
$data['account_id'] = $accountId;
}
if ($stage) {
$data['stage_id'] = $stage->id;
}
if ($businessProcess) {
$recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);
if ($recordType) {
$data['record_type_id'] = $recordType->id;
}
}
return $data;
}
private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess
{
if ($pipelineId === null) {
return null;
}
if (isset($this->cachedBusinessProcesses[$pipelineId])) {
return $this->cachedBusinessProcesses[$pipelineId];
}
$businessProcess = $this->getBusinessProcess($pipelineId);
if (! $businessProcess instanceof BusinessProcess) {
$this->importStages();
$businessProcess = $this->getBusinessProcess($pipelineId);
}
if (! $businessProcess instanceof BusinessProcess) {
$this->logger->info(
'[HubSpot] Deal is not attached to a pipeline',
[
'pipeline' => $pipelineId]
);
}
$this->cachedBusinessProcesses[$pipelineId] = $businessProcess;
return $businessProcess;
}
private function getBusinessProcess(string $pipelineId): ?BusinessProcess
{
return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);
}
private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage
{
if (empty($stageId)) {
return null;
}
$cacheKey = $businessProcess->getId() . ':' . $stageId;
if (isset($this->cachedStages[$cacheKey])) {
return $this->cachedStages[$cacheKey];
}
$stage = $this->crmEntityRepository->getPipelineStageByConditions(
$businessProcess,
[
'crm_provider_id' => $stageId,
'type' => Stage::TYPE_OPPORTUNITY,
]
);
if ($stage === null) {
$this->importStages(null, $stageId);
}
if ($stage === null) {
$this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);
}
$this->cachedStages[$cacheKey] = $stage;
return $stage;
}
private function resolveAmount(array $properties): ?string
{
$amount = null;
if (! empty($properties['amount'])) {
$amount = str_replace(',', '', $properties['amount']);
}
if ($this->config->hasDefaultCurrencyFieldSet()) {
$valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();
$amount = $properties[$valueFieldName] ?? $amount;
}
return $amount;
}
private function parseCleanDatetime(string $datetime): ?Carbon
{
// Treat pre-1980 values as invalid
$minValidDate = Carbon::parse('1980-01-01 00:00:00');
try {
$date = Carbon::parse($datetime);
if ($minValidDate->gt($date)) {
return null;
}
return $date;
} catch (Exception) {
return null; // On parse error, treat as null
}
}
private function resolveDealProbability(?string $stageProbability): int
{
if ($stageProbability === null) {
return 0;
}
$probability = (float) $stageProbability;
return $probability > 1 ? 0 : (int) ($probability * 100);
}
private function resolveForecastCategory(?string $forecastCategory): string
{
if (! $forecastCategory) {
return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;
}
$forecastCategory = str_replace('_', ' ', $forecastCategory);
return ucwords(strtolower($forecastCategory));
}
private function importExternalFieldData(array $properties, int $opportunityId): void
{
$crmFields = $this->getOpportunitySyncableFields();
$this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);
}
private function importOpportunityContacts(Opportunity $opportunity, array $associations): void
{
// Handle empty or missing contact associations
if (empty($associations)) {
// Remove all existing contact associations if none provided
$this->removeAllOpportunityContacts($opportunity);
return;
}
// Use differential sync approach for better performance and accuracy
$this->syncOpportunityContactsDifferential($opportunity, $associations);
}
/**
* Sync opportunity contacts using differential approach
* This compares current vs new associations and only makes necessary changes
*/
private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void
{
$currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);
$contactAssociationIds = array_keys($contactAssociations);
$contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);
$contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);
if (empty($contactsToAdd) && empty($contactsToRemove)) {
return;
}
$this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);
$this->removeContactAssociations($opportunity, $contactsToRemove);
$this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);
}
private function getCurrentContactCrmIds(Opportunity $opportunity): array
{
return $opportunity->contacts()
->pluck('contacts.crm_provider_id')
->toArray();
}
private function logContactAssociationChanges(
Opportunity $opportunity,
array $currentContactCrmIds,
array $contactAssociations,
array $contactsToAdd,
array $contactsToRemove
): void {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [
'opportunity_id' => $opportunity->getId(),
'current_contacts' => $currentContactCrmIds,
'new_contacts' => $contactAssociations,
'contacts_to_add' => $contactsToAdd,
'contacts_to_remove' => $contactsToRemove,
]);
}
private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void
{
if (empty($contactsToRemove)) {
return;
}
$contactsToDetach = $opportunity->contacts()
->whereIn('contacts.crm_provider_id', $contactsToRemove)
->pluck('contacts.id')
->toArray();
if (! empty($contactsToDetach)) {
$opportunity->contacts()->detach($contactsToDetach);
$this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_contact_crm_ids' => $contactsToRemove,
'removed_contact_count' => count($contactsToDetach),
]);
}
}
private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void
{
if (empty($contactsToAdd)) {
return;
}
$contactsAdded = [];
foreach ($contactsToAdd as $crmId) {
$id = $contactAssociations[$crmId];
if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {
$contactsAdded[] = $crmId;
}
}
$this->logAddedContacts($opportunity, $contactsAdded);
}
private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool
{
try {
$contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);
if (! $contact) {
return false;
}
return $this->performContactAttachment($opportunity, $contact, $crmId);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [
'opportunity_id' => $opportunity->getId(),
'contact_crm_id' => $crmId,
'error' => $e->getMessage(),
]);
return false;
}
}
private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool
{
try {
$opportunity->contacts()->attach($contact->getId(), [
'crm_provider_id' => $crmId,
]);
return true;
} catch (\Illuminate\Database\QueryException $e) {
if (str_contains($e->getMessage(), 'Duplicate entry')) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [
'contact_id' => $contact->getId(),
'contact_crm_id' => $crmId,
'opportunity_id' => $opportunity->getId(),
]);
return false;
}
throw $e;
}
}
private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void
{
if (! empty($contactsAdded)) {
$this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [
'opportunity_id' => $opportunity->getId(),
'contacts_to_add_count' => count($contactsAdded),
'added_contact_crm_ids' => $contactsAdded,
'added_contacts_count' => count($contactsAdded),
]);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
1
Previous Highlighted Error
Next Highlighted Error
<template>
<WelcomeLayout
title="Account disconnected"
textPosition="center"
:icon="faUnlink"
:class="$style.layout"
>
<div :class="$style.container" v-if="providersLoaded">
<p>
<strong>
It looks like your {{ localProvider.displayName }} account has become
disconnected
</strong>
</p>
<p :class="$style.small">Please re-connect to continue</p>
<p v-if="isInIframe">
We'll open the {{ localProvider.displayName }} authentication in a new
tab. Please return here and refresh the page once complete
</p>
<GoogleLikeButton
v-if="localProvider.viaIntegrationApp && crmTokenLoaded"
as="a"
:key="localProvider.name"
:brand-logo="localProvider.name"
:class="$style.connectButton"
@click="integrationAppOnClick"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
<GoogleLikeButton
v-if="!localProvider.viaIntegrationApp"
as="a"
:key="localProvider.name"
:href="`/auth/redirect/${localProvider.name}`"
:target="target"
:brand-logo="localProvider.name"
:class="$style.connectButton"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
</div>
<BuildInfo />
<KioskBanner />
</WelcomeLayout>
</template>
<script>
import window from "window";
import axios from "axios";
import { faUnlink } from "@fortawesome/pro-regular-svg-icons";
import isInIframe from "@/utils/isInIframe";
import BuildInfo from "@/components/layout/BuildInfo/BuildInfo.vue";
import KioskBanner from "@/components/shared/KioskBanner/KioskBanner.vue";
import WelcomeLayout from "@/components/layout/WelcomeLayout/WelcomeLayout.vue";
import GoogleLikeButton from "@/components/shared/Buttons/GoogleLikeButton.vue";
import { showSnackbarError, normalizeError } from "@/utils/index";
import { IntegrationAppClient } from "@integration-app/sdk";
export default {
name: "ConnectPage",
components: {
BuildInfo,
KioskBanner,
WelcomeLayout,
GoogleLikeButton,
},
data() {
return {
...window.connectData,
crmToken: null,
faUnlink,
isInIframe,
providers: [],
providersLoaded: false,
crmTokenLoaded: false,
};
},
computed: {
localProvider() {
return this.providers.find((e) => e.name === this.provider);
},
target() {
return this.isInIframe ? "_blank" : null;
},
},
created() {
this.getProviders();
},
mounted() {
this.showErrors();
},
watch: {
providersLoaded() {
if (this.providersLoaded) {
this.prepareIntegrationAppConnection();
}
},
},
methods: {
showErrors() {
if (!this.error) return;
showSnackbarError(this.error, undefined, undefined, false);
},
unwrapEntityResponse({ data }) {
return data.map(({ icon, name, displayName, viaIntegrationApp }) => {
return { icon, name, displayName, viaIntegrationApp };
});
},
async getProviders() {
try {
const response = await axios.get("/api/v1/connect-providers");
this.providers = this.unwrapEntityResponse(response);
this.providersLoaded = true;
} catch {
showSnackbarError(
"An error occurred, while loading form data (connect providers).",
);
}
},
async prepareIntegrationAppConnection() {
if (this.localProvider.viaIntegrationApp) {
try {
const response = await axios.get("/api/v1/integration-app-token");
this.crmToken = response.data.token;
this.crmTokenLoaded = true;
} catch (error) {
console.log(error);
showSnackbarError(
`An error occurred while preparing the page.
Try refreshing, if the error persists get in touch with the Jiminny team.`,
);
}
}
},
async integrationAppOnClick() {
console.log('[IntegrationApp] integrationAppOnClick called');
const integrationApp = new IntegrationAppClient({
token: this.crmToken,
});
const connection = await integrationApp
.integration(this.localProvider.name)
.openNewConnection({
showPoweredBy: false,
allowMultipleConnections: false,
}).catch((err) => {
console.log('[IntegrationApp] openNewConnection rejected:', err);
return null;
});
console.log('[IntegrationApp] openNewConnection resolved:', JSON.stringify(connection));
// [IntegrationApp] openNewConnection resolved: {
// "id":"69e0b41a67d0068c2ca0b48e",
// "name":"Zoho CRM",
// "userId":"1ece66c8-feb1-4df1-b321-21607daf4623",
// "tenantId":"69e0b3faef3e7b6248189289",
// "isTest":false,
// "connected":true,
// "state":"READY",
// "errors":[],
// "integrationId":"66fe6c913202f3a165e3c14d",
// "externalAppId":"6671653e7e2d642e4e41b0fa",
// "authOptionKey":"",
// "createdAt":"2026-04-16T10:04:10.420Z",
// "updatedAt":"2026-04-16T10:04:10.575Z",
// "retryAttempts":0,
// "isDeactivated":false
// }
if (connection && connection.disconnected !== true && connection.connected !== false) {
console.log('[IntegrationApp] connection condition matched');
try {
const saveRequest = await axios.post(
"/api/v1/integration-app-connect",
);
if (saveRequest.data && saveRequest.data.success === true) {
/** If all is good refresh the page here */
window.location = "/dashboard";
return;
}
throw new Error(saveRequest.data.message);
} catch (error) {
console.log(error);
showSnackbarError(normalizeError(error));
}
}
},
},
};
</script>
<style module lang="less" src="./connect.less"></style>
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20692-fix-integration-app-token-auth-response-change, menu","depth":5,"help_text":"Git Branch: JY-20692-fix-integration-app-token-auth-response-change","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsCommandTest","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsCommandTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsCommandTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show Replace Field","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Search History","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"cachedStages","depth":4,"value":"cachedStages","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Match Case","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Words","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Regex","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Replace History","depth":3,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Replace","depth":4,"role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Preserve case","depth":3,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2/4","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Occurrence","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Occurrence","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Filter Search Results","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open in Window, Multiple Cursors","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Click to highlight","depth":4,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"33","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"19","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\CollectionResponseAssociatedId;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Models\\Account;\nuse Exception;\nuse Jiminny\\Component\\DealInsights\\Forecast\\Forecast;\nuse Jiminny\\Jobs\\Crm\\MatchActivitiesToNewOpportunity;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Crm\\BusinessProcess;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Models\\Opportunity;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DealFieldsService;\nuse Jiminny\\Services\\Crm\\Hubspot\\OpportunitySyncStrategy\\HubspotSingleSyncStrategy;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Services\\Crm\\OpportunitySyncStrategyResolver;\nuse Jiminny\\Utils\\CurrencyFormatter;\n\n/**\n * Optimized sync methods for better performance\n * These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains\n */\ntrait OpportunitySyncTrait\n{\n private const int BATCH_SIZE = 100;\n private const int BATCH_PROCESS_SIZE = 800;\n\n protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;\n protected CrmEntityRepository $crmEntityRepository;\n protected DealFieldsService $dealFieldsService;\n\n private ?array $cachedClosedDealStages = null;\n private array $cachedBusinessProcesses = [];\n private array $cachedStages = [];\n\n public function syncOpportunities(array $parameters, ?string $strategy = null): int\n {\n $strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);\n $parameters['config'] = $this->config;\n $syncCount = 0;\n $reportedTotal = 0;\n $lastSyncedId = [];\n\n try {\n foreach ($strategies as $strategyName => $syncStrategy) {\n $this->logger->info(\n '[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .\n $strategyName\n );\n\n $total = 0;\n $lastId = null;\n $buffer = [];\n\n // HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies\n foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {\n $buffer[] = $hsOpportunity;\n\n // process every 800 rows (fits < 1 000 association limit)\n if (\\count($buffer) >= self::BATCH_PROCESS_SIZE) {\n $syncCount += $this->processOpportunityBatch($buffer);\n $buffer = [];\n }\n }\n\n // leftovers\n if ($buffer) {\n $syncCount += $this->processOpportunityBatch($buffer);\n }\n\n $reportedTotal += $total;\n $lastSyncedId = $lastId;\n }\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException | CrmException $e) {\n $this->handleSyncException($e, $parameters);\n }\n\n $this->logger->info(\n '[HubSpot] Synced opportunities',\n [\n 'team' => $this->team->getId(),\n 'sync_count' => $syncCount,\n 'total' => $reportedTotal,\n 'last_synced_id' => $lastSyncedId,\n ]\n );\n\n return $reportedTotal;\n }\n\n private function handleSyncException(\\Throwable $e, array $parameters): void\n {\n if (($parameters['since'] ?? null) instanceof Carbon) {\n $parameters['since'] = $parameters['since']->toDateTimeString();\n }\n $parameters['config'] = $this->config->getId();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [\n 'teamId' => $this->team->getUuid(),\n 'parameters' => $parameters,\n 'reason' => $e->getMessage(),\n ]);\n }\n\n /**\n * @inheritdoc\n */\n public function syncOpportunity(string $crmId): ?Opportunity\n {\n $strategy = $this->opportunitySyncStrategyResolver->resolve(\n $this->config,\n OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,\n );\n\n $parameters = [\n 'config' => $this->config,\n 'crm_id' => $crmId,\n ];\n\n try {\n if (! $strategy instanceof HubspotSingleSyncStrategy) {\n throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');\n }\n\n $hsOpportunity = $strategy->fetchOpportunity($parameters);\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);\n\n return $this->importOrUpdateOpportunity($hsOpportunity);\n }\n\n /**\n * Process webhook-collected opportunity batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportOpportunityBatch jobs for batch processing.\n *\n * @return int Number of opportunity IDs dispatched to jobs\n */\n public function batchSyncOpportunities(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,\n $configId\n );\n }\n\n /**\n * Import a batch of opportunities by their CRM IDs.\n * Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().\n *\n * @param array<string> $crmIds HubSpot deal CRM IDs\n *\n * @return array{success: array, failed_ids: array, errors?: array<string, string>}\n */\n public function importOpportunityBatchByIds(array $crmIds): array\n {\n $fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);\n\n $allDeals = [];\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n $deals = $this->client->getOpportunitiesByIds($chunk, $fields);\n foreach ($deals as $deal) {\n $allDeals[] = $deal;\n }\n }\n\n // IDs not returned by HubSpot are likely deleted or inaccessible deals.\n // These are not failures — retrying won't bring them back.\n $fetchedIds = array_map('strval', array_column($allDeals, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allDeals),\n ]);\n }\n\n if (empty($allDeals)) {\n return ['success' => [], 'failed_ids' => []];\n }\n\n return $this->importOpportunityBatch($allDeals);\n }\n\n private function getClosedDealStages(): array\n {\n if ($this->cachedClosedDealStages !== null) {\n return $this->cachedClosedDealStages;\n }\n\n $stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);\n $data = [\n 'lost' => [],\n 'won' => [],\n ];\n\n foreach ($stages as $stage) {\n if ($stage->probability == 0.00) {\n $data['lost'][] = $stage->crm_provider_id;\n }\n if ($stage->probability == 100.00) {\n $data['won'][] = $stage->crm_provider_id;\n }\n }\n\n $this->cachedClosedDealStages = $data;\n\n return $data;\n }\n\n /**\n * Import deals into the database with pre-fetched associations.\n *\n * API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT\n * caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()\n * where Laravel retries the whole job with backoff. After all retries exhausted,\n * failed() requeues all IDs to Redis.\n *\n * The per-deal loop catches exceptions individually. A deal can end up in three states:\n * - success: imported/updated successfully\n * - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)\n * These are permanent issues — retrying won't fix them.\n * - skipped (null): missing dependencies (no account, unknown pipeline/stage).\n * This is acceptable — the deal cannot be imported until those exist.\n */\n private function importOpportunityBatch(array $deals): array\n {\n $syncedOpportunities = [\n 'success' => [],\n 'failed_ids' => [],\n ];\n $dealIds = array_column($deals, 'id');\n\n // Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the\n // queue job retries the whole batch and eventually requeues all deal IDs back to Redis.\n try {\n $companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');\n $contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');\n\n $associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);\n\n $existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(\n $this->config,\n array_map('strval', $dealIds)\n );\n $existingCrmIdSet = array_flip($existingCrmIds);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [\n 'teamId' => $this->team->getId(),\n 'dealCount' => count($dealIds),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n foreach ($deals as $deal) {\n try {\n $deal['associations'] = $this->prepareAssociationsForOpportunity(\n $deal['id'],\n $companyAssociations,\n $contactAssociations,\n $associationsData\n );\n\n $syncedOpportunity = $this->importOrUpdateOpportunity(\n $deal,\n isset($existingCrmIdSet[(string) $deal['id']])\n );\n if ($syncedOpportunity) {\n $syncedOpportunities['success'][] = $syncedOpportunity;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $deal['id'],\n 'error' => $e->getMessage(),\n ]);\n $syncedOpportunities['failed_ids'][] = $deal['id'];\n $syncedOpportunities['errors'][$deal['id']] = $e->getMessage();\n }\n }\n\n return $syncedOpportunities;\n }\n\n /**\n * Prepare associated entities for opportunities with optimized batch processing\n * Returns structured data with CRM ID to DB ID mappings for each opportunity\n */\n private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array\n {\n // Step 1: Collect all unique company and contact IDs from associations\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n\n // Step 2: Batch sync missing entities and get CRM ID to DB ID mappings\n $companyIdMappings = [];\n $contactIdMappings = [];\n\n if (! empty($allCompanyIds)) {\n $companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);\n }\n\n if (! empty($allContactIds)) {\n $contactIdMappings = $this->prepareAssociatedContacts($allContactIds);\n }\n\n return [\n 'company_id_mappings' => $companyIdMappings,\n 'contact_id_mappings' => $contactIdMappings,\n ];\n }\n\n /**\n * Flatten association data to get unique IDs\n */\n private function flattenAssociationIds(array $associations): array\n {\n $ids = [];\n foreach ($associations as $dealAssociations) {\n if (is_array($dealAssociations)) {\n foreach ($dealAssociations as $id) {\n $ids[$id] = true;\n }\n }\n }\n\n return array_keys($ids);\n }\n\n /**\n * Batch sync missing accounts\n */\n private function prepareAssociatedAccounts(array $companyIds): array\n {\n // Find which accounts already exist\n $existingAccounts = $this->crmEntityRepository\n ->findAccountsByExternalIds($this->config, $companyIds);\n\n $existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();\n\n $existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {\n return [$account->getCrmProviderId() => $account->getId()];\n })->toArray();\n\n $missingCompanyIds = array_diff($companyIds, $existingCompanyIds);\n\n if (empty($missingCompanyIds)) {\n return $existingAccountsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [\n 'teamId' => $this->team->getUuid(),\n 'total_companies' => count($companyIds),\n 'existing_companies' => count($existingCompanyIds),\n 'missing_companies' => count($missingCompanyIds),\n ]);\n\n // we already have limit on opportunity ids count\n // Initialize variable before try block\n $syncedAccountsData = [];\n\n try {\n $syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [\n 'size' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedAccountsData = [];\n }\n\n return $existingAccountsData + $syncedAccountsData;\n }\n\n /**\n * Prepare associated contacts - find existing and sync missing ones\n * Returns mapping of CRM ID to DB ID\n */\n private function prepareAssociatedContacts(array $contactIds): array\n {\n // Find which contacts already exist\n $existingContacts = $this->crmEntityRepository\n ->findContactsByExternalIds($this->config, $contactIds);\n\n $existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();\n\n // Create mapping for existing contacts\n $existingContactsData = $existingContacts->mapWithKeys(function ($contact) {\n return [$contact->getCrmProviderId() => $contact->getId()];\n })->toArray();\n\n $missingContactIds = array_diff($contactIds, $existingContactIds);\n\n if (empty($missingContactIds)) {\n return $existingContactsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [\n 'teamId' => $this->team->getUuid(),\n 'total_contacts' => count($contactIds),\n 'existing_contacts' => count($existingContactIds),\n 'missing_contacts' => count($missingContactIds),\n ]);\n\n // Sync missing contacts using batch API\n try {\n $syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [\n 'size' => count($missingContactIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedContactsData = [];\n }\n\n return $existingContactsData + $syncedContactsData;\n }\n\n private function batchSyncCrmObjects(string $objectType, array $crmIds): array\n {\n $syncObjects = [];\n $crmObjectIds = array_values($crmIds);\n\n foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {\n try {\n $objects = $objectType === 'companies' ?\n $this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :\n $this->client->getContactsByIds($chunk, $this->getContactFields());\n\n foreach ($objects as $objectId => $objectData) {\n $this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [\n 'requested_count' => count($chunk),\n 'synced_count' => count($objects),\n ]);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [\n 'ids' => $chunk,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncObjects;\n }\n\n private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void\n {\n try {\n $object = $objectType === 'companies' ?\n $this->importAccount($objectData) :\n $this->importContact($objectData);\n\n if ($object) {\n $syncObjects[$object->getCrmProviderId()] = $object->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [\n 'id' => $objectId,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n /**\n * Prepare associations for a single opportunity\n *\n * The return value is an array with the following structure:\n * [\n * 'companies' => [\n * $companyCrmId => $companyId,\n * ...\n * ],\n * 'contacts' => [\n * $contactCrmId => $contactId,\n * ...\n * ],\n * 'account_id' => $accountId,\n * ]\n */\n private function prepareAssociationsForOpportunity(\n string $oppCrmId,\n array $companyAssociations,\n array $contactAssociations,\n array $associationsData\n ): array {\n $associations = [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n\n $oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];\n foreach ($oppCompanyIds as $companyCrmId) {\n if (isset($associationsData['company_id_mappings'][$companyCrmId])) {\n $associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];\n\n // Set primary account (first company becomes primary account)\n if ($associations['account_id'] === null) {\n $associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];\n }\n }\n }\n\n $oppContactIds = $contactAssociations[$oppCrmId] ?? [];\n foreach ($oppContactIds as $contactCrmId) {\n if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {\n $associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];\n }\n }\n\n return $associations;\n }\n\n /**\n * Update only associations for an opportunity\n */\n private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void\n {\n // Update contact associations\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n // Update company (account) associations\n $this->updateOpportunityAccount($opportunity, $associations['account_id']);\n }\n\n /**\n * Remove all contact associations from an opportunity\n */\n private function removeAllOpportunityContacts(Opportunity $opportunity): void\n {\n $currentCount = (int) $opportunity->contacts()->count();\n\n if ($currentCount > 0) {\n $opportunity->contacts()->detach();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_count' => $currentCount,\n ]);\n }\n }\n\n private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void\n {\n if ($accountId === null) {\n // No account ID provided - keep current account\n return;\n }\n\n $currentAccountId = $opportunity->getAccountId();\n\n // Only update if account has changed\n if ($currentAccountId !== $accountId) {\n $opportunity->account_id = $accountId;\n $opportunity->save();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [\n 'opportunity_id' => $opportunity->getId(),\n 'old_account_id' => $currentAccountId,\n 'new_account_id' => $accountId,\n ]);\n }\n }\n\n /**\n * Find existing opportunities by external IDs (OPTIMIZED VERSION)\n * Uses batch query for better performance\n */\n private function findExistingOpportunities(array $crmIds): Collection\n {\n return $this->crmEntityRepository\n ->findOpportunitiesByExternalIds($this->config, $crmIds);\n }\n\n private function processOpportunityBatch(array $opportunities): int\n {\n $syncedOpportunities = $this->importOpportunityBatch($opportunities);\n\n return count($syncedOpportunities['success'] ?? []);\n }\n\n /**\n * Convert single deal associations from HubSpot format to internal format\n * Handles both HubSpot SDK objects and array formats\n *\n * @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed\n *\n * @return array Processed associations with DB IDs\n */\n private function convertDealAssociations(array $opportunityAssociations): array\n {\n $associations = $this->initializeAssociationsStructure();\n\n if (empty($opportunityAssociations)) {\n return $associations;\n }\n\n $associationIds = $this->extractAssociationIds($opportunityAssociations);\n\n $this->processCompanyAssociations($associationIds, $associations);\n $this->processContactAssociations($associationIds, $associations);\n\n return $associations;\n }\n\n private function initializeAssociationsStructure(): array\n {\n return [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n }\n\n private function extractAssociationIds(array $opportunityAssociations): array\n {\n $associationIds = [];\n\n foreach ($opportunityAssociations as $type => $associationData) {\n if (! empty($associationData)) {\n $associationIds[$type] = $this->convertSingleDealAssociations($associationData);\n }\n }\n\n return $associationIds;\n }\n\n private function processCompanyAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['companies'])) {\n return;\n }\n\n $companyId = $associationIds['companies'][0];\n $account = $this->findOrSyncAccount($companyId);\n\n if ($account instanceof Account) {\n $associations['companies'][$companyId] = $account->getId();\n $associations['account_id'] = $account->getId();\n }\n }\n\n private function processContactAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['contacts'])) {\n return;\n }\n\n foreach ($associationIds['contacts'] as $contactId) {\n $contact = $this->findOrSyncContact($contactId);\n\n if ($contact instanceof Contact) {\n $associations['contacts'][$contactId] = $contact->getId();\n }\n }\n }\n\n private function findOrSyncAccount(string $companyId): ?Account\n {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);\n\n if (! $account instanceof Account) {\n $account = $this->syncAccount($companyId);\n }\n\n return $account;\n }\n\n private function findOrSyncContact(string $contactId): ?Contact\n {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);\n\n if (! $contact instanceof Contact) {\n $contact = $this->syncContact($contactId);\n }\n\n return $contact;\n }\n\n private function convertSingleDealAssociations($opportunityAssociations = null): array\n {\n $associationData = [];\n\n if ($opportunityAssociations === null) {\n return $associationData;\n }\n\n // Handle array input (from extractAssociationIds)\n if (is_array($opportunityAssociations)) {\n return $opportunityAssociations;\n }\n\n // Handle CollectionResponseAssociatedId object\n if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {\n foreach ($opportunityAssociations->getResults() as $association) {\n $associationData[] = $association->getId();\n }\n }\n\n return $associationData;\n }\n\n private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity\n {\n if (empty($crmData['properties'])) {\n return null;\n }\n\n $crmId = (string) $crmData['id'];\n $properties = $crmData['properties'];\n $associations = $crmData['associations'] ?? [];\n\n $opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(\n $this->config,\n $crmId\n );\n\n if ($opportunityExists) {\n return $this->updateOpportunity($crmId, $properties, $associations);\n } else {\n return $this->createOpportunity($crmId, $properties, $associations);\n }\n }\n\n /**\n * Create new opportunity\n */\n private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n if (! $accountId) {\n return null;\n }\n\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n if (! $businessProcess) {\n return null;\n }\n\n $stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);\n if (! $stage) {\n return null;\n }\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n if ($opportunity->wasRecentlyCreated) {\n MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());\n }\n\n return $opportunity;\n }\n\n /**\n * Update existing opportunity\n */\n private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n $stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->updateOpportunityAssociations($opportunity, $associations);\n\n return $opportunity;\n }\n\n private function resolveAccountId(array $associations): ?int\n {\n if (! empty($associations['accountId'])) {\n return $associations['accountId'];\n }\n\n if (empty($associations)) {\n return null;\n }\n\n // we can't resolve multiple account ids (currently SDK returns one company)\n foreach ($associations['companies'] as $accountId) {\n return $accountId;\n }\n\n return null;\n }\n\n private function buildOpportunityData(\n array $properties,\n ?int $accountId,\n ?BusinessProcess $businessProcess,\n ?Stage $stage\n ): array {\n $ownerId = null;\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $name = 'Unknown';\n if (isset($properties['dealname'])) {\n $name = mb_strimwidth($properties['dealname'], 0, 128);\n }\n\n $amount = $this->resolveAmount($properties);\n $currency = $properties['deal_currency_code'] ?? null;\n\n $closeDate = null;\n if (! empty($properties['closedate'])) {\n $closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');\n }\n\n $remotelyCreatedAt = null;\n if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {\n $date = $this->parseCleanDatetime($properties['createdate']);\n $remotelyCreatedAt = $date?->format('Y-m-d H:i:s');\n }\n\n $closedStages = $this->getClosedDealStages();\n $isWon = in_array($properties['dealstage'], $closedStages['won']);\n $isLost = in_array($properties['dealstage'], $closedStages['lost']);\n\n $data = [\n 'team_id' => $this->team->getId(),\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'value' => ! empty($amount) ? $amount : null,\n 'currency_code' => CurrencyFormatter::formatCode($currency),\n 'close_date' => $closeDate,\n 'is_closed' => $isWon || $isLost,\n 'is_won' => $isWon,\n 'remotely_created_at' => $remotelyCreatedAt,\n 'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),\n 'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),\n ];\n\n if ($accountId) {\n $data['account_id'] = $accountId;\n }\n\n if ($stage) {\n $data['stage_id'] = $stage->id;\n }\n\n if ($businessProcess) {\n $recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);\n if ($recordType) {\n $data['record_type_id'] = $recordType->id;\n }\n }\n\n return $data;\n }\n\n private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess\n {\n if ($pipelineId === null) {\n return null;\n }\n\n if (isset($this->cachedBusinessProcesses[$pipelineId])) {\n return $this->cachedBusinessProcesses[$pipelineId];\n }\n\n $businessProcess = $this->getBusinessProcess($pipelineId);\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->importStages();\n $businessProcess = $this->getBusinessProcess($pipelineId);\n }\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->logger->info(\n '[HubSpot] Deal is not attached to a pipeline',\n [\n 'pipeline' => $pipelineId]\n );\n }\n\n $this->cachedBusinessProcesses[$pipelineId] = $businessProcess;\n\n return $businessProcess;\n }\n\n private function getBusinessProcess(string $pipelineId): ?BusinessProcess\n {\n return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);\n }\n\n private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage\n {\n if (empty($stageId)) {\n return null;\n }\n\n $cacheKey = $businessProcess->getId() . ':' . $stageId;\n if (isset($this->cachedStages[$cacheKey])) {\n return $this->cachedStages[$cacheKey];\n }\n\n $stage = $this->crmEntityRepository->getPipelineStageByConditions(\n $businessProcess,\n [\n 'crm_provider_id' => $stageId,\n 'type' => Stage::TYPE_OPPORTUNITY,\n ]\n );\n\n if ($stage === null) {\n $this->importStages(null, $stageId);\n }\n\n if ($stage === null) {\n $this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);\n }\n\n $this->cachedStages[$cacheKey] = $stage;\n\n return $stage;\n }\n\n private function resolveAmount(array $properties): ?string\n {\n $amount = null;\n if (! empty($properties['amount'])) {\n $amount = str_replace(',', '', $properties['amount']);\n }\n\n if ($this->config->hasDefaultCurrencyFieldSet()) {\n $valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();\n $amount = $properties[$valueFieldName] ?? $amount;\n }\n\n return $amount;\n }\n\n private function parseCleanDatetime(string $datetime): ?Carbon\n {\n // Treat pre-1980 values as invalid\n $minValidDate = Carbon::parse('1980-01-01 00:00:00');\n\n try {\n $date = Carbon::parse($datetime);\n\n if ($minValidDate->gt($date)) {\n return null;\n }\n\n return $date;\n } catch (Exception) {\n return null; // On parse error, treat as null\n }\n }\n\n private function resolveDealProbability(?string $stageProbability): int\n {\n if ($stageProbability === null) {\n return 0;\n }\n\n $probability = (float) $stageProbability;\n\n return $probability > 1 ? 0 : (int) ($probability * 100);\n }\n\n private function resolveForecastCategory(?string $forecastCategory): string\n {\n if (! $forecastCategory) {\n return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;\n }\n\n $forecastCategory = str_replace('_', ' ', $forecastCategory);\n\n return ucwords(strtolower($forecastCategory));\n }\n\n private function importExternalFieldData(array $properties, int $opportunityId): void\n {\n $crmFields = $this->getOpportunitySyncableFields();\n $this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);\n }\n\n private function importOpportunityContacts(Opportunity $opportunity, array $associations): void\n {\n // Handle empty or missing contact associations\n if (empty($associations)) {\n // Remove all existing contact associations if none provided\n $this->removeAllOpportunityContacts($opportunity);\n\n return;\n }\n\n // Use differential sync approach for better performance and accuracy\n $this->syncOpportunityContactsDifferential($opportunity, $associations);\n }\n\n /**\n * Sync opportunity contacts using differential approach\n * This compares current vs new associations and only makes necessary changes\n */\n private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void\n {\n $currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);\n $contactAssociationIds = array_keys($contactAssociations);\n\n $contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);\n $contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);\n\n if (empty($contactsToAdd) && empty($contactsToRemove)) {\n return;\n }\n\n $this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);\n\n $this->removeContactAssociations($opportunity, $contactsToRemove);\n $this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);\n }\n\n private function getCurrentContactCrmIds(Opportunity $opportunity): array\n {\n return $opportunity->contacts()\n ->pluck('contacts.crm_provider_id')\n ->toArray();\n }\n\n private function logContactAssociationChanges(\n Opportunity $opportunity,\n array $currentContactCrmIds,\n array $contactAssociations,\n array $contactsToAdd,\n array $contactsToRemove\n ): void {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [\n 'opportunity_id' => $opportunity->getId(),\n 'current_contacts' => $currentContactCrmIds,\n 'new_contacts' => $contactAssociations,\n 'contacts_to_add' => $contactsToAdd,\n 'contacts_to_remove' => $contactsToRemove,\n ]);\n }\n\n private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void\n {\n if (empty($contactsToRemove)) {\n return;\n }\n\n $contactsToDetach = $opportunity->contacts()\n ->whereIn('contacts.crm_provider_id', $contactsToRemove)\n ->pluck('contacts.id')\n ->toArray();\n\n if (! empty($contactsToDetach)) {\n $opportunity->contacts()->detach($contactsToDetach);\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_contact_crm_ids' => $contactsToRemove,\n 'removed_contact_count' => count($contactsToDetach),\n ]);\n }\n }\n\n private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void\n {\n if (empty($contactsToAdd)) {\n return;\n }\n\n $contactsAdded = [];\n foreach ($contactsToAdd as $crmId) {\n $id = $contactAssociations[$crmId];\n\n if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {\n $contactsAdded[] = $crmId;\n }\n }\n\n $this->logAddedContacts($opportunity, $contactsAdded);\n }\n\n private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);\n\n if (! $contact) {\n return false;\n }\n\n return $this->performContactAttachment($opportunity, $contact, $crmId);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [\n 'opportunity_id' => $opportunity->getId(),\n 'contact_crm_id' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool\n {\n try {\n $opportunity->contacts()->attach($contact->getId(), [\n 'crm_provider_id' => $crmId,\n ]);\n\n return true;\n } catch (\\Illuminate\\Database\\QueryException $e) {\n if (str_contains($e->getMessage(), 'Duplicate entry')) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [\n 'contact_id' => $contact->getId(),\n 'contact_crm_id' => $crmId,\n 'opportunity_id' => $opportunity->getId(),\n ]);\n\n return false;\n }\n\n throw $e;\n }\n }\n\n private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void\n {\n if (! empty($contactsAdded)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'contacts_to_add_count' => count($contactsAdded),\n 'added_contact_crm_ids' => $contactsAdded,\n 'added_contacts_count' => count($contactsAdded),\n ]);\n }\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\CollectionResponseAssociatedId;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Models\\Account;\nuse Exception;\nuse Jiminny\\Component\\DealInsights\\Forecast\\Forecast;\nuse Jiminny\\Jobs\\Crm\\MatchActivitiesToNewOpportunity;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Crm\\BusinessProcess;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Models\\Opportunity;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DealFieldsService;\nuse Jiminny\\Services\\Crm\\Hubspot\\OpportunitySyncStrategy\\HubspotSingleSyncStrategy;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Services\\Crm\\OpportunitySyncStrategyResolver;\nuse Jiminny\\Utils\\CurrencyFormatter;\n\n/**\n * Optimized sync methods for better performance\n * These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains\n */\ntrait OpportunitySyncTrait\n{\n private const int BATCH_SIZE = 100;\n private const int BATCH_PROCESS_SIZE = 800;\n\n protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;\n protected CrmEntityRepository $crmEntityRepository;\n protected DealFieldsService $dealFieldsService;\n\n private ?array $cachedClosedDealStages = null;\n private array $cachedBusinessProcesses = [];\n private array $cachedStages = [];\n\n public function syncOpportunities(array $parameters, ?string $strategy = null): int\n {\n $strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);\n $parameters['config'] = $this->config;\n $syncCount = 0;\n $reportedTotal = 0;\n $lastSyncedId = [];\n\n try {\n foreach ($strategies as $strategyName => $syncStrategy) {\n $this->logger->info(\n '[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .\n $strategyName\n );\n\n $total = 0;\n $lastId = null;\n $buffer = [];\n\n // HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies\n foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {\n $buffer[] = $hsOpportunity;\n\n // process every 800 rows (fits < 1 000 association limit)\n if (\\count($buffer) >= self::BATCH_PROCESS_SIZE) {\n $syncCount += $this->processOpportunityBatch($buffer);\n $buffer = [];\n }\n }\n\n // leftovers\n if ($buffer) {\n $syncCount += $this->processOpportunityBatch($buffer);\n }\n\n $reportedTotal += $total;\n $lastSyncedId = $lastId;\n }\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException | CrmException $e) {\n $this->handleSyncException($e, $parameters);\n }\n\n $this->logger->info(\n '[HubSpot] Synced opportunities',\n [\n 'team' => $this->team->getId(),\n 'sync_count' => $syncCount,\n 'total' => $reportedTotal,\n 'last_synced_id' => $lastSyncedId,\n ]\n );\n\n return $reportedTotal;\n }\n\n private function handleSyncException(\\Throwable $e, array $parameters): void\n {\n if (($parameters['since'] ?? null) instanceof Carbon) {\n $parameters['since'] = $parameters['since']->toDateTimeString();\n }\n $parameters['config'] = $this->config->getId();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [\n 'teamId' => $this->team->getUuid(),\n 'parameters' => $parameters,\n 'reason' => $e->getMessage(),\n ]);\n }\n\n /**\n * @inheritdoc\n */\n public function syncOpportunity(string $crmId): ?Opportunity\n {\n $strategy = $this->opportunitySyncStrategyResolver->resolve(\n $this->config,\n OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,\n );\n\n $parameters = [\n 'config' => $this->config,\n 'crm_id' => $crmId,\n ];\n\n try {\n if (! $strategy instanceof HubspotSingleSyncStrategy) {\n throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');\n }\n\n $hsOpportunity = $strategy->fetchOpportunity($parameters);\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);\n\n return $this->importOrUpdateOpportunity($hsOpportunity);\n }\n\n /**\n * Process webhook-collected opportunity batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportOpportunityBatch jobs for batch processing.\n *\n * @return int Number of opportunity IDs dispatched to jobs\n */\n public function batchSyncOpportunities(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,\n $configId\n );\n }\n\n /**\n * Import a batch of opportunities by their CRM IDs.\n * Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().\n *\n * @param array<string> $crmIds HubSpot deal CRM IDs\n *\n * @return array{success: array, failed_ids: array, errors?: array<string, string>}\n */\n public function importOpportunityBatchByIds(array $crmIds): array\n {\n $fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);\n\n $allDeals = [];\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n $deals = $this->client->getOpportunitiesByIds($chunk, $fields);\n foreach ($deals as $deal) {\n $allDeals[] = $deal;\n }\n }\n\n // IDs not returned by HubSpot are likely deleted or inaccessible deals.\n // These are not failures — retrying won't bring them back.\n $fetchedIds = array_map('strval', array_column($allDeals, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allDeals),\n ]);\n }\n\n if (empty($allDeals)) {\n return ['success' => [], 'failed_ids' => []];\n }\n\n return $this->importOpportunityBatch($allDeals);\n }\n\n private function getClosedDealStages(): array\n {\n if ($this->cachedClosedDealStages !== null) {\n return $this->cachedClosedDealStages;\n }\n\n $stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);\n $data = [\n 'lost' => [],\n 'won' => [],\n ];\n\n foreach ($stages as $stage) {\n if ($stage->probability == 0.00) {\n $data['lost'][] = $stage->crm_provider_id;\n }\n if ($stage->probability == 100.00) {\n $data['won'][] = $stage->crm_provider_id;\n }\n }\n\n $this->cachedClosedDealStages = $data;\n\n return $data;\n }\n\n /**\n * Import deals into the database with pre-fetched associations.\n *\n * API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT\n * caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()\n * where Laravel retries the whole job with backoff. After all retries exhausted,\n * failed() requeues all IDs to Redis.\n *\n * The per-deal loop catches exceptions individually. A deal can end up in three states:\n * - success: imported/updated successfully\n * - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)\n * These are permanent issues — retrying won't fix them.\n * - skipped (null): missing dependencies (no account, unknown pipeline/stage).\n * This is acceptable — the deal cannot be imported until those exist.\n */\n private function importOpportunityBatch(array $deals): array\n {\n $syncedOpportunities = [\n 'success' => [],\n 'failed_ids' => [],\n ];\n $dealIds = array_column($deals, 'id');\n\n // Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the\n // queue job retries the whole batch and eventually requeues all deal IDs back to Redis.\n try {\n $companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');\n $contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');\n\n $associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);\n\n $existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(\n $this->config,\n array_map('strval', $dealIds)\n );\n $existingCrmIdSet = array_flip($existingCrmIds);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [\n 'teamId' => $this->team->getId(),\n 'dealCount' => count($dealIds),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n foreach ($deals as $deal) {\n try {\n $deal['associations'] = $this->prepareAssociationsForOpportunity(\n $deal['id'],\n $companyAssociations,\n $contactAssociations,\n $associationsData\n );\n\n $syncedOpportunity = $this->importOrUpdateOpportunity(\n $deal,\n isset($existingCrmIdSet[(string) $deal['id']])\n );\n if ($syncedOpportunity) {\n $syncedOpportunities['success'][] = $syncedOpportunity;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $deal['id'],\n 'error' => $e->getMessage(),\n ]);\n $syncedOpportunities['failed_ids'][] = $deal['id'];\n $syncedOpportunities['errors'][$deal['id']] = $e->getMessage();\n }\n }\n\n return $syncedOpportunities;\n }\n\n /**\n * Prepare associated entities for opportunities with optimized batch processing\n * Returns structured data with CRM ID to DB ID mappings for each opportunity\n */\n private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array\n {\n // Step 1: Collect all unique company and contact IDs from associations\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n\n // Step 2: Batch sync missing entities and get CRM ID to DB ID mappings\n $companyIdMappings = [];\n $contactIdMappings = [];\n\n if (! empty($allCompanyIds)) {\n $companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);\n }\n\n if (! empty($allContactIds)) {\n $contactIdMappings = $this->prepareAssociatedContacts($allContactIds);\n }\n\n return [\n 'company_id_mappings' => $companyIdMappings,\n 'contact_id_mappings' => $contactIdMappings,\n ];\n }\n\n /**\n * Flatten association data to get unique IDs\n */\n private function flattenAssociationIds(array $associations): array\n {\n $ids = [];\n foreach ($associations as $dealAssociations) {\n if (is_array($dealAssociations)) {\n foreach ($dealAssociations as $id) {\n $ids[$id] = true;\n }\n }\n }\n\n return array_keys($ids);\n }\n\n /**\n * Batch sync missing accounts\n */\n private function prepareAssociatedAccounts(array $companyIds): array\n {\n // Find which accounts already exist\n $existingAccounts = $this->crmEntityRepository\n ->findAccountsByExternalIds($this->config, $companyIds);\n\n $existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();\n\n $existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {\n return [$account->getCrmProviderId() => $account->getId()];\n })->toArray();\n\n $missingCompanyIds = array_diff($companyIds, $existingCompanyIds);\n\n if (empty($missingCompanyIds)) {\n return $existingAccountsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [\n 'teamId' => $this->team->getUuid(),\n 'total_companies' => count($companyIds),\n 'existing_companies' => count($existingCompanyIds),\n 'missing_companies' => count($missingCompanyIds),\n ]);\n\n // we already have limit on opportunity ids count\n // Initialize variable before try block\n $syncedAccountsData = [];\n\n try {\n $syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [\n 'size' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedAccountsData = [];\n }\n\n return $existingAccountsData + $syncedAccountsData;\n }\n\n /**\n * Prepare associated contacts - find existing and sync missing ones\n * Returns mapping of CRM ID to DB ID\n */\n private function prepareAssociatedContacts(array $contactIds): array\n {\n // Find which contacts already exist\n $existingContacts = $this->crmEntityRepository\n ->findContactsByExternalIds($this->config, $contactIds);\n\n $existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();\n\n // Create mapping for existing contacts\n $existingContactsData = $existingContacts->mapWithKeys(function ($contact) {\n return [$contact->getCrmProviderId() => $contact->getId()];\n })->toArray();\n\n $missingContactIds = array_diff($contactIds, $existingContactIds);\n\n if (empty($missingContactIds)) {\n return $existingContactsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [\n 'teamId' => $this->team->getUuid(),\n 'total_contacts' => count($contactIds),\n 'existing_contacts' => count($existingContactIds),\n 'missing_contacts' => count($missingContactIds),\n ]);\n\n // Sync missing contacts using batch API\n try {\n $syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [\n 'size' => count($missingContactIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedContactsData = [];\n }\n\n return $existingContactsData + $syncedContactsData;\n }\n\n private function batchSyncCrmObjects(string $objectType, array $crmIds): array\n {\n $syncObjects = [];\n $crmObjectIds = array_values($crmIds);\n\n foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {\n try {\n $objects = $objectType === 'companies' ?\n $this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :\n $this->client->getContactsByIds($chunk, $this->getContactFields());\n\n foreach ($objects as $objectId => $objectData) {\n $this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [\n 'requested_count' => count($chunk),\n 'synced_count' => count($objects),\n ]);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [\n 'ids' => $chunk,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncObjects;\n }\n\n private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void\n {\n try {\n $object = $objectType === 'companies' ?\n $this->importAccount($objectData) :\n $this->importContact($objectData);\n\n if ($object) {\n $syncObjects[$object->getCrmProviderId()] = $object->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [\n 'id' => $objectId,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n /**\n * Prepare associations for a single opportunity\n *\n * The return value is an array with the following structure:\n * [\n * 'companies' => [\n * $companyCrmId => $companyId,\n * ...\n * ],\n * 'contacts' => [\n * $contactCrmId => $contactId,\n * ...\n * ],\n * 'account_id' => $accountId,\n * ]\n */\n private function prepareAssociationsForOpportunity(\n string $oppCrmId,\n array $companyAssociations,\n array $contactAssociations,\n array $associationsData\n ): array {\n $associations = [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n\n $oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];\n foreach ($oppCompanyIds as $companyCrmId) {\n if (isset($associationsData['company_id_mappings'][$companyCrmId])) {\n $associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];\n\n // Set primary account (first company becomes primary account)\n if ($associations['account_id'] === null) {\n $associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];\n }\n }\n }\n\n $oppContactIds = $contactAssociations[$oppCrmId] ?? [];\n foreach ($oppContactIds as $contactCrmId) {\n if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {\n $associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];\n }\n }\n\n return $associations;\n }\n\n /**\n * Update only associations for an opportunity\n */\n private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void\n {\n // Update contact associations\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n // Update company (account) associations\n $this->updateOpportunityAccount($opportunity, $associations['account_id']);\n }\n\n /**\n * Remove all contact associations from an opportunity\n */\n private function removeAllOpportunityContacts(Opportunity $opportunity): void\n {\n $currentCount = (int) $opportunity->contacts()->count();\n\n if ($currentCount > 0) {\n $opportunity->contacts()->detach();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_count' => $currentCount,\n ]);\n }\n }\n\n private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void\n {\n if ($accountId === null) {\n // No account ID provided - keep current account\n return;\n }\n\n $currentAccountId = $opportunity->getAccountId();\n\n // Only update if account has changed\n if ($currentAccountId !== $accountId) {\n $opportunity->account_id = $accountId;\n $opportunity->save();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [\n 'opportunity_id' => $opportunity->getId(),\n 'old_account_id' => $currentAccountId,\n 'new_account_id' => $accountId,\n ]);\n }\n }\n\n /**\n * Find existing opportunities by external IDs (OPTIMIZED VERSION)\n * Uses batch query for better performance\n */\n private function findExistingOpportunities(array $crmIds): Collection\n {\n return $this->crmEntityRepository\n ->findOpportunitiesByExternalIds($this->config, $crmIds);\n }\n\n private function processOpportunityBatch(array $opportunities): int\n {\n $syncedOpportunities = $this->importOpportunityBatch($opportunities);\n\n return count($syncedOpportunities['success'] ?? []);\n }\n\n /**\n * Convert single deal associations from HubSpot format to internal format\n * Handles both HubSpot SDK objects and array formats\n *\n * @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed\n *\n * @return array Processed associations with DB IDs\n */\n private function convertDealAssociations(array $opportunityAssociations): array\n {\n $associations = $this->initializeAssociationsStructure();\n\n if (empty($opportunityAssociations)) {\n return $associations;\n }\n\n $associationIds = $this->extractAssociationIds($opportunityAssociations);\n\n $this->processCompanyAssociations($associationIds, $associations);\n $this->processContactAssociations($associationIds, $associations);\n\n return $associations;\n }\n\n private function initializeAssociationsStructure(): array\n {\n return [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n }\n\n private function extractAssociationIds(array $opportunityAssociations): array\n {\n $associationIds = [];\n\n foreach ($opportunityAssociations as $type => $associationData) {\n if (! empty($associationData)) {\n $associationIds[$type] = $this->convertSingleDealAssociations($associationData);\n }\n }\n\n return $associationIds;\n }\n\n private function processCompanyAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['companies'])) {\n return;\n }\n\n $companyId = $associationIds['companies'][0];\n $account = $this->findOrSyncAccount($companyId);\n\n if ($account instanceof Account) {\n $associations['companies'][$companyId] = $account->getId();\n $associations['account_id'] = $account->getId();\n }\n }\n\n private function processContactAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['contacts'])) {\n return;\n }\n\n foreach ($associationIds['contacts'] as $contactId) {\n $contact = $this->findOrSyncContact($contactId);\n\n if ($contact instanceof Contact) {\n $associations['contacts'][$contactId] = $contact->getId();\n }\n }\n }\n\n private function findOrSyncAccount(string $companyId): ?Account\n {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);\n\n if (! $account instanceof Account) {\n $account = $this->syncAccount($companyId);\n }\n\n return $account;\n }\n\n private function findOrSyncContact(string $contactId): ?Contact\n {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);\n\n if (! $contact instanceof Contact) {\n $contact = $this->syncContact($contactId);\n }\n\n return $contact;\n }\n\n private function convertSingleDealAssociations($opportunityAssociations = null): array\n {\n $associationData = [];\n\n if ($opportunityAssociations === null) {\n return $associationData;\n }\n\n // Handle array input (from extractAssociationIds)\n if (is_array($opportunityAssociations)) {\n return $opportunityAssociations;\n }\n\n // Handle CollectionResponseAssociatedId object\n if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {\n foreach ($opportunityAssociations->getResults() as $association) {\n $associationData[] = $association->getId();\n }\n }\n\n return $associationData;\n }\n\n private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity\n {\n if (empty($crmData['properties'])) {\n return null;\n }\n\n $crmId = (string) $crmData['id'];\n $properties = $crmData['properties'];\n $associations = $crmData['associations'] ?? [];\n\n $opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(\n $this->config,\n $crmId\n );\n\n if ($opportunityExists) {\n return $this->updateOpportunity($crmId, $properties, $associations);\n } else {\n return $this->createOpportunity($crmId, $properties, $associations);\n }\n }\n\n /**\n * Create new opportunity\n */\n private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n if (! $accountId) {\n return null;\n }\n\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n if (! $businessProcess) {\n return null;\n }\n\n $stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);\n if (! $stage) {\n return null;\n }\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n if ($opportunity->wasRecentlyCreated) {\n MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());\n }\n\n return $opportunity;\n }\n\n /**\n * Update existing opportunity\n */\n private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n $stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->updateOpportunityAssociations($opportunity, $associations);\n\n return $opportunity;\n }\n\n private function resolveAccountId(array $associations): ?int\n {\n if (! empty($associations['accountId'])) {\n return $associations['accountId'];\n }\n\n if (empty($associations)) {\n return null;\n }\n\n // we can't resolve multiple account ids (currently SDK returns one company)\n foreach ($associations['companies'] as $accountId) {\n return $accountId;\n }\n\n return null;\n }\n\n private function buildOpportunityData(\n array $properties,\n ?int $accountId,\n ?BusinessProcess $businessProcess,\n ?Stage $stage\n ): array {\n $ownerId = null;\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $name = 'Unknown';\n if (isset($properties['dealname'])) {\n $name = mb_strimwidth($properties['dealname'], 0, 128);\n }\n\n $amount = $this->resolveAmount($properties);\n $currency = $properties['deal_currency_code'] ?? null;\n\n $closeDate = null;\n if (! empty($properties['closedate'])) {\n $closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');\n }\n\n $remotelyCreatedAt = null;\n if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {\n $date = $this->parseCleanDatetime($properties['createdate']);\n $remotelyCreatedAt = $date?->format('Y-m-d H:i:s');\n }\n\n $closedStages = $this->getClosedDealStages();\n $isWon = in_array($properties['dealstage'], $closedStages['won']);\n $isLost = in_array($properties['dealstage'], $closedStages['lost']);\n\n $data = [\n 'team_id' => $this->team->getId(),\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'value' => ! empty($amount) ? $amount : null,\n 'currency_code' => CurrencyFormatter::formatCode($currency),\n 'close_date' => $closeDate,\n 'is_closed' => $isWon || $isLost,\n 'is_won' => $isWon,\n 'remotely_created_at' => $remotelyCreatedAt,\n 'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),\n 'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),\n ];\n\n if ($accountId) {\n $data['account_id'] = $accountId;\n }\n\n if ($stage) {\n $data['stage_id'] = $stage->id;\n }\n\n if ($businessProcess) {\n $recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);\n if ($recordType) {\n $data['record_type_id'] = $recordType->id;\n }\n }\n\n return $data;\n }\n\n private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess\n {\n if ($pipelineId === null) {\n return null;\n }\n\n if (isset($this->cachedBusinessProcesses[$pipelineId])) {\n return $this->cachedBusinessProcesses[$pipelineId];\n }\n\n $businessProcess = $this->getBusinessProcess($pipelineId);\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->importStages();\n $businessProcess = $this->getBusinessProcess($pipelineId);\n }\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->logger->info(\n '[HubSpot] Deal is not attached to a pipeline',\n [\n 'pipeline' => $pipelineId]\n );\n }\n\n $this->cachedBusinessProcesses[$pipelineId] = $businessProcess;\n\n return $businessProcess;\n }\n\n private function getBusinessProcess(string $pipelineId): ?BusinessProcess\n {\n return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);\n }\n\n private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage\n {\n if (empty($stageId)) {\n return null;\n }\n\n $cacheKey = $businessProcess->getId() . ':' . $stageId;\n if (isset($this->cachedStages[$cacheKey])) {\n return $this->cachedStages[$cacheKey];\n }\n\n $stage = $this->crmEntityRepository->getPipelineStageByConditions(\n $businessProcess,\n [\n 'crm_provider_id' => $stageId,\n 'type' => Stage::TYPE_OPPORTUNITY,\n ]\n );\n\n if ($stage === null) {\n $this->importStages(null, $stageId);\n }\n\n if ($stage === null) {\n $this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);\n }\n\n $this->cachedStages[$cacheKey] = $stage;\n\n return $stage;\n }\n\n private function resolveAmount(array $properties): ?string\n {\n $amount = null;\n if (! empty($properties['amount'])) {\n $amount = str_replace(',', '', $properties['amount']);\n }\n\n if ($this->config->hasDefaultCurrencyFieldSet()) {\n $valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();\n $amount = $properties[$valueFieldName] ?? $amount;\n }\n\n return $amount;\n }\n\n private function parseCleanDatetime(string $datetime): ?Carbon\n {\n // Treat pre-1980 values as invalid\n $minValidDate = Carbon::parse('1980-01-01 00:00:00');\n\n try {\n $date = Carbon::parse($datetime);\n\n if ($minValidDate->gt($date)) {\n return null;\n }\n\n return $date;\n } catch (Exception) {\n return null; // On parse error, treat as null\n }\n }\n\n private function resolveDealProbability(?string $stageProbability): int\n {\n if ($stageProbability === null) {\n return 0;\n }\n\n $probability = (float) $stageProbability;\n\n return $probability > 1 ? 0 : (int) ($probability * 100);\n }\n\n private function resolveForecastCategory(?string $forecastCategory): string\n {\n if (! $forecastCategory) {\n return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;\n }\n\n $forecastCategory = str_replace('_', ' ', $forecastCategory);\n\n return ucwords(strtolower($forecastCategory));\n }\n\n private function importExternalFieldData(array $properties, int $opportunityId): void\n {\n $crmFields = $this->getOpportunitySyncableFields();\n $this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);\n }\n\n private function importOpportunityContacts(Opportunity $opportunity, array $associations): void\n {\n // Handle empty or missing contact associations\n if (empty($associations)) {\n // Remove all existing contact associations if none provided\n $this->removeAllOpportunityContacts($opportunity);\n\n return;\n }\n\n // Use differential sync approach for better performance and accuracy\n $this->syncOpportunityContactsDifferential($opportunity, $associations);\n }\n\n /**\n * Sync opportunity contacts using differential approach\n * This compares current vs new associations and only makes necessary changes\n */\n private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void\n {\n $currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);\n $contactAssociationIds = array_keys($contactAssociations);\n\n $contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);\n $contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);\n\n if (empty($contactsToAdd) && empty($contactsToRemove)) {\n return;\n }\n\n $this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);\n\n $this->removeContactAssociations($opportunity, $contactsToRemove);\n $this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);\n }\n\n private function getCurrentContactCrmIds(Opportunity $opportunity): array\n {\n return $opportunity->contacts()\n ->pluck('contacts.crm_provider_id')\n ->toArray();\n }\n\n private function logContactAssociationChanges(\n Opportunity $opportunity,\n array $currentContactCrmIds,\n array $contactAssociations,\n array $contactsToAdd,\n array $contactsToRemove\n ): void {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [\n 'opportunity_id' => $opportunity->getId(),\n 'current_contacts' => $currentContactCrmIds,\n 'new_contacts' => $contactAssociations,\n 'contacts_to_add' => $contactsToAdd,\n 'contacts_to_remove' => $contactsToRemove,\n ]);\n }\n\n private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void\n {\n if (empty($contactsToRemove)) {\n return;\n }\n\n $contactsToDetach = $opportunity->contacts()\n ->whereIn('contacts.crm_provider_id', $contactsToRemove)\n ->pluck('contacts.id')\n ->toArray();\n\n if (! empty($contactsToDetach)) {\n $opportunity->contacts()->detach($contactsToDetach);\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_contact_crm_ids' => $contactsToRemove,\n 'removed_contact_count' => count($contactsToDetach),\n ]);\n }\n }\n\n private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void\n {\n if (empty($contactsToAdd)) {\n return;\n }\n\n $contactsAdded = [];\n foreach ($contactsToAdd as $crmId) {\n $id = $contactAssociations[$crmId];\n\n if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {\n $contactsAdded[] = $crmId;\n }\n }\n\n $this->logAddedContacts($opportunity, $contactsAdded);\n }\n\n private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);\n\n if (! $contact) {\n return false;\n }\n\n return $this->performContactAttachment($opportunity, $contact, $crmId);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [\n 'opportunity_id' => $opportunity->getId(),\n 'contact_crm_id' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool\n {\n try {\n $opportunity->contacts()->attach($contact->getId(), [\n 'crm_provider_id' => $crmId,\n ]);\n\n return true;\n } catch (\\Illuminate\\Database\\QueryException $e) {\n if (str_contains($e->getMessage(), 'Duplicate entry')) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [\n 'contact_id' => $contact->getId(),\n 'contact_crm_id' => $crmId,\n 'opportunity_id' => $opportunity->getId(),\n ]);\n\n return false;\n }\n\n throw $e;\n }\n }\n\n private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void\n {\n if (! empty($contactsAdded)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'contacts_to_add_count' => count($contactsAdded),\n 'added_contact_crm_ids' => $contactsAdded,\n 'added_contacts_count' => count($contactsAdded),\n ]);\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<template>\n <WelcomeLayout\n title=\"Account disconnected\"\n textPosition=\"center\"\n :icon=\"faUnlink\"\n :class=\"$style.layout\"\n >\n <div :class=\"$style.container\" v-if=\"providersLoaded\">\n <p>\n <strong>\n It looks like your {{ localProvider.displayName }} account has become\n disconnected\n </strong>\n </p>\n <p :class=\"$style.small\">Please re-connect to continue</p>\n <p v-if=\"isInIframe\">\n We'll open the {{ localProvider.displayName }} authentication in a new\n tab. Please return here and refresh the page once complete\n </p>\n\n <GoogleLikeButton\n v-if=\"localProvider.viaIntegrationApp && crmTokenLoaded\"\n as=\"a\"\n :key=\"localProvider.name\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n @click=\"integrationAppOnClick\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n <GoogleLikeButton\n v-if=\"!localProvider.viaIntegrationApp\"\n as=\"a\"\n :key=\"localProvider.name\"\n :href=\"`/auth/redirect/${localProvider.name}`\"\n :target=\"target\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n </div>\n <BuildInfo />\n\n <KioskBanner />\n </WelcomeLayout>\n</template>\n\n<script>\nimport window from \"window\";\nimport axios from \"axios\";\nimport { faUnlink } from \"@fortawesome/pro-regular-svg-icons\";\nimport isInIframe from \"@/utils/isInIframe\";\nimport BuildInfo from \"@/components/layout/BuildInfo/BuildInfo.vue\";\nimport KioskBanner from \"@/components/shared/KioskBanner/KioskBanner.vue\";\nimport WelcomeLayout from \"@/components/layout/WelcomeLayout/WelcomeLayout.vue\";\nimport GoogleLikeButton from \"@/components/shared/Buttons/GoogleLikeButton.vue\";\nimport { showSnackbarError, normalizeError } from \"@/utils/index\";\nimport { IntegrationAppClient } from \"@integration-app/sdk\";\n\nexport default {\n name: \"ConnectPage\",\n components: {\n BuildInfo,\n KioskBanner,\n WelcomeLayout,\n GoogleLikeButton,\n },\n data() {\n return {\n ...window.connectData,\n crmToken: null,\n faUnlink,\n isInIframe,\n providers: [],\n providersLoaded: false,\n crmTokenLoaded: false,\n };\n },\n computed: {\n localProvider() {\n return this.providers.find((e) => e.name === this.provider);\n },\n target() {\n return this.isInIframe ? \"_blank\" : null;\n },\n },\n created() {\n this.getProviders();\n },\n mounted() {\n this.showErrors();\n },\n watch: {\n providersLoaded() {\n if (this.providersLoaded) {\n this.prepareIntegrationAppConnection();\n }\n },\n },\n methods: {\n showErrors() {\n if (!this.error) return;\n\n showSnackbarError(this.error, undefined, undefined, false);\n },\n unwrapEntityResponse({ data }) {\n return data.map(({ icon, name, displayName, viaIntegrationApp }) => {\n return { icon, name, displayName, viaIntegrationApp };\n });\n },\n async getProviders() {\n try {\n const response = await axios.get(\"/api/v1/connect-providers\");\n this.providers = this.unwrapEntityResponse(response);\n this.providersLoaded = true;\n } catch {\n showSnackbarError(\n \"An error occurred, while loading form data (connect providers).\",\n );\n }\n },\n async prepareIntegrationAppConnection() {\n if (this.localProvider.viaIntegrationApp) {\n try {\n const response = await axios.get(\"/api/v1/integration-app-token\");\n this.crmToken = response.data.token;\n this.crmTokenLoaded = true;\n } catch (error) {\n console.log(error);\n showSnackbarError(\n `An error occurred while preparing the page.\n Try refreshing, if the error persists get in touch with the Jiminny team.`,\n );\n }\n }\n },\n async integrationAppOnClick() {\n console.log('[IntegrationApp] integrationAppOnClick called');\n const integrationApp = new IntegrationAppClient({\n token: this.crmToken,\n });\n\n const connection = await integrationApp\n .integration(this.localProvider.name)\n .openNewConnection({\n showPoweredBy: false,\n allowMultipleConnections: false,\n }).catch((err) => {\n console.log('[IntegrationApp] openNewConnection rejected:', err);\n return null;\n });\n\n console.log('[IntegrationApp] openNewConnection resolved:', JSON.stringify(connection));\n\n // [IntegrationApp] openNewConnection resolved: {\n // \"id\":\"69e0b41a67d0068c2ca0b48e\",\n // \"name\":\"Zoho CRM\",\n // \"userId\":\"1ece66c8-feb1-4df1-b321-21607daf4623\",\n // \"tenantId\":\"69e0b3faef3e7b6248189289\",\n // \"isTest\":false,\n // \"connected\":true,\n // \"state\":\"READY\",\n // \"errors\":[],\n // \"integrationId\":\"66fe6c913202f3a165e3c14d\",\n // \"externalAppId\":\"6671653e7e2d642e4e41b0fa\",\n // \"authOptionKey\":\"\",\n // \"createdAt\":\"2026-04-16T10:04:10.420Z\",\n // \"updatedAt\":\"2026-04-16T10:04:10.575Z\",\n // \"retryAttempts\":0,\n // \"isDeactivated\":false\n // }\n\n if (connection && connection.disconnected !== true && connection.connected !== false) {\n console.log('[IntegrationApp] connection condition matched');\n try {\n const saveRequest = await axios.post(\n \"/api/v1/integration-app-connect\",\n );\n if (saveRequest.data && saveRequest.data.success === true) {\n /** If all is good refresh the page here */\n window.location = \"/dashboard\";\n return;\n }\n\n throw new Error(saveRequest.data.message);\n } catch (error) {\n console.log(error);\n showSnackbarError(normalizeError(error));\n }\n }\n },\n },\n};\n</script>\n\n<style module lang=\"less\" src=\"./connect.less\"></style>","depth":4,"value":"<template>\n <WelcomeLayout\n title=\"Account disconnected\"\n textPosition=\"center\"\n :icon=\"faUnlink\"\n :class=\"$style.layout\"\n >\n <div :class=\"$style.container\" v-if=\"providersLoaded\">\n <p>\n <strong>\n It looks like your {{ localProvider.displayName }} account has become\n disconnected\n </strong>\n </p>\n <p :class=\"$style.small\">Please re-connect to continue</p>\n <p v-if=\"isInIframe\">\n We'll open the {{ localProvider.displayName }} authentication in a new\n tab. Please return here and refresh the page once complete\n </p>\n\n <GoogleLikeButton\n v-if=\"localProvider.viaIntegrationApp && crmTokenLoaded\"\n as=\"a\"\n :key=\"localProvider.name\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n @click=\"integrationAppOnClick\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n <GoogleLikeButton\n v-if=\"!localProvider.viaIntegrationApp\"\n as=\"a\"\n :key=\"localProvider.name\"\n :href=\"`/auth/redirect/${localProvider.name}`\"\n :target=\"target\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n </div>\n <BuildInfo />\n\n <KioskBanner />\n </WelcomeLayout>\n</template>\n\n<script>\nimport window from \"window\";\nimport axios from \"axios\";\nimport { faUnlink } from \"@fortawesome/pro-regular-svg-icons\";\nimport isInIframe from \"@/utils/isInIframe\";\nimport BuildInfo from \"@/components/layout/BuildInfo/BuildInfo.vue\";\nimport KioskBanner from \"@/components/shared/KioskBanner/KioskBanner.vue\";\nimport WelcomeLayout from \"@/components/layout/WelcomeLayout/WelcomeLayout.vue\";\nimport GoogleLikeButton from \"@/components/shared/Buttons/GoogleLikeButton.vue\";\nimport { showSnackbarError, normalizeError } from \"@/utils/index\";\nimport { IntegrationAppClient } from \"@integration-app/sdk\";\n\nexport default {\n name: \"ConnectPage\",\n components: {\n BuildInfo,\n KioskBanner,\n WelcomeLayout,\n GoogleLikeButton,\n },\n data() {\n return {\n ...window.connectData,\n crmToken: null,\n faUnlink,\n isInIframe,\n providers: [],\n providersLoaded: false,\n crmTokenLoaded: false,\n };\n },\n computed: {\n localProvider() {\n return this.providers.find((e) => e.name === this.provider);\n },\n target() {\n return this.isInIframe ? \"_blank\" : null;\n },\n },\n created() {\n this.getProviders();\n },\n mounted() {\n this.showErrors();\n },\n watch: {\n providersLoaded() {\n if (this.providersLoaded) {\n this.prepareIntegrationAppConnection();\n }\n },\n },\n methods: {\n showErrors() {\n if (!this.error) return;\n\n showSnackbarError(this.error, undefined, undefined, false);\n },\n unwrapEntityResponse({ data }) {\n return data.map(({ icon, name, displayName, viaIntegrationApp }) => {\n return { icon, name, displayName, viaIntegrationApp };\n });\n },\n async getProviders() {\n try {\n const response = await axios.get(\"/api/v1/connect-providers\");\n this.providers = this.unwrapEntityResponse(response);\n this.providersLoaded = true;\n } catch {\n showSnackbarError(\n \"An error occurred, while loading form data (connect providers).\",\n );\n }\n },\n async prepareIntegrationAppConnection() {\n if (this.localProvider.viaIntegrationApp) {\n try {\n const response = await axios.get(\"/api/v1/integration-app-token\");\n this.crmToken = response.data.token;\n this.crmTokenLoaded = true;\n } catch (error) {\n console.log(error);\n showSnackbarError(\n `An error occurred while preparing the page.\n Try refreshing, if the error persists get in touch with the Jiminny team.`,\n );\n }\n }\n },\n async integrationAppOnClick() {\n console.log('[IntegrationApp] integrationAppOnClick called');\n const integrationApp = new IntegrationAppClient({\n token: this.crmToken,\n });\n\n const connection = await integrationApp\n .integration(this.localProvider.name)\n .openNewConnection({\n showPoweredBy: false,\n allowMultipleConnections: false,\n }).catch((err) => {\n console.log('[IntegrationApp] openNewConnection rejected:', err);\n return null;\n });\n\n console.log('[IntegrationApp] openNewConnection resolved:', JSON.stringify(connection));\n\n // [IntegrationApp] openNewConnection resolved: {\n // \"id\":\"69e0b41a67d0068c2ca0b48e\",\n // \"name\":\"Zoho CRM\",\n // \"userId\":\"1ece66c8-feb1-4df1-b321-21607daf4623\",\n // \"tenantId\":\"69e0b3faef3e7b6248189289\",\n // \"isTest\":false,\n // \"connected\":true,\n // \"state\":\"READY\",\n // \"errors\":[],\n // \"integrationId\":\"66fe6c913202f3a165e3c14d\",\n // \"externalAppId\":\"6671653e7e2d642e4e41b0fa\",\n // \"authOptionKey\":\"\",\n // \"createdAt\":\"2026-04-16T10:04:10.420Z\",\n // \"updatedAt\":\"2026-04-16T10:04:10.575Z\",\n // \"retryAttempts\":0,\n // \"isDeactivated\":false\n // }\n\n if (connection && connection.disconnected !== true && connection.connected !== false) {\n console.log('[IntegrationApp] connection condition matched');\n try {\n const saveRequest = await axios.post(\n \"/api/v1/integration-app-connect\",\n );\n if (saveRequest.data && saveRequest.data.success === true) {\n /** If all is good refresh the page here */\n window.location = \"/dashboard\";\n return;\n }\n\n throw new Error(saveRequest.data.message);\n } catch (error) {\n console.log(error);\n showSnackbarError(normalizeError(error));\n }\n }\n },\n },\n};\n</script>\n\n<style module lang=\"less\" src=\"./connect.less\"></style>","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-9063734413195222639
|
-8178086449155632858
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20692-fix-integration- Project: faVsco.js, menu
JY-20692-fix-integration-app-[API_KEY], menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
cachedStages
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
2/4
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Code changed:
Hide
Sync Changes
Hide This Notification
33
2
19
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use HubSpot\Client\Crm\Deals\Model\CollectionResponseAssociatedId;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Models\Account;
use Exception;
use Jiminny\Component\DealInsights\Forecast\Forecast;
use Jiminny\Jobs\Crm\MatchActivitiesToNewOpportunity;
use Jiminny\Models\Contact;
use Jiminny\Models\Crm\BusinessProcess;
use Jiminny\Exceptions\CrmException;
use Jiminny\Models\Opportunity;
use Illuminate\Support\Collection;
use Jiminny\Models\Stage;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Crm\Hubspot\DealFieldsService;
use Jiminny\Services\Crm\Hubspot\OpportunitySyncStrategy\HubspotSingleSyncStrategy;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Services\Crm\OpportunitySyncStrategyResolver;
use Jiminny\Utils\CurrencyFormatter;
/**
* Optimized sync methods for better performance
* These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains
*/
trait OpportunitySyncTrait
{
private const int BATCH_SIZE = 100;
private const int BATCH_PROCESS_SIZE = 800;
protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;
protected CrmEntityRepository $crmEntityRepository;
protected DealFieldsService $dealFieldsService;
private ?array $cachedClosedDealStages = null;
private array $cachedBusinessProcesses = [];
private array $cachedStages = [];
public function syncOpportunities(array $parameters, ?string $strategy = null): int
{
$strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);
$parameters['config'] = $this->config;
$syncCount = 0;
$reportedTotal = 0;
$lastSyncedId = [];
try {
foreach ($strategies as $strategyName => $syncStrategy) {
$this->logger->info(
'[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .
$strategyName
);
$total = 0;
$lastId = null;
$buffer = [];
// HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies
foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {
$buffer[] = $hsOpportunity;
// process every 800 rows (fits < 1 000 association limit)
if (\count($buffer) >= self::BATCH_PROCESS_SIZE) {
$syncCount += $this->processOpportunityBatch($buffer);
$buffer = [];
}
}
// leftovers
if ($buffer) {
$syncCount += $this->processOpportunityBatch($buffer);
}
$reportedTotal += $total;
$lastSyncedId = $lastId;
}
} catch (\HubSpot\Client\Crm\Deals\ApiException | CrmException $e) {
$this->handleSyncException($e, $parameters);
}
$this->logger->info(
'[HubSpot] Synced opportunities',
[
'team' => $this->team->getId(),
'sync_count' => $syncCount,
'total' => $reportedTotal,
'last_synced_id' => $lastSyncedId,
]
);
return $reportedTotal;
}
private function handleSyncException(\Throwable $e, array $parameters): void
{
if (($parameters['since'] ?? null) instanceof Carbon) {
$parameters['since'] = $parameters['since']->toDateTimeString();
}
$parameters['config'] = $this->config->getId();
$this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [
'teamId' => $this->team->getUuid(),
'parameters' => $parameters,
'reason' => $e->getMessage(),
]);
}
/**
* @inheritdoc
*/
public function syncOpportunity(string $crmId): ?Opportunity
{
$strategy = $this->opportunitySyncStrategyResolver->resolve(
$this->config,
OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,
);
$parameters = [
'config' => $this->config,
'crm_id' => $crmId,
];
try {
if (! $strategy instanceof HubspotSingleSyncStrategy) {
throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');
}
$hsOpportunity = $strategy->fetchOpportunity($parameters);
} catch (\HubSpot\Client\Crm\Deals\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
$hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);
return $this->importOrUpdateOpportunity($hsOpportunity);
}
/**
* Process webhook-collected opportunity batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportOpportunityBatch jobs for batch processing.
*
* @return int Number of opportunity IDs dispatched to jobs
*/
public function batchSyncOpportunities(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,
$configId
);
}
/**
* Import a batch of opportunities by their CRM IDs.
* Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().
*
* @param array<string> $crmIds HubSpot deal CRM IDs
*
* @return array{success: array, failed_ids: array, errors?: array<string, string>}
*/
public function importOpportunityBatchByIds(array $crmIds): array
{
$fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);
$allDeals = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
$deals = $this->client->getOpportunitiesByIds($chunk, $fields);
foreach ($deals as $deal) {
$allDeals[] = $deal;
}
}
// IDs not returned by HubSpot are likely deleted or inaccessible deals.
// These are not failures — retrying won't bring them back.
$fetchedIds = array_map('strval', array_column($allDeals, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allDeals),
]);
}
if (empty($allDeals)) {
return ['success' => [], 'failed_ids' => []];
}
return $this->importOpportunityBatch($allDeals);
}
private function getClosedDealStages(): array
{
if ($this->cachedClosedDealStages !== null) {
return $this->cachedClosedDealStages;
}
$stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);
$data = [
'lost' => [],
'won' => [],
];
foreach ($stages as $stage) {
if ($stage->probability == 0.00) {
$data['lost'][] = $stage->crm_provider_id;
}
if ($stage->probability == 100.00) {
$data['won'][] = $stage->crm_provider_id;
}
}
$this->cachedClosedDealStages = $data;
return $data;
}
/**
* Import deals into the database with pre-fetched associations.
*
* API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT
* caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()
* where Laravel retries the whole job with backoff. After all retries exhausted,
* failed() requeues all IDs to Redis.
*
* The per-deal loop catches exceptions individually. A deal can end up in three states:
* - success: imported/updated successfully
* - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)
* These are permanent issues — retrying won't fix them.
* - skipped (null): missing dependencies (no account, unknown pipeline/stage).
* This is acceptable — the deal cannot be imported until those exist.
*/
private function importOpportunityBatch(array $deals): array
{
$syncedOpportunities = [
'success' => [],
'failed_ids' => [],
];
$dealIds = array_column($deals, 'id');
// Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the
// queue job retries the whole batch and eventually requeues all deal IDs back to Redis.
try {
$companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');
$contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');
$associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);
$existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(
$this->config,
array_map('strval', $dealIds)
);
$existingCrmIdSet = array_flip($existingCrmIds);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [
'teamId' => $this->team->getId(),
'dealCount' => count($dealIds),
'error' => $e->getMessage(),
]);
throw $e;
}
foreach ($deals as $deal) {
try {
$deal['associations'] = $this->prepareAssociationsForOpportunity(
$deal['id'],
$companyAssociations,
$contactAssociations,
$associationsData
);
$syncedOpportunity = $this->importOrUpdateOpportunity(
$deal,
isset($existingCrmIdSet[(string) $deal['id']])
);
if ($syncedOpportunity) {
$syncedOpportunities['success'][] = $syncedOpportunity;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [
'teamId' => $this->team->getId(),
'crmId' => $deal['id'],
'error' => $e->getMessage(),
]);
$syncedOpportunities['failed_ids'][] = $deal['id'];
$syncedOpportunities['errors'][$deal['id']] = $e->getMessage();
}
}
return $syncedOpportunities;
}
/**
* Prepare associated entities for opportunities with optimized batch processing
* Returns structured data with CRM ID to DB ID mappings for each opportunity
*/
private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array
{
// Step 1: Collect all unique company and contact IDs from associations
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
// Step 2: Batch sync missing entities and get CRM ID to DB ID mappings
$companyIdMappings = [];
$contactIdMappings = [];
if (! empty($allCompanyIds)) {
$companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);
}
if (! empty($allContactIds)) {
$contactIdMappings = $this->prepareAssociatedContacts($allContactIds);
}
return [
'company_id_mappings' => $companyIdMappings,
'contact_id_mappings' => $contactIdMappings,
];
}
/**
* Flatten association data to get unique IDs
*/
private function flattenAssociationIds(array $associations): array
{
$ids = [];
foreach ($associations as $dealAssociations) {
if (is_array($dealAssociations)) {
foreach ($dealAssociations as $id) {
$ids[$id] = true;
}
}
}
return array_keys($ids);
}
/**
* Batch sync missing accounts
*/
private function prepareAssociatedAccounts(array $companyIds): array
{
// Find which accounts already exist
$existingAccounts = $this->crmEntityRepository
->findAccountsByExternalIds($this->config, $companyIds);
$existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();
$existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {
return [$account->getCrmProviderId() => $account->getId()];
})->toArray();
$missingCompanyIds = array_diff($companyIds, $existingCompanyIds);
if (empty($missingCompanyIds)) {
return $existingAccountsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [
'teamId' => $this->team->getUuid(),
'total_companies' => count($companyIds),
'existing_companies' => count($existingCompanyIds),
'missing_companies' => count($missingCompanyIds),
]);
// we already have limit on opportunity ids count
// Initialize variable before try block
$syncedAccountsData = [];
try {
$syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [
'size' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
$syncedAccountsData = [];
}
return $existingAccountsData + $syncedAccountsData;
}
/**
* Prepare associated contacts - find existing and sync missing ones
* Returns mapping of CRM ID to DB ID
*/
private function prepareAssociatedContacts(array $contactIds): array
{
// Find which contacts already exist
$existingContacts = $this->crmEntityRepository
->findContactsByExternalIds($this->config, $contactIds);
$existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();
// Create mapping for existing contacts
$existingContactsData = $existingContacts->mapWithKeys(function ($contact) {
return [$contact->getCrmProviderId() => $contact->getId()];
})->toArray();
$missingContactIds = array_diff($contactIds, $existingContactIds);
if (empty($missingContactIds)) {
return $existingContactsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [
'teamId' => $this->team->getUuid(),
'total_contacts' => count($contactIds),
'existing_contacts' => count($existingContactIds),
'missing_contacts' => count($missingContactIds),
]);
// Sync missing contacts using batch API
try {
$syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [
'size' => count($missingContactIds),
'error' => $e->getMessage(),
]);
$syncedContactsData = [];
}
return $existingContactsData + $syncedContactsData;
}
private function batchSyncCrmObjects(string $objectType, array $crmIds): array
{
$syncObjects = [];
$crmObjectIds = array_values($crmIds);
foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {
try {
$objects = $objectType === 'companies' ?
$this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :
$this->client->getContactsByIds($chunk, $this->getContactFields());
foreach ($objects as $objectId => $objectData) {
$this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [
'requested_count' => count($chunk),
'synced_count' => count($objects),
]);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [
'ids' => $chunk,
'error' => $e->getMessage(),
]);
}
}
return $syncObjects;
}
private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void
{
try {
$object = $objectType === 'companies' ?
$this->importAccount($objectData) :
$this->importContact($objectData);
if ($object) {
$syncObjects[$object->getCrmProviderId()] = $object->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [
'id' => $objectId,
'error' => $e->getMessage(),
]);
}
}
/**
* Prepare associations for a single opportunity
*
* The return value is an array with the following structure:
* [
* 'companies' => [
* $companyCrmId => $companyId,
* ...
* ],
* 'contacts' => [
* $contactCrmId => $contactId,
* ...
* ],
* 'account_id' => $accountId,
* ]
*/
private function prepareAssociationsForOpportunity(
string $oppCrmId,
array $companyAssociations,
array $contactAssociations,
array $associationsData
): array {
$associations = [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
$oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];
foreach ($oppCompanyIds as $companyCrmId) {
if (isset($associationsData['company_id_mappings'][$companyCrmId])) {
$associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];
// Set primary account (first company becomes primary account)
if ($associations['account_id'] === null) {
$associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];
}
}
}
$oppContactIds = $contactAssociations[$oppCrmId] ?? [];
foreach ($oppContactIds as $contactCrmId) {
if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {
$associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];
}
}
return $associations;
}
/**
* Update only associations for an opportunity
*/
private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void
{
// Update contact associations
$this->importOpportunityContacts($opportunity, $associations['contacts']);
// Update company (account) associations
$this->updateOpportunityAccount($opportunity, $associations['account_id']);
}
/**
* Remove all contact associations from an opportunity
*/
private function removeAllOpportunityContacts(Opportunity $opportunity): void
{
$currentCount = (int) $opportunity->contacts()->count();
if ($currentCount > 0) {
$opportunity->contacts()->detach();
$this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_count' => $currentCount,
]);
}
}
private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void
{
if ($accountId === null) {
// No account ID provided - keep current account
return;
}
$currentAccountId = $opportunity->getAccountId();
// Only update if account has changed
if ($currentAccountId !== $accountId) {
$opportunity->account_id = $accountId;
$opportunity->save();
$this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [
'opportunity_id' => $opportunity->getId(),
'old_account_id' => $currentAccountId,
'new_account_id' => $accountId,
]);
}
}
/**
* Find existing opportunities by external IDs (OPTIMIZED VERSION)
* Uses batch query for better performance
*/
private function findExistingOpportunities(array $crmIds): Collection
{
return $this->crmEntityRepository
->findOpportunitiesByExternalIds($this->config, $crmIds);
}
private function processOpportunityBatch(array $opportunities): int
{
$syncedOpportunities = $this->importOpportunityBatch($opportunities);
return count($syncedOpportunities['success'] ?? []);
}
/**
* Convert single deal associations from HubSpot format to internal format
* Handles both HubSpot SDK objects and array formats
*
* @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed
*
* @return array Processed associations with DB IDs
*/
private function convertDealAssociations(array $opportunityAssociations): array
{
$associations = $this->initializeAssociationsStructure();
if (empty($opportunityAssociations)) {
return $associations;
}
$associationIds = $this->extractAssociationIds($opportunityAssociations);
$this->processCompanyAssociations($associationIds, $associations);
$this->processContactAssociations($associationIds, $associations);
return $associations;
}
private function initializeAssociationsStructure(): array
{
return [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
}
private function extractAssociationIds(array $opportunityAssociations): array
{
$associationIds = [];
foreach ($opportunityAssociations as $type => $associationData) {
if (! empty($associationData)) {
$associationIds[$type] = $this->convertSingleDealAssociations($associationData);
}
}
return $associationIds;
}
private function processCompanyAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['companies'])) {
return;
}
$companyId = $associationIds['companies'][0];
$account = $this->findOrSyncAccount($companyId);
if ($account instanceof Account) {
$associations['companies'][$companyId] = $account->getId();
$associations['account_id'] = $account->getId();
}
}
private function processContactAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['contacts'])) {
return;
}
foreach ($associationIds['contacts'] as $contactId) {
$contact = $this->findOrSyncContact($contactId);
if ($contact instanceof Contact) {
$associations['contacts'][$contactId] = $contact->getId();
}
}
}
private function findOrSyncAccount(string $companyId): ?Account
{
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);
if (! $account instanceof Account) {
$account = $this->syncAccount($companyId);
}
return $account;
}
private function findOrSyncContact(string $contactId): ?Contact
{
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);
if (! $contact instanceof Contact) {
$contact = $this->syncContact($contactId);
}
return $contact;
}
private function convertSingleDealAssociations($opportunityAssociations = null): array
{
$associationData = [];
if ($opportunityAssociations === null) {
return $associationData;
}
// Handle array input (from extractAssociationIds)
if (is_array($opportunityAssociations)) {
return $opportunityAssociations;
}
// Handle CollectionResponseAssociatedId object
if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {
foreach ($opportunityAssociations->getResults() as $association) {
$associationData[] = $association->getId();
}
}
return $associationData;
}
private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity
{
if (empty($crmData['properties'])) {
return null;
}
$crmId = (string) $crmData['id'];
$properties = $crmData['properties'];
$associations = $crmData['associations'] ?? [];
$opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(
$this->config,
$crmId
);
if ($opportunityExists) {
return $this->updateOpportunity($crmId, $properties, $associations);
} else {
return $this->createOpportunity($crmId, $properties, $associations);
}
}
/**
* Create new opportunity
*/
private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity
{
$accountId = $this->resolveAccountId($associations);
if (! $accountId) {
return null;
}
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
if (! $businessProcess) {
return null;
}
$stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);
if (! $stage) {
return null;
}
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->importOpportunityContacts($opportunity, $associations['contacts']);
if ($opportunity->wasRecentlyCreated) {
MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());
}
return $opportunity;
}
/**
* Update existing opportunity
*/
private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity
{
$accountId = $this->resolveAccountId($associations);
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
$stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->updateOpportunityAssociations($opportunity, $associations);
return $opportunity;
}
private function resolveAccountId(array $associations): ?int
{
if (! empty($associations['accountId'])) {
return $associations['accountId'];
}
if (empty($associations)) {
return null;
}
// we can't resolve multiple account ids (currently SDK returns one company)
foreach ($associations['companies'] as $accountId) {
return $accountId;
}
return null;
}
private function buildOpportunityData(
array $properties,
?int $accountId,
?BusinessProcess $businessProcess,
?Stage $stage
): array {
$ownerId = null;
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$name = 'Unknown';
if (isset($properties['dealname'])) {
$name = mb_strimwidth($properties['dealname'], 0, 128);
}
$amount = $this->resolveAmount($properties);
$currency = $properties['deal_currency_code'] ?? null;
$closeDate = null;
if (! empty($properties['closedate'])) {
$closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');
}
$remotelyCreatedAt = null;
if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {
$date = $this->parseCleanDatetime($properties['createdate']);
$remotelyCreatedAt = $date?->format('Y-m-d H:i:s');
}
$closedStages = $this->getClosedDealStages();
$isWon = in_array($properties['dealstage'], $closedStages['won']);
$isLost = in_array($properties['dealstage'], $closedStages['lost']);
$data = [
'team_id' => $this->team->getId(),
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => $name,
'value' => ! empty($amount) ? $amount : null,
'currency_code' => CurrencyFormatter::formatCode($currency),
'close_date' => $closeDate,
'is_closed' => $isWon || $isLost,
'is_won' => $isWon,
'remotely_created_at' => $remotelyCreatedAt,
'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),
'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),
];
if ($accountId) {
$data['account_id'] = $accountId;
}
if ($stage) {
$data['stage_id'] = $stage->id;
}
if ($businessProcess) {
$recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);
if ($recordType) {
$data['record_type_id'] = $recordType->id;
}
}
return $data;
}
private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess
{
if ($pipelineId === null) {
return null;
}
if (isset($this->cachedBusinessProcesses[$pipelineId])) {
return $this->cachedBusinessProcesses[$pipelineId];
}
$businessProcess = $this->getBusinessProcess($pipelineId);
if (! $businessProcess instanceof BusinessProcess) {
$this->importStages();
$businessProcess = $this->getBusinessProcess($pipelineId);
}
if (! $businessProcess instanceof BusinessProcess) {
$this->logger->info(
'[HubSpot] Deal is not attached to a pipeline',
[
'pipeline' => $pipelineId]
);
}
$this->cachedBusinessProcesses[$pipelineId] = $businessProcess;
return $businessProcess;
}
private function getBusinessProcess(string $pipelineId): ?BusinessProcess
{
return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);
}
private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage
{
if (empty($stageId)) {
return null;
}
$cacheKey = $businessProcess->getId() . ':' . $stageId;
if (isset($this->cachedStages[$cacheKey])) {
return $this->cachedStages[$cacheKey];
}
$stage = $this->crmEntityRepository->getPipelineStageByConditions(
$businessProcess,
[
'crm_provider_id' => $stageId,
'type' => Stage::TYPE_OPPORTUNITY,
]
);
if ($stage === null) {
$this->importStages(null, $stageId);
}
if ($stage === null) {
$this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);
}
$this->cachedStages[$cacheKey] = $stage;
return $stage;
}
private function resolveAmount(array $properties): ?string
{
$amount = null;
if (! empty($properties['amount'])) {
$amount = str_replace(',', '', $properties['amount']);
}
if ($this->config->hasDefaultCurrencyFieldSet()) {
$valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();
$amount = $properties[$valueFieldName] ?? $amount;
}
return $amount;
}
private function parseCleanDatetime(string $datetime): ?Carbon
{
// Treat pre-1980 values as invalid
$minValidDate = Carbon::parse('1980-01-01 00:00:00');
try {
$date = Carbon::parse($datetime);
if ($minValidDate->gt($date)) {
return null;
}
return $date;
} catch (Exception) {
return null; // On parse error, treat as null
}
}
private function resolveDealProbability(?string $stageProbability): int
{
if ($stageProbability === null) {
return 0;
}
$probability = (float) $stageProbability;
return $probability > 1 ? 0 : (int) ($probability * 100);
}
private function resolveForecastCategory(?string $forecastCategory): string
{
if (! $forecastCategory) {
return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;
}
$forecastCategory = str_replace('_', ' ', $forecastCategory);
return ucwords(strtolower($forecastCategory));
}
private function importExternalFieldData(array $properties, int $opportunityId): void
{
$crmFields = $this->getOpportunitySyncableFields();
$this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);
}
private function importOpportunityContacts(Opportunity $opportunity, array $associations): void
{
// Handle empty or missing contact associations
if (empty($associations)) {
// Remove all existing contact associations if none provided
$this->removeAllOpportunityContacts($opportunity);
return;
}
// Use differential sync approach for better performance and accuracy
$this->syncOpportunityContactsDifferential($opportunity, $associations);
}
/**
* Sync opportunity contacts using differential approach
* This compares current vs new associations and only makes necessary changes
*/
private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void
{
$currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);
$contactAssociationIds = array_keys($contactAssociations);
$contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);
$contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);
if (empty($contactsToAdd) && empty($contactsToRemove)) {
return;
}
$this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);
$this->removeContactAssociations($opportunity, $contactsToRemove);
$this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);
}
private function getCurrentContactCrmIds(Opportunity $opportunity): array
{
return $opportunity->contacts()
->pluck('contacts.crm_provider_id')
->toArray();
}
private function logContactAssociationChanges(
Opportunity $opportunity,
array $currentContactCrmIds,
array $contactAssociations,
array $contactsToAdd,
array $contactsToRemove
): void {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [
'opportunity_id' => $opportunity->getId(),
'current_contacts' => $currentContactCrmIds,
'new_contacts' => $contactAssociations,
'contacts_to_add' => $contactsToAdd,
'contacts_to_remove' => $contactsToRemove,
]);
}
private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void
{
if (empty($contactsToRemove)) {
return;
}
$contactsToDetach = $opportunity->contacts()
->whereIn('contacts.crm_provider_id', $contactsToRemove)
->pluck('contacts.id')
->toArray();
if (! empty($contactsToDetach)) {
$opportunity->contacts()->detach($contactsToDetach);
$this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_contact_crm_ids' => $contactsToRemove,
'removed_contact_count' => count($contactsToDetach),
]);
}
}
private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void
{
if (empty($contactsToAdd)) {
return;
}
$contactsAdded = [];
foreach ($contactsToAdd as $crmId) {
$id = $contactAssociations[$crmId];
if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {
$contactsAdded[] = $crmId;
}
}
$this->logAddedContacts($opportunity, $contactsAdded);
}
private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool
{
try {
$contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);
if (! $contact) {
return false;
}
return $this->performContactAttachment($opportunity, $contact, $crmId);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [
'opportunity_id' => $opportunity->getId(),
'contact_crm_id' => $crmId,
'error' => $e->getMessage(),
]);
return false;
}
}
private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool
{
try {
$opportunity->contacts()->attach($contact->getId(), [
'crm_provider_id' => $crmId,
]);
return true;
} catch (\Illuminate\Database\QueryException $e) {
if (str_contains($e->getMessage(), 'Duplicate entry')) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [
'contact_id' => $contact->getId(),
'contact_crm_id' => $crmId,
'opportunity_id' => $opportunity->getId(),
]);
return false;
}
throw $e;
}
}
private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void
{
if (! empty($contactsAdded)) {
$this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [
'opportunity_id' => $opportunity->getId(),
'contacts_to_add_count' => count($contactsAdded),
'added_contact_crm_ids' => $contactsAdded,
'added_contacts_count' => count($contactsAdded),
]);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
1
Previous Highlighted Error
Next Highlighted Error
<template>
<WelcomeLayout
title="Account disconnected"
textPosition="center"
:icon="faUnlink"
:class="$style.layout"
>
<div :class="$style.container" v-if="providersLoaded">
<p>
<strong>
It looks like your {{ localProvider.displayName }} account has become
disconnected
</strong>
</p>
<p :class="$style.small">Please re-connect to continue</p>
<p v-if="isInIframe">
We'll open the {{ localProvider.displayName }} authentication in a new
tab. Please return here and refresh the page once complete
</p>
<GoogleLikeButton
v-if="localProvider.viaIntegrationApp && crmTokenLoaded"
as="a"
:key="localProvider.name"
:brand-logo="localProvider.name"
:class="$style.connectButton"
@click="integrationAppOnClick"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
<GoogleLikeButton
v-if="!localProvider.viaIntegrationApp"
as="a"
:key="localProvider.name"
:href="`/auth/redirect/${localProvider.name}`"
:target="target"
:brand-logo="localProvider.name"
:class="$style.connectButton"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
</div>
<BuildInfo />
<KioskBanner />
</WelcomeLayout>
</template>
<script>
import window from "window";
import axios from "axios";
import { faUnlink } from "@fortawesome/pro-regular-svg-icons";
import isInIframe from "@/utils/isInIframe";
import BuildInfo from "@/components/layout/BuildInfo/BuildInfo.vue";
import KioskBanner from "@/components/shared/KioskBanner/KioskBanner.vue";
import WelcomeLayout from "@/components/layout/WelcomeLayout/WelcomeLayout.vue";
import GoogleLikeButton from "@/components/shared/Buttons/GoogleLikeButton.vue";
import { showSnackbarError, normalizeError } from "@/utils/index";
import { IntegrationAppClient } from "@integration-app/sdk";
export default {
name: "ConnectPage",
components: {
BuildInfo,
KioskBanner,
WelcomeLayout,
GoogleLikeButton,
},
data() {
return {
...window.connectData,
crmToken: null,
faUnlink,
isInIframe,
providers: [],
providersLoaded: false,
crmTokenLoaded: false,
};
},
computed: {
localProvider() {
return this.providers.find((e) => e.name === this.provider);
},
target() {
return this.isInIframe ? "_blank" : null;
},
},
created() {
this.getProviders();
},
mounted() {
this.showErrors();
},
watch: {
providersLoaded() {
if (this.providersLoaded) {
this.prepareIntegrationAppConnection();
}
},
},
methods: {
showErrors() {
if (!this.error) return;
showSnackbarError(this.error, undefined, undefined, false);
},
unwrapEntityResponse({ data }) {
return data.map(({ icon, name, displayName, viaIntegrationApp }) => {
return { icon, name, displayName, viaIntegrationApp };
});
},
async getProviders() {
try {
const response = await axios.get("/api/v1/connect-providers");
this.providers = this.unwrapEntityResponse(response);
this.providersLoaded = true;
} catch {
showSnackbarError(
"An error occurred, while loading form data (connect providers).",
);
}
},
async prepareIntegrationAppConnection() {
if (this.localProvider.viaIntegrationApp) {
try {
const response = await axios.get("/api/v1/integration-app-token");
this.crmToken = response.data.token;
this.crmTokenLoaded = true;
} catch (error) {
console.log(error);
showSnackbarError(
`An error occurred while preparing the page.
Try refreshing, if the error persists get in touch with the Jiminny team.`,
);
}
}
},
async integrationAppOnClick() {
console.log('[IntegrationApp] integrationAppOnClick called');
const integrationApp = new IntegrationAppClient({
token: this.crmToken,
});
const connection = await integrationApp
.integration(this.localProvider.name)
.openNewConnection({
showPoweredBy: false,
allowMultipleConnections: false,
}).catch((err) => {
console.log('[IntegrationApp] openNewConnection rejected:', err);
return null;
});
console.log('[IntegrationApp] openNewConnection resolved:', JSON.stringify(connection));
// [IntegrationApp] openNewConnection resolved: {
// "id":"69e0b41a67d0068c2ca0b48e",
// "name":"Zoho CRM",
// "userId":"1ece66c8-feb1-4df1-b321-21607daf4623",
// "tenantId":"69e0b3faef3e7b6248189289",
// "isTest":false,
// "connected":true,
// "state":"READY",
// "errors":[],
// "integrationId":"66fe6c913202f3a165e3c14d",
// "externalAppId":"6671653e7e2d642e4e41b0fa",
// "authOptionKey":"",
// "createdAt":"2026-04-16T10:04:10.420Z",
// "updatedAt":"2026-04-16T10:04:10.575Z",
// "retryAttempts":0,
// "isDeactivated":false
// }
if (connection && connection.disconnected !== true && connection.connected !== false) {
console.log('[IntegrationApp] connection condition matched');
try {
const saveRequest = await axios.post(
"/api/v1/integration-app-connect",
);
if (saveRequest.data && saveRequest.data.success === true) {
/** If all is good refresh the page here */
window.location = "/dashboard";
return;
}
throw new Error(saveRequest.data.message);
} catch (error) {
console.log(error);
showSnackbarError(normalizeError(error));
}
}
},
},
};
</script>
<style module lang="less" src="./connect.less"></style>
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
|
46933
|
NULL
|
0
|
2026-04-17T10:52:44.201703+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776423164201_m2.jpg...
|
Firefox
|
Jiminny — Work
|
True
|
app.dev.jiminny.com/dashboard
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Developers | HubSpot
Developers Workers | Datadog
Developers | HubSpot
Developers | HubSpot
Inbox (1,576) - [EMAIL] - Jiminny Mail
Inbox (1,576) - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
CloudWatch | eu-west-1
CloudWatch | eu-west-1
New Tab
New Tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
[JY-20692] Issue with reconnecting Zoho - Jira
[JY-20692] Issue with reconnecting Zoho - Jira
Jiminny
Jiminny
Close tab
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
AI Chat settings
Close
WORK, Google Account: [EMAIL]
Main menu
New chat
Gemini
PRO
PRO
Conversation with Gemini
Conversation with Gemini
Copy prompt
Edit
You said firefox how to preseve console
You said
firefox how to preseve console
Show more options
Enter a prompt for Gemini
encrypted
Enter a prompt for Gemini
encrypted
Open upload file menu
Tools
Open mode picker
Pro
Stop response
Your Jiminny chats aren’t used to improve our models. Gemini is AI and can make mistakes, including about people.
Your privacy & Gemini Opens in a new window
Your privacy & Gemini
Opens in a new window
Gemini is typing
Summarize page
Summarize page
install
My Recordings
My Recordings
Everyone's Recordings
Everyone's Recordings
No Recordings
Schedule
Schedule
Invite Notetaker
This Week
This Week
My Schedule
My Schedule
No Meetings
Trending this month
Trending this month
Sort by Most played
Sort by
Most played
No Recordings
Live Feed
Live Feed
No Activity
Clear the Web Console output (⌘K, Ctrl+L)
Filter Output
Errors
Warnings
Info
Logs
Debug
CSS
XHR
Requests
Console Settings
Show/hide message details.
GET
[URL_WITH_CREDENTIALS]
[URL_WITH_CREDENTIALS]
[HTTP/2
200
OK 0ms]
Content-Security-Policy: (Report-Only policy) The page’s settings would block the loading of a resource (font-src) at
https://fonts.gstatic.com/s/lato/v25/S6u8w4BMUTPHjxsAUi-qNiXg7eU0.woff2
https://fonts.gstatic.com/s/lato/v25/S6u8w4BMUTPHjxsAUi-qNiXg7eU0.woff2
because it violates the following directive: “font-src 'self'
https://app.dev.jiminny.com
https://app.dev.jiminny.com
https://app.dev.jiminny.com/
https://app.dev.jiminny.com/
https://js.intercomcdn.com
https://js.intercomcdn.com
”
dashboard
dashboard
Content-Security-Policy: (Report-Only policy) The page’s settings would block the loading of a resource (font-src) at
https://fonts.gstatic.com/s/lato/v25/S6u8w4BMUTPHjxsAXC-qNiXg7Q.woff2
https://fonts.gstatic.com/s/lato/v25/S6u8w4BMUTPHjxsAXC-qNiXg7Q.woff2
because it violates the following directive: “font-src 'self'
https://app.dev.jiminny.com
https://app.dev.jiminny.com
https://app.dev.jiminny.com/
https://app.dev.jiminny.com/
https://js.intercomcdn.com
https://js.intercomcdn.com
”
dashboard
dashboard
Content-Security-Policy: (Report-Only policy) The page’s settings would block the loading of a resource (font-src) at
https://fonts.gstatic.com/s/lato/v25/S6u_w4BMUTPHjxsI5wq_FQftx9897sxZ.woff2
https://fonts.gstatic.com/s/lato/v25/S6u_w4BMUTPHjxsI5wq_FQftx9897sxZ.woff2
because it violates the following directive: “font-src 'self'
https://app.dev.jiminny.com
https://app.dev.jiminny.com
https://app.dev.jiminny.com/
https://app.dev.jiminny.com/
https://js.intercomcdn.com
https://js.intercomcdn.com
”
dashboard
dashboard
Content-Security-Policy: (Report-Only policy) The page’s settings would block the loading of a resource (font-src) at
https://fonts.gstatic.com/s/lato/v25/S6u_w4BMUTPHjxsI5wq_Gwftx9897g.woff2
https://fonts.gstatic.com/s/lato/v25/S6u_w4BMUTPHjxsI5wq_Gwftx9897g.woff2
because it violates the following directive: “font-src 'self'
https://app.dev.jiminny.com
https://app.dev.jiminny.com
https://app.dev.jiminny.com/
https://app.dev.jiminny.com/
https://js.intercomcdn.com
https://js.intercomcdn.com
”
dashboard...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"bounds":{"left":0.00234375,"top":0.045138888,"width":0.0890625,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Developers | HubSpot","depth":4,"bounds":{"left":0.0,"top":0.08263889,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Developers | HubSpot","depth":5,"bounds":{"left":0.015625,"top":0.09236111,"width":0.04453125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Inbox (1,576) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.11111111,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox (1,576) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"bounds":{"left":0.015625,"top":0.12083333,"width":0.11445312,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"120216 is your HubSpot Log In Code - integration-account@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.13958333,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"120216 is your HubSpot Log In Code - integration-account@jiminny.com - Jiminny Mail","depth":5,"bounds":{"left":0.015625,"top":0.14930555,"width":0.17734376,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | eu-west-1","depth":4,"bounds":{"left":0.0,"top":0.16805555,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | eu-west-1","depth":5,"bounds":{"left":0.015625,"top":0.17777778,"width":0.048828125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.19652778,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.015625,"top":0.20625,"width":0.017578125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.225,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":5,"bounds":{"left":0.015625,"top":0.23472223,"width":0.1515625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.2534722,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app","depth":5,"bounds":{"left":0.015625,"top":0.26319444,"width":0.17421874,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20692] Issue with reconnecting Zoho - Jira","depth":4,"bounds":{"left":0.0,"top":0.28194445,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20692] Issue with reconnecting Zoho - Jira","depth":5,"bounds":{"left":0.015625,"top":0.29166666,"width":0.09726562,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.31041667,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.015625,"top":0.3201389,"width":0.015625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.07890625,"top":0.31666666,"width":0.009375,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.003125,"top":0.3402778,"width":0.08710937,"height":0.022222223},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.003125,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.01640625,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.029296875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.0421875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.05546875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.2171875,"top":0.047916666,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.23125,"top":0.047916666,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"WORK, Google Account: lukas.kovalik@jiminny.com","depth":12,"bounds":{"left":0.228125,"top":0.090277776,"width":0.015625,"height":0.027777778},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Main menu","depth":12,"bounds":{"left":0.0984375,"top":0.090277776,"width":0.015625,"height":0.027777778},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.1140625,"top":0.09097222,"width":0.03359375,"height":0.02638889},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1171875,"top":0.09513889,"width":0.02578125,"height":0.018055556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"PRO","depth":11,"bounds":{"left":0.20820312,"top":0.09583333,"width":0.017578125,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"PRO","depth":13,"bounds":{"left":0.21132812,"top":0.09791667,"width":0.011328125,"height":0.013194445},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.09335937,"top":0.12847222,"width":0.000390625,"height":0.00069444446},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.09335937,"top":0.13055556,"width":0.14101562,"height":0.022222223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"bounds":{"left":0.1203125,"top":0.15972222,"width":0.015625,"height":0.027777778},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":20,"bounds":{"left":0.1375,"top":0.15972222,"width":0.015625,"height":0.027777778},"role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said firefox how to preseve console","depth":20,"bounds":{"left":0.1609375,"top":0.16805555,"width":0.072265625,"height":0.03888889},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":22,"bounds":{"left":0.09335937,"top":0.1701389,"width":0.0234375,"height":0.014583333},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"firefox how to preseve console","depth":22,"bounds":{"left":0.1609375,"top":0.17083333,"width":0.06289063,"height":0.034027778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show more options","depth":18,"bounds":{"left":0.22382812,"top":0.23194444,"width":0.015625,"height":0.027777778},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXTextArea","text":"Enter a prompt for Gemini\nencrypted","depth":20,"bounds":{"left":0.109375,"top":0.8354167,"width":0.125,"height":0.016666668},"value":"Enter a prompt for Gemini\nencrypted","help_text":"","role_description":"text entry area","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false},{"role":"AXStaticText","text":"Enter a prompt for Gemini","depth":21,"bounds":{"left":0.1171875,"top":0.8354167,"width":0.08203125,"height":0.016666668},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"encrypted","depth":21,"bounds":{"left":0.10820313,"top":0.8354167,"width":0.0078125,"height":0.016666668},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open upload file menu","depth":20,"bounds":{"left":0.1046875,"top":0.86527777,"width":0.015625,"height":0.027777778},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tools","depth":18,"bounds":{"left":0.1234375,"top":0.86527777,"width":0.015625,"height":0.027777778},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open mode picker","depth":20,"bounds":{"left":0.190625,"top":0.8645833,"width":0.03046875,"height":0.027777778},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pro","depth":23,"bounds":{"left":0.196875,"top":0.87222224,"width":0.00859375,"height":0.013194445},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Stop response","depth":19,"bounds":{"left":0.22265625,"top":0.86388886,"width":0.01640625,"height":0.029166667},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Your Jiminny chats aren’t used to improve our models. Gemini is AI and can make mistakes, including about people.","depth":17,"bounds":{"left":0.10039063,"top":0.9097222,"width":0.14296874,"height":0.022222223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Your privacy & Gemini Opens in a new window","depth":17,"bounds":{"left":0.1484375,"top":0.93194443,"width":0.046875,"height":0.011111111},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Your privacy & Gemini","depth":18,"bounds":{"left":0.1484375,"top":0.93194443,"width":0.046875,"height":0.011111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Opens in a new window","depth":19,"bounds":{"left":0.09335937,"top":0.93125,"width":0.05078125,"height":0.011111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini is typing","depth":9,"bounds":{"left":0.09335937,"top":0.07986111,"width":0.0421875,"height":0.011111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Summarize page","depth":7,"bounds":{"left":0.1,"top":0.96319443,"width":0.06289063,"height":0.022222223},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Summarize page","depth":9,"bounds":{"left":0.10664062,"top":0.9673611,"width":0.049609374,"height":0.013888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"install","depth":11,"bounds":{"left":0.23125,"top":0.9451389,"width":0.01875,"height":0.030555556},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"My Recordings","depth":10,"bounds":{"left":0.25703126,"top":0.10277778,"width":0.043359376,"height":0.045833334},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"My Recordings","depth":11,"bounds":{"left":0.2609375,"top":0.12013889,"width":0.035546876,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Everyone's Recordings","depth":10,"bounds":{"left":0.39648438,"top":0.10277778,"width":0.061328124,"height":0.045833334},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Everyone's Recordings","depth":11,"bounds":{"left":0.40039062,"top":0.12013889,"width":0.053515624,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No Recordings","depth":13,"bounds":{"left":0.33945313,"top":0.23125,"width":0.0359375,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Schedule","depth":9,"bounds":{"left":0.2589844,"top":0.2888889,"width":0.028515626,"height":0.023611112},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Schedule","depth":10,"bounds":{"left":0.2589844,"top":0.29305556,"width":0.028515626,"height":0.015277778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Invite Notetaker","depth":10,"bounds":{"left":0.40976563,"top":0.28680557,"width":0.051953126,"height":0.025},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXComboBox","text":"This Week","depth":10,"bounds":{"left":0.2589844,"top":0.3298611,"width":0.196875,"height":0.025694445},"value":"This Week","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"This Week","depth":12,"bounds":{"left":0.26328126,"top":0.33680555,"width":0.02578125,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"My Schedule","depth":10,"bounds":{"left":0.2589844,"top":0.3625,"width":0.196875,"height":0.025694445},"value":"My Schedule","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"My Schedule","depth":12,"bounds":{"left":0.26328126,"top":0.36944443,"width":0.03125,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No Meetings","depth":12,"bounds":{"left":0.34179688,"top":0.48055556,"width":0.03125,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Trending this month","depth":9,"bounds":{"left":0.26289064,"top":0.56319445,"width":0.054296874,"height":0.016666668},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Trending this month","depth":10,"bounds":{"left":0.26289064,"top":0.56458336,"width":0.054296874,"height":0.013888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Sort by Most played","depth":9,"bounds":{"left":0.3921875,"top":0.55833334,"width":0.059765626,"height":0.025694445},"value":"Sort by Most played","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Sort by","depth":10,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Most played","depth":11,"bounds":{"left":0.39648438,"top":0.56527776,"width":0.0296875,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No Recordings","depth":12,"bounds":{"left":0.33945313,"top":0.68333334,"width":0.0359375,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Live Feed","depth":9,"bounds":{"left":0.26289064,"top":0.7590278,"width":0.02578125,"height":0.016666668},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Live Feed","depth":10,"bounds":{"left":0.26289064,"top":0.7604167,"width":0.02578125,"height":0.013888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No Activity","depth":12,"bounds":{"left":0.34375,"top":0.87222224,"width":0.02734375,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Clear the Web Console output (⌘K, Ctrl+L)","depth":15,"bounds":{"left":0.46328124,"top":0.068055555,"width":0.01015625,"height":0.013888889},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXTextField","text":"Filter Output","depth":15,"bounds":{"left":0.47578126,"top":0.065972224,"width":0.36132812,"height":0.018055556},"role_description":"search text field","subrole":"AXSearchField","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Errors","depth":15,"bounds":{"left":0.8417969,"top":0.068055555,"width":0.0171875,"height":0.013888889},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Warnings","depth":15,"bounds":{"left":0.85976565,"top":0.068055555,"width":0.023828125,"height":0.013888889},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Info","depth":15,"bounds":{"left":0.884375,"top":0.068055555,"width":0.0125,"height":0.013888889},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Logs","depth":15,"bounds":{"left":0.89765626,"top":0.068055555,"width":0.014453125,"height":0.013888889},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Debug","depth":15,"bounds":{"left":0.9128906,"top":0.068055555,"width":0.01796875,"height":0.013888889},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"CSS","depth":15,"bounds":{"left":0.9359375,"top":0.068055555,"width":0.01328125,"height":0.013888889},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"XHR","depth":15,"bounds":{"left":0.95,"top":0.068055555,"width":0.013671875,"height":0.013888889},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Requests","depth":15,"bounds":{"left":0.9644531,"top":0.068055555,"width":0.023828125,"height":0.013888889},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Console Settings","depth":15,"bounds":{"left":0.9898437,"top":0.06527778,"width":0.01015625,"height":0.019444445},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show/hide message details.","depth":17,"bounds":{"left":0.46367186,"top":0.08680555,"width":0.009375,"height":0.011111111},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"GET","depth":21,"bounds":{"left":0.47421876,"top":0.0875,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://app.dev.jiminny.com/dashboard","depth":20,"bounds":{"left":0.484375,"top":0.0875,"width":0.4519531,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://app.dev.jiminny.com/dashboard","depth":21,"bounds":{"left":0.484375,"top":0.0875,"width":0.095703125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[HTTP/1.1","depth":21,"bounds":{"left":0.9386719,"top":0.0875,"width":0.02578125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":22,"bounds":{"left":0.9652344,"top":0.0875,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0ms]","depth":21,"bounds":{"left":0.97382814,"top":0.0875,"width":0.015625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show/hide message details.","depth":17,"bounds":{"left":0.4703125,"top":0.10138889,"width":0.00625,"height":0.011111111},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Content-Security-Policy warnings","depth":20,"bounds":{"left":0.4765625,"top":0.10208333,"width":0.0828125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":21,"bounds":{"left":0.5660156,"top":0.103472225,"width":0.00234375,"height":0.007638889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show/hide message details.","depth":17,"bounds":{"left":0.46367186,"top":0.11597222,"width":0.009375,"height":0.011111111},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"GET","depth":21,"bounds":{"left":0.47421876,"top":0.11666667,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400;1,700&family=Roboto:wght@500&display=swap","depth":20,"bounds":{"left":0.484375,"top":0.11666667,"width":0.4519531,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400;1,700&family=Roboto:wght@500&display=swap","depth":21,"bounds":{"left":0.484375,"top":0.11666667,"width":0.29726562,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[HTTP/2","depth":21,"bounds":{"left":0.9386719,"top":0.11666667,"width":0.020703126,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":22,"bounds":{"left":0.96015626,"top":0.11666667,"width":0.0078125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"OK 0ms]","depth":21,"bounds":{"left":0.96875,"top":0.11666667,"width":0.020703126,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Content-Security-Policy: (Report-Only policy) The page’s settings would block the loading of a resource (font-src) at","depth":21,"bounds":{"left":0.471875,"top":0.13125,"width":0.30507812,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://fonts.gstatic.com/s/lato/v25/S6u8w4BMUTPHjxsAUi-qNiXg7eU0.woff2","depth":21,"bounds":{"left":0.7769531,"top":0.13125,"width":0.18359375,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://fonts.gstatic.com/s/lato/v25/S6u8w4BMUTPHjxsAUi-qNiXg7eU0.woff2","depth":22,"bounds":{"left":0.7769531,"top":0.13125,"width":0.18359375,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"because it violates the following directive: “font-src 'self'","depth":21,"bounds":{"left":0.471875,"top":0.13125,"width":0.4910156,"height":0.019444445},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://app.dev.jiminny.com","depth":21,"bounds":{"left":0.63203126,"top":0.14097223,"width":0.06992187,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://app.dev.jiminny.com","depth":22,"bounds":{"left":0.63203126,"top":0.14097223,"width":0.06992187,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://app.dev.jiminny.com/","depth":21,"bounds":{"left":0.7042969,"top":0.14097223,"width":0.07265625,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://app.dev.jiminny.com/","depth":22,"bounds":{"left":0.7042969,"top":0.14097223,"width":0.07265625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://js.intercomcdn.com","depth":21,"bounds":{"left":0.7792969,"top":0.14097223,"width":0.0671875,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://js.intercomcdn.com","depth":22,"bounds":{"left":0.7792969,"top":0.14097223,"width":0.0671875,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"”","depth":21,"bounds":{"left":0.84648436,"top":0.14097223,"width":0.002734375,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"dashboard","depth":20,"bounds":{"left":0.96601564,"top":0.13125,"width":0.0234375,"height":0.009722223},"help_text":"View source in Debugger → https://app.dev.jiminny.com/dashboard","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"dashboard","depth":22,"bounds":{"left":0.96601564,"top":0.13125,"width":0.0234375,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Content-Security-Policy: (Report-Only policy) The page’s settings would block the loading of a resource (font-src) at","depth":21,"bounds":{"left":0.471875,"top":0.15555556,"width":0.30507812,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://fonts.gstatic.com/s/lato/v25/S6u8w4BMUTPHjxsAXC-qNiXg7Q.woff2","depth":21,"bounds":{"left":0.7769531,"top":0.15555556,"width":0.178125,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://fonts.gstatic.com/s/lato/v25/S6u8w4BMUTPHjxsAXC-qNiXg7Q.woff2","depth":22,"bounds":{"left":0.7769531,"top":0.15555556,"width":0.178125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"because it violates the following directive: “font-src 'self'","depth":21,"bounds":{"left":0.471875,"top":0.15555556,"width":0.4859375,"height":0.019444445},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://app.dev.jiminny.com","depth":21,"bounds":{"left":0.63203126,"top":0.16527778,"width":0.06992187,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://app.dev.jiminny.com","depth":22,"bounds":{"left":0.63203126,"top":0.16527778,"width":0.06992187,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://app.dev.jiminny.com/","depth":21,"bounds":{"left":0.7042969,"top":0.16527778,"width":0.07265625,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://app.dev.jiminny.com/","depth":22,"bounds":{"left":0.7042969,"top":0.16527778,"width":0.07265625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://js.intercomcdn.com","depth":21,"bounds":{"left":0.7792969,"top":0.16527778,"width":0.0671875,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://js.intercomcdn.com","depth":22,"bounds":{"left":0.7792969,"top":0.16527778,"width":0.0671875,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"”","depth":21,"bounds":{"left":0.84648436,"top":0.16527778,"width":0.002734375,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"dashboard","depth":20,"bounds":{"left":0.96601564,"top":0.15555556,"width":0.0234375,"height":0.009722223},"help_text":"View source in Debugger → https://app.dev.jiminny.com/dashboard","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"dashboard","depth":22,"bounds":{"left":0.96601564,"top":0.15555556,"width":0.0234375,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Content-Security-Policy: (Report-Only policy) The page’s settings would block the loading of a resource (font-src) at","depth":21,"bounds":{"left":0.471875,"top":0.17986111,"width":0.30507812,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://fonts.gstatic.com/s/lato/v25/S6u_w4BMUTPHjxsI5wq_FQftx9897sxZ.woff2","depth":21,"bounds":{"left":0.471875,"top":0.17986111,"width":0.4910156,"height":0.019444445},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://fonts.gstatic.com/s/lato/v25/S6u_w4BMUTPHjxsI5wq_FQftx9897sxZ.woff2","depth":22,"bounds":{"left":0.471875,"top":0.17986111,"width":0.4910156,"height":0.019444445},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"because it violates the following directive: “font-src 'self'","depth":21,"bounds":{"left":0.4796875,"top":0.18958333,"width":0.16289063,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://app.dev.jiminny.com","depth":21,"bounds":{"left":0.6425781,"top":0.18958333,"width":0.06953125,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://app.dev.jiminny.com","depth":22,"bounds":{"left":0.6425781,"top":0.18958333,"width":0.06953125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://app.dev.jiminny.com/","depth":21,"bounds":{"left":0.71484375,"top":0.18958333,"width":0.072265625,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://app.dev.jiminny.com/","depth":22,"bounds":{"left":0.71484375,"top":0.18958333,"width":0.072265625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://js.intercomcdn.com","depth":21,"bounds":{"left":0.78984374,"top":0.18958333,"width":0.0671875,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://js.intercomcdn.com","depth":22,"bounds":{"left":0.78984374,"top":0.18958333,"width":0.0671875,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"”","depth":21,"bounds":{"left":0.8570312,"top":0.18958333,"width":0.00234375,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"dashboard","depth":20,"bounds":{"left":0.96601564,"top":0.17986111,"width":0.0234375,"height":0.009722223},"help_text":"View source in Debugger → https://app.dev.jiminny.com/dashboard","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"dashboard","depth":22,"bounds":{"left":0.96601564,"top":0.17986111,"width":0.0234375,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Content-Security-Policy: (Report-Only policy) The page’s settings would block the loading of a resource (font-src) at","depth":21,"bounds":{"left":0.471875,"top":0.20416667,"width":0.30507812,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://fonts.gstatic.com/s/lato/v25/S6u_w4BMUTPHjxsI5wq_Gwftx9897g.woff2","depth":21,"bounds":{"left":0.471875,"top":0.20416667,"width":0.4910156,"height":0.019444445},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://fonts.gstatic.com/s/lato/v25/S6u_w4BMUTPHjxsI5wq_Gwftx9897g.woff2","depth":22,"bounds":{"left":0.471875,"top":0.20416667,"width":0.4910156,"height":0.019444445},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"because it violates the following directive: “font-src 'self'","depth":21,"bounds":{"left":0.47460938,"top":0.21388888,"width":0.1625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://app.dev.jiminny.com","depth":21,"bounds":{"left":0.6371094,"top":0.21388888,"width":0.06992187,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://app.dev.jiminny.com","depth":22,"bounds":{"left":0.6371094,"top":0.21388888,"width":0.06992187,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://app.dev.jiminny.com/","depth":21,"bounds":{"left":0.7097656,"top":0.21388888,"width":0.072265625,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://app.dev.jiminny.com/","depth":22,"bounds":{"left":0.7097656,"top":0.21388888,"width":0.072265625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://js.intercomcdn.com","depth":21,"bounds":{"left":0.7847656,"top":0.21388888,"width":0.0671875,"height":0.009722223},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://js.intercomcdn.com","depth":22,"bounds":{"left":0.7847656,"top":0.21388888,"width":0.0671875,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"”","depth":21,"bounds":{"left":0.85195315,"top":0.21388888,"width":0.00234375,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"dashboard","depth":20,"bounds":{"left":0.96601564,"top":0.20416667,"width":0.0234375,"height":0.009722223},"help_text":"View source in Debugger → https://app.dev.jiminny.com/dashboard","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-3520186457033888925
|
4293020297422048420
|
visual_change
|
accessibility
|
NULL
|
Workers | Datadog
Developers | HubSpot
Developers Workers | Datadog
Developers | HubSpot
Developers | HubSpot
Inbox (1,576) - [EMAIL] - Jiminny Mail
Inbox (1,576) - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
CloudWatch | eu-west-1
CloudWatch | eu-west-1
New Tab
New Tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
[JY-20692] Issue with reconnecting Zoho - Jira
[JY-20692] Issue with reconnecting Zoho - Jira
Jiminny
Jiminny
Close tab
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
AI Chat settings
Close
WORK, Google Account: [EMAIL]
Main menu
New chat
Gemini
PRO
PRO
Conversation with Gemini
Conversation with Gemini
Copy prompt
Edit
You said firefox how to preseve console
You said
firefox how to preseve console
Show more options
Enter a prompt for Gemini
encrypted
Enter a prompt for Gemini
encrypted
Open upload file menu
Tools
Open mode picker
Pro
Stop response
Your Jiminny chats aren’t used to improve our models. Gemini is AI and can make mistakes, including about people.
Your privacy & Gemini Opens in a new window
Your privacy & Gemini
Opens in a new window
Gemini is typing
Summarize page
Summarize page
install
My Recordings
My Recordings
Everyone's Recordings
Everyone's Recordings
No Recordings
Schedule
Schedule
Invite Notetaker
This Week
This Week
My Schedule
My Schedule
No Meetings
Trending this month
Trending this month
Sort by Most played
Sort by
Most played
No Recordings
Live Feed
Live Feed
No Activity
Clear the Web Console output (⌘K, Ctrl+L)
Filter Output
Errors
Warnings
Info
Logs
Debug
CSS
XHR
Requests
Console Settings
Show/hide message details.
GET
[URL_WITH_CREDENTIALS]
[URL_WITH_CREDENTIALS]
[HTTP/2
200
OK 0ms]
Content-Security-Policy: (Report-Only policy) The page’s settings would block the loading of a resource (font-src) at
https://fonts.gstatic.com/s/lato/v25/S6u8w4BMUTPHjxsAUi-qNiXg7eU0.woff2
https://fonts.gstatic.com/s/lato/v25/S6u8w4BMUTPHjxsAUi-qNiXg7eU0.woff2
because it violates the following directive: “font-src 'self'
https://app.dev.jiminny.com
https://app.dev.jiminny.com
https://app.dev.jiminny.com/
https://app.dev.jiminny.com/
https://js.intercomcdn.com
https://js.intercomcdn.com
”
dashboard
dashboard
Content-Security-Policy: (Report-Only policy) The page’s settings would block the loading of a resource (font-src) at
https://fonts.gstatic.com/s/lato/v25/S6u8w4BMUTPHjxsAXC-qNiXg7Q.woff2
https://fonts.gstatic.com/s/lato/v25/S6u8w4BMUTPHjxsAXC-qNiXg7Q.woff2
because it violates the following directive: “font-src 'self'
https://app.dev.jiminny.com
https://app.dev.jiminny.com
https://app.dev.jiminny.com/
https://app.dev.jiminny.com/
https://js.intercomcdn.com
https://js.intercomcdn.com
”
dashboard
dashboard
Content-Security-Policy: (Report-Only policy) The page’s settings would block the loading of a resource (font-src) at
https://fonts.gstatic.com/s/lato/v25/S6u_w4BMUTPHjxsI5wq_FQftx9897sxZ.woff2
https://fonts.gstatic.com/s/lato/v25/S6u_w4BMUTPHjxsI5wq_FQftx9897sxZ.woff2
because it violates the following directive: “font-src 'self'
https://app.dev.jiminny.com
https://app.dev.jiminny.com
https://app.dev.jiminny.com/
https://app.dev.jiminny.com/
https://js.intercomcdn.com
https://js.intercomcdn.com
”
dashboard
dashboard
Content-Security-Policy: (Report-Only policy) The page’s settings would block the loading of a resource (font-src) at
https://fonts.gstatic.com/s/lato/v25/S6u_w4BMUTPHjxsI5wq_Gwftx9897g.woff2
https://fonts.gstatic.com/s/lato/v25/S6u_w4BMUTPHjxsI5wq_Gwftx9897g.woff2
because it violates the following directive: “font-src 'self'
https://app.dev.jiminny.com
https://app.dev.jiminny.com
https://app.dev.jiminny.com/
https://app.dev.jiminny.com/
https://js.intercomcdn.com
https://js.intercomcdn.com
”
dashboard...
|
46932
|
|
46925
|
NULL
|
0
|
2026-04-17T10:52:12.907193+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776423132907_m1.jpg...
|
Firefox
|
Jiminny — Work
|
True
|
app.dev.jiminny.com/dashboard
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Developers | HubSpot
Developers Workers | Datadog
Developers | HubSpot
Developers | HubSpot
Inbox (1,576) - [EMAIL] - Jiminny Mail
Inbox (1,576) - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
CloudWatch | eu-west-1
CloudWatch | eu-west-1
New Tab
New Tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
[JY-20692] Issue with reconnecting Zoho - Jira
[JY-20692] Issue with reconnecting Zoho - Jira
Jiminny
Jiminny
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Developers | HubSpot","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Developers | HubSpot","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Inbox (1,576) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox (1,576) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"120216 is your HubSpot Log In Code - integration-account@jiminny.com - Jiminny Mail","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"120216 is your HubSpot Log In Code - integration-account@jiminny.com - Jiminny Mail","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | eu-west-1","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | eu-west-1","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20692] Issue with reconnecting Zoho - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20692] Issue with reconnecting Zoho - Jira","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Jiminny","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-4465696449359315208
|
-2648858699392996788
|
click
|
accessibility
|
NULL
|
Workers | Datadog
Developers | HubSpot
Developers Workers | Datadog
Developers | HubSpot
Developers | HubSpot
Inbox (1,576) - [EMAIL] - Jiminny Mail
Inbox (1,576) - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
120216 is your HubSpot Log In Code - [EMAIL] - Jiminny Mail
CloudWatch | eu-west-1
CloudWatch | eu-west-1
New Tab
New Tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
fix-cache-for-business-processes by ilian-jiminny · Pull Request #11985 · jiminny/app
[JY-20692] Issue with reconnecting Zoho - Jira
[JY-20692] Issue with reconnecting Zoho - Jira
Jiminny
Jiminny
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)...
|
NULL
|
|
46773
|
NULL
|
0
|
2026-04-17T10:47:39.696274+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776422859696_m1.jpg...
|
PhpStorm
|
faVsco.js – SF [jiminny@localhost]
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
FirefoxFileEditViewHistoryBookmarksProfilesToolsWi FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelplall= Support Daily - in 1 h 13 m100% <47APP (-zsh)X4-zshDOCKER• ₴1DEV (docker)• *2APP (-zsh)X3-zsh./public/vue-assets/assets/ondemand-mxFxJiHI.js./public/vue-assets/assets/CrmLink-DKYsnHnx.js./public/vue-assets/assets/liquor-tree-COUefof4.js./public/vue-assets/assets/DealRiskList-D7gUVql5.js:./public/vue-assets/assets/AskAnything-DgI0NfRA.js:./public/vue-assets/assets/lib-CwM9toD2.js./public/vue-assets/assets/AppFormField-BqQiLGFF.js./public/vue-assets/assets/deal-view-D4YlPwr_.js:./public/vue-assets/assets/exports-D1lmea40.js../public/vue-assets/assets/playlists-BWAdERcJ.js../public/vue-assets/assets/callScoringTemplates-zeRn40ul.js../public/vue-assets/assets/_copy0bject-USkOnlaQ.js../public/vue-assets/assets/pusher-znYCfz7U.js./public/vue-assets/assets/onboard-CyAPGoFk.js./public/vue-assets/assets/StatusBadge-DNHiCr2i.js./public/vue-assets/assets/kiosk-dfcpodo5.js./public/vue-assets/assets/preload-helper-DCvhahzG.js../public/vue-assets/assets/deal-insights-BVnPilVP.js../public/vue-assets/assets/ListView-DJD6SV4A.js:./public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js:./public/vue-assets/assets/WelcomeLayout-B6wd32HG.js../public/vue-assets/assets/dashboard-CsD0iLAi.js:./public/vue-assets/assets/emoji-input-CSq87OVy.js../public/vue-assets/assets/AppButton-D3qMdODr.js../public/vue-assets/assets/sentry-B3B12M60.js:./public/vue-assets/assets/OrgSettingsLayout-DatDldIe.js./public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js./public/vue-assets/assets/playback-D1gm80qQ.js./public/vue-assets/assets/index.module-Bjlhgfdl.js./public/vue-assets/assets/intl-tel-input-BW4mv40Q.js:./public/vue-assets/assets/team-insights-DRugjYCA.js../public/vue-assets/assets/popper-CQwVcrX4.js../public/vue-assets/assets/PhoneField-CwCIoAYm.js./public/vue-assets/assets/live-CxSmZv7h.js../public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BN0485xV.js../public/vue-assets/assets/index-Cp0YOK4U.js../public/vue-assets/assets/logged-in-layout-CE9ox17M.js• ₴526.88kB27.91kB30.75kB34.39kB39.50kB39.69kB41.91kB43.22kB47.84kB48.28kB55.13kB61.28kB62.98kB63.11kB64.66kB79.60kB82.59kB94.84kB115.71kB117.59 kВ120.67kB128.71 kB129.28 kB133.44 kB164.28kB176.33kB180.40kB198.79kB218.14kB264.94 kВ298.57kB307.13kB343.99kB367.43 kB689.63kB825.23 kB1,402.70 kB• built in 26.01s[plugin builtin:vite-reporter](!) Some chunksare larger than 500 kB after minification. Consider:- Using dynamic import() to code-split the application- Use build.rolldown0ptions.output.codeSplitting to improve chunking: [URL_WITH_CREDENTIALS] 88APP...
|
NULL
|
-5025856933011940221
|
NULL
|
click
|
ocr
|
NULL
|
FirefoxFileEditViewHistoryBookmarksProfilesToolsWi FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelplall= Support Daily - in 1 h 13 m100% <47APP (-zsh)X4-zshDOCKER• ₴1DEV (docker)• *2APP (-zsh)X3-zsh./public/vue-assets/assets/ondemand-mxFxJiHI.js./public/vue-assets/assets/CrmLink-DKYsnHnx.js./public/vue-assets/assets/liquor-tree-COUefof4.js./public/vue-assets/assets/DealRiskList-D7gUVql5.js:./public/vue-assets/assets/AskAnything-DgI0NfRA.js:./public/vue-assets/assets/lib-CwM9toD2.js./public/vue-assets/assets/AppFormField-BqQiLGFF.js./public/vue-assets/assets/deal-view-D4YlPwr_.js:./public/vue-assets/assets/exports-D1lmea40.js../public/vue-assets/assets/playlists-BWAdERcJ.js../public/vue-assets/assets/callScoringTemplates-zeRn40ul.js../public/vue-assets/assets/_copy0bject-USkOnlaQ.js../public/vue-assets/assets/pusher-znYCfz7U.js./public/vue-assets/assets/onboard-CyAPGoFk.js./public/vue-assets/assets/StatusBadge-DNHiCr2i.js./public/vue-assets/assets/kiosk-dfcpodo5.js./public/vue-assets/assets/preload-helper-DCvhahzG.js../public/vue-assets/assets/deal-insights-BVnPilVP.js../public/vue-assets/assets/ListView-DJD6SV4A.js:./public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js:./public/vue-assets/assets/WelcomeLayout-B6wd32HG.js../public/vue-assets/assets/dashboard-CsD0iLAi.js:./public/vue-assets/assets/emoji-input-CSq87OVy.js../public/vue-assets/assets/AppButton-D3qMdODr.js../public/vue-assets/assets/sentry-B3B12M60.js:./public/vue-assets/assets/OrgSettingsLayout-DatDldIe.js./public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js./public/vue-assets/assets/playback-D1gm80qQ.js./public/vue-assets/assets/index.module-Bjlhgfdl.js./public/vue-assets/assets/intl-tel-input-BW4mv40Q.js:./public/vue-assets/assets/team-insights-DRugjYCA.js../public/vue-assets/assets/popper-CQwVcrX4.js../public/vue-assets/assets/PhoneField-CwCIoAYm.js./public/vue-assets/assets/live-CxSmZv7h.js../public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BN0485xV.js../public/vue-assets/assets/index-Cp0YOK4U.js../public/vue-assets/assets/logged-in-layout-CE9ox17M.js• ₴526.88kB27.91kB30.75kB34.39kB39.50kB39.69kB41.91kB43.22kB47.84kB48.28kB55.13kB61.28kB62.98kB63.11kB64.66kB79.60kB82.59kB94.84kB115.71kB117.59 kВ120.67kB128.71 kB129.28 kB133.44 kB164.28kB176.33kB180.40kB198.79kB218.14kB264.94 kВ298.57kB307.13kB343.99kB367.43 kB689.63kB825.23 kB1,402.70 kB• built in 26.01s[plugin builtin:vite-reporter](!) Some chunksare larger than 500 kB after minification. Consider:- Using dynamic import() to code-split the application- Use build.rolldown0ptions.output.codeSplitting to improve chunking: [URL_WITH_CREDENTIALS] 88APP...
|
NULL
|
|
46772
|
NULL
|
0
|
2026-04-17T10:47:39.299823+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776422859299_m2.jpg...
|
PhpStorm
|
faVsco.js – SF [jiminny@localhost]
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20692-fix-integration- Project: faVsco.js, menu
JY-20692-fix-integration-app-[API_KEY], menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
cachedStages
New Line
Match Case...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.03046875,"top":0.017361112,"width":0.0453125,"height":0.022222223},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20692-fix-integration-app-token-auth-response-change, menu","depth":5,"bounds":{"left":0.07578125,"top":0.017361112,"width":0.15898438,"height":0.022222223},"help_text":"Git Branch: JY-20692-fix-integration-app-token-auth-response-change","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.78515625,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsCommandTest","depth":6,"bounds":{"left":0.803125,"top":0.017361112,"width":0.09765625,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsCommandTest'","depth":6,"bounds":{"left":0.9007813,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsCommandTest'","depth":6,"bounds":{"left":0.9140625,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9273437,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96015626,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9734375,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9867188,"top":0.017361112,"width":0.013281226,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show Replace Field","depth":4,"bounds":{"left":0.12382813,"top":0.22083333,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Search History","depth":3,"bounds":{"left":0.13867188,"top":0.22013889,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"cachedStages","depth":4,"bounds":{"left":0.1515625,"top":0.22013889,"width":0.0515625,"height":0.013888889},"value":"cachedStages","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"bounds":{"left":0.21367188,"top":0.22013889,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Match Case","depth":3,"bounds":{"left":0.22539063,"top":0.22013889,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-7665353928652150759
|
-7771723620485485626
|
visual_change
|
hybrid
|
NULL
|
Project: faVsco.js, menu
JY-20692-fix-integration- Project: faVsco.js, menu
JY-20692-fix-integration-app-[API_KEY], menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
cachedStages
New Line
Match Case
PhpStormFileEditViewNavigateCodeLaravelRefactorFV faVsco.s vT° JY-20692-fix-integration-app-[API_KEY].php-cs-fixer.dist.phpphp.phpstorm.meta.php=.phpunit.result.cacheE.prettierignoreE windsurfrulespip _lde_nelper.pnppnpce neloermoces.onepnp aruisani composer.isonO composer.lockdependency-checker.jsonO dev.jsonE ids.txt=infection.json.distM-INSALL.mdM+ INTERNAL WEBHOOK SETUPjiminny_storageM+ licenses.mdM MakefileOpackage-lock.json= phpstan.neon.dist= phpstan-baseline.neon< phpunit.xmlTa raw_sqLquery.sqlM+ README.mdsonar-project.properties= test.py<> Untitled Diagram.xmlI5 vetur.config.jsM-+ WEbnOOK HILIEKING_IMPLE› Iib External Librariesv E® Scratches and Consolesv D Database Consoles© AutomatedReportsService.php© SendReportJob.phpC ReportController.phplokenbullaer.onoc leamsetuocontroller.onp• Filesystem.php© Team.php© RequestGenerateReportJob.phpT InteractsWithPivotTable.phg© CreateHeldActivityEvent.phpOpportunitySyncTrait.phpC OpportunityUpdated.php© SendReportMailJob.phpphp api.php© TrackProviderInstalledEvent.phpC Opportunity.phpC OpportunityStageUpdated.phpc) FventService?rovider.onn© RunOpportunityAiAnalysis.php( ImportBatchJobTrait.php© OpportunityPendingAiAnalysisAfterStageChanged.php© ProcessAiAutomationAnalysisResults.php(C) Service.php© ImportOpportunityBatch.php101819281030cachedStagesCcW165trait OpportunitySyncTraitA33 X2 X19 A Y 166private function resolveForecastCategory(?string $forecastCategory): stringf...1672 usages=169private function importExternalFieldData(array $properties, int $opportunitylra, 170171$crmFields = $this->getOpportunitySyncableFieldsO;=172$this->import0pportunityCrmFieldData(Sproperties, $crmFields, Sopportunityi17310331034103510481049105010511052Lusaeesprivate function importOpportunityContacts(Opportunity Sopportunity, array $as:177/*** Sync opportunity contacts using differential approach* This compares current vs new associations and only makes necessary changes=175176=1791801811821 usageprivate function syncOpportunityContactsDifferential(Opportunity $opportunity,1070private function getcurrentContactCrmIds(Opportunity $opportunity): array{...7 187A console [EU]4 DEAL RISKS [EU]A DI [EU]dEuleUv& jiminny@localhost& console yiminny @localrA DI [jiminny@localhost]4 HS_local [jiminny@localA SF [jiminny@localhost]A zoho_dev [jiminny@loceY A PRODД0/110771078107910801 usageprivate function logContactAssociationChanges(Opportunity Sopportunity,array $currentContactCrmIds,array $contactAssociations,array $contactsToAdd,array +concacustokemove): void {...}-189190191192—193services+,o,cv D DatabasevdtuA console 1 s 59 msu crm_contigurations 1s 391 msv & minnvo ocanost4SF4 HS_localV A PROD4 console 1 s 806 msV A STAGING& consoleOutputiD # 1695 X3 rowsS9T2_ ICIsImtpzC16l7VmNwrhzGFKLT0wzDKtNDLKNy04ZWI2LTAZNmNLM.FQG0A• expires Y177633617617296136151776509176I refresh_token_expires Y3 rows retrieved starting from 1 in 430 ms (execution: 40 ms, fetching: 390 ms)lablf Support Daily • in 1h 13 mAAutomatedRenortsCommandTest100% 145Fri 17 Apr 13:47:38= custom.log< Hs local liminnyalocalnostA console [PROD]E laravel.logA SF [jiminny@localhost] XC scratch_1.jsonA console (EU]© CrmEntityRepository.phpA console [STAGING]V connect.vuefi crm_configurations (EU]V Onboard.vueCascadeAutomated Report RetrDebugging OpportunitUpdate Connection Loconnect.vue tront-end/src/components/connecXAutovPlaygroundSELECT * FROM automated_reports order by za desc;SELECT * FROMautomated_report_results order by id desc;select * from activity_searches where user_id = 143;select * from ask_anything_prompts;ma liminnvvM09412 X2 X4AVnothing is therew"cant see log'sSELECT * FROM groups WHERE id = 1439;SELECT * FROM users WHERE group_id = 1439;select * from permissions; # 158select * from roles;select * from permission_role,The console is full of CSP warnings and errors but none of your [IntegrationApp] logs. This means intedrat1onApponcL1ck Is never belno called at all — the burton click isn't reachine that tunction.Check:1. Is the button actually calling integrationApp0nClick? - the click handler might be bound to adifferent method or the component re-rendered2. Is the frontend bundle rebuilt? — you may be running a stale build that doesn't include your changesselect * from teams where id = 1;select * from groups g JOIN playbooks p 1.n<->1: on g.playbook_id = p.id where g.team_id = 1;select * from groups where id = 28;select * from playbooks where team_id = 1;select * from playbooks where id = 179;select * from playbook_categories where id = 1391;select * from users where id = 143;select * from crm_profiles where user_id = 143;select * from activities where crm_configuration_id = 39 and type = 'conference"and crm_provider_id IS NOT NULL ORDER by id desc;select * from activities where id = 422003; # 00U0400000pB6FpMACSearched.intecrationAoo@nClickin tront-enc/src/cents/connect/connect.vue (2)The binding looks correct. The most likely issue is a stale build - your changes aren't compiled yet. Add aquick test: put a console.log before the IntegrationAppClient instantiation that's impossible to miss:Reao connectvue :158-|44• connect.vueNow after saving, hard refresh the browser ( Cmd+Shift+R ) to bust the cache. If you still don't see [Integratlonado incecrac1onApouncllck called arter cllckine tne outton, tne dev server nasn't olckea up tnechanges or the click event isn't reaching this component (e.g. another element is intercepting it).SELECT ar.ig, ar.wuig, ar.mediantxpe, ar.status, a.txReFROM automated_report_results arJOIN automated_reports a ON a.id = ar.repontaidWHERE a.IxRe = 'ask_iiminnx'LIMIT 10;select * from teams where id = 3143;select * from crm_configurations where id = 500;select * from users where name = 'Integration Account'; # 1695SELECT * FROM social_accounts WHERE sociable_id = 1695;etI to CascadeAsk anything (284L)+ <> Code Claude Sonnet 4.6. provider Yu cooqle<null> zoom-phone<null> integration-app! state Ytuu-retresnfull-refreshfull-refreshWauth_scope Yopenid https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/calendar http.phone:read:admin user:read:admin<null>• :-CSV vДOB,retry_after Y00 cr2020<nul><null>20242026-W Windsurf Teams 193:1 0 UTF-84 spaces...
|
46771
|
|
46617
|
NULL
|
0
|
2026-04-17T10:42:18.997032+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776422538997_m1.jpg...
|
iTerm2
|
APP (-zsh)
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
info Visit [URL_WITH_CREDENTIALS] ~/jiminny/app/fr info Visit [URL_WITH_CREDENTIALS] ~/jiminny/app/front-end (JY-20692-fix-integration-app-[API_KEY]) $ yanrn build
zsh: command not found: yanrn
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-[API_KEY]) $ yarn build
[dotenv@17.3.1] injecting env (0) from .env -- tip: ⚡️ secrets for agents: [URL_WITH_CREDENTIALS] ~/jiminny/app/front-end (JY-20692-fix-integration-app-[API_KEY]) $ yarn install
➤ YN0000: · Yarn 4.12.0
➤ YN0000: ┌ Resolution step
➤ YN0000: └ Completed
➤ YN0000: ┌ Post-resolution validation
➤ YN0002: │ root-workspace-0b6124@workspace:. doesn't provide @testing-library/dom (pea6ced), requested by @testing-library/user-event.
➤ YN0002: │ root-workspace-0b6124@workspace:. doesn't provide noty (p5bdb0d), requested by vue-components.
➤ YN0086: │ Some peer dependencies are incorrectly met by your project; run yarn explain peer-requirements <hash> for details, where <hash> is the six-letter p-prefixed code.
➤ YN0086: │ Some peer dependencies are incorrectly met by dependencies; run yarn explain peer-requirements for details.
➤ YN0000: └ Completed
➤ YN0000: ┌ Fetch step
➤ YN0000: └ Completed in 1s 655ms
➤ YN0000: ┌ Link step
➤ YN0008: │ root-workspace-0b6124@workspace:. must be rebuilt because its dependency tree changed
➤ YN0000: └ Completed in 0s 969ms
➤ YN0000: · Done with warnings in 3s 14ms
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-[API_KEY]) $ yarn build
[dotenv@17.3.1] injecting env (0) from .env -- tip: ⚙️ override existing env vars with { override: true }
vite v8.0.0 building client environment for production...
✓ 4656 modules transformed.
[sentry-vite-plugin] Warning: No auth token provided. Will not create release. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/
[sentry-vite-plugin] Warning: No auth token provided. Will not upload source maps. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/
computing gzip size...
../public/vue-assets/index.html 4.58 kB │ gzip: 1.15 kB
../public/vue-assets/assets/job-adder-B2LcgC4o.png 5.41 kB
../public/vue-assets/assets/dixa-DzT89tKh.png 7.87 kB
../public/vue-assets/assets/planhat-CQycOTMW.svg 8.61 kB │ gzip: 3.48 kB
../public/vue-assets/assets/funding-circle-9iGnDGsz.png 9.43 kB
../public/vue-assets/assets/cision-uOdx_YiN.svg 14.41 kB │ gzip: 6.32 kB
../public/vue-assets/assets/les-mills-DWabpmYc.png 15.73 kB
../public/vue-assets/assets/superside-btT37L9L.png 26.69 kB
../public/vue-assets/assets/flags-a2kmUSbF.webp 28.18 kB
../public/vue-assets/assets/quinyx-RGQNHx6o.png 63.17 kB
../public/vue-assets/assets/[EMAIL] 66.44 kB
../public/vue-assets/assets/bg-marketing-CAIHIJMj.png 70.66 kB
../public/vue-assets/.vite/manifest.json 85.73 kB │ gzip: 8.32 kB
../public/vue-assets/assets/jiminny-score-DtTbKZaD.svg 131.93 kB │ gzip: 86.07 kB
../public/vue-assets/assets/wavy-bg-vnCaCm9m.webm 670.97 kB
../public/vue-assets/assets/wavy-bg-Op6dRIjR.mp4 14,484.63 kB
../public/vue-assets/assets/AppForm-EEGPL5K8.css 0.04 kB │ gzip: 0.06 kB
../public/vue-assets/assets/RecipientsCell-DNOSS8k1.css 0.12 kB │ gzip: 0.12 kB
../public/vue-assets/assets/softphone-coach-Cy_9nuQZ.css 0.13 kB │ gzip: 0.10 kB
../public/vue-assets/assets/locked-CHS8VPur.css 0.15 kB │ gzip: 0.12 kB
../public/vue-assets/assets/mobile-KPnGhnTp.css 0.16 kB │ gzip: 0.14 kB
../public/vue-assets/assets/BuildInfo-CxNQv_OV.css 0.20 kB │ gzip: 0.17 kB
../public/vue-assets/assets/AvatarsStack-C0Nvfh1V.css 0.28 kB │ gzip: 0.20 kB
../public/vue-assets/assets/LogoLong100-ByQTvUY3.css 0.32 kB │ gzip: 0.20 kB
../public/vue-assets/assets/connect--xBDzLbc.css 0.34 kB │ gzip: 0.21 kB
../public/vue-assets/assets/KioskBanner-xU3FRd8g.css 0.41 kB │ gzip: 0.27 kB
../public/vue-assets/assets/extension-installed-BPBhG13P.css 0.41 kB │ gzip: 0.26 kB
../public/vue-assets/assets/FollowModal-A0rJmIOK.css 0.45 kB │ gzip: 0.23 kB
../public/vue-assets/assets/GenericMessage-DutAlObK.css 0.49 kB │ gzip: 0.26 kB
../public/vue-assets/assets/textarea-caret-DCiNUER_.css 0.51 kB │ gzip: 0.32 kB
../public/vue-assets/assets/AppLinks-BznfGcBR.css 0.58 kB │ gzip: 0.33 kB
../public/vue-assets/assets/SeekBtn-_VMPIk6v.css 0.63 kB │ gzip: 0.32 kB
../public/vue-assets/assets/emoji-input-kJWRbWf8.css 0.68 kB │ gzip: 0.38 kB
../public/vue-assets/assets/activity-preview-BDxP2S0-.css 0.70 kB │ gzip: 0.41 kB
../public/vue-assets/assets/ListLoader-DQqAw-8s.css 0.73 kB │ gzip: 0.38 kB
../public/vue-assets/assets/filters-Bgb6cQD6.css 0.75 kB │ gzip: 0.39 kB
../public/vue-assets/assets/AiContext-Bg-zjjbA.css 0.99 kB │ gzip: 0.45 kB
../public/vue-assets/assets/vue3-daterange-picker-izPOBj3P.css 1.02 kB │ gzip: 0.39 kB
../public/vue-assets/assets/PrimaryButton-CJj2FzKS.css 1.08 kB │ gzip: 0.34 kB
../public/vue-assets/assets/other-BTuPXtu_.css 1.15 kB │ gzip: 0.49 kB
../public/vue-assets/assets/jenesius-vue-modal-DDcfejHO.css 1.17 kB │ gzip: 0.48 kB
../public/vue-assets/assets/TabEmptyState-DOlIO68k.css 1.21 kB │ gzip: 0.53 kB
../public/vue-assets/assets/AppAlert-BlSJKiqj.css 1.29 kB │ gzip: 0.41 kB
../public/vue-assets/assets/useActivityCustomerName-CBjfIUfJ.css 1.42 kB │ gzip: 0.45 kB
../public/vue-assets/assets/UserAvatar-co8l9mMk.css 1.51 kB │ gzip: 0.49 kB
../public/vue-assets/assets/basic-modal-Dx7ZBbcA.css 1.55 kB │ gzip: 0.66 kB
../public/vue-assets/assets/login-DYbcBesM.css 1.58 kB │ gzip: 0.61 kB
../public/vue-assets/assets/DrawerWidget-poAmqspk.css 1.67 kB │ gzip: 0.69 kB
../public/vue-assets/assets/ActionItems-5FkZSo3G.css 1.79 kB │ gzip: 0.80 kB
../public/vue-assets/assets/join-conference-DQ6Yl7eU.css 1.82 kB │ gzip: 0.75 kB
../public/vue-assets/assets/InputText-C-DxDNSV.css 1.95 kB │ gzip: 0.71 kB
../public/vue-assets/assets/GoogleLikeButton-DvQHxbqR.css 2.11 kB │ gzip: 0.66 kB
../public/vue-assets/assets/usePusherEventListener-CDj8aSNp.css 2.20 kB │ gzip: 0.73 kB
../public/vue-assets/assets/onboard-IzGiQ-EC.css 2.32 kB │ gzip: 0.95 kB
../public/vue-assets/assets/ai-reports-manage-DaynTl62.css 2.34 kB │ gzip: 0.86 kB
../public/vue-assets/assets/StatusBadge-B4N12dzU.css 2.35 kB │ gzip: 0.86 kB
../public/vue-assets/assets/GridView-CsP1Jk-k.css 2.36 kB │ gzip: 0.90 kB
../public/vue-assets/assets/WelcomeLayout-6AX86p6F.css 2.38 kB │ gzip: 0.91 kB
../public/vue-assets/assets/snackbar-CS_iqvrv.css 2.45 kB │ gzip: 0.80 kB
../public/vue-assets/assets/ai-reports-mtqGqPPq.css 2.47 kB │ gzip: 0.91 kB
../public/vue-assets/assets/Comment-gmAfiuer.css 2.63 kB │ gzip: 1.00 kB
../public/vue-assets/assets/meeting-consent-BasRvxE3.css 2.66 kB │ gzip: 0.96 kB
../public/vue-assets/assets/DealRiskList-CwxmDnSC.css 2.67 kB │ gzip: 0.96 kB
../public/vue-assets/assets/add-to-playlist-modal-C_ad1hht.css 2.84 kB │ gzip: 0.95 kB
../public/vue-assets/assets/live-_zT5qBc1.css 3.60 kB │ gzip: 1.16 kB
../public/vue-assets/assets/RadioField-DDp8Y71G.css 3.64 kB │ gzip: 1.20 kB
../public/vue-assets/assets/vue-multiselect-BkQNV7oL.css 3.80 kB │ gzip: 0.86 kB
../public/vue-assets/assets/activity-preview-result-Db1Da81T.css 3.89 kB │ gzip: 1.15 kB
../public/vue-assets/assets/liquor-tree-CB5oh8v1.css 4.08 kB │ gzip: 1.24 kB
../public/vue-assets/assets/sentry-CSjHcvmn.css 4.47 kB │ gzip: 1.08 kB
../public/vue-assets/assets/AiAutomation-Cj1t8v5y.css 4.61 kB │ gzip: 0.84 kB
../public/vue-assets/assets/export-portal-CyIUVTEe.css 5.34 kB │ gzip: 1.65 kB
../public/vue-assets/assets/InputField-C4hoKJ7Z.css 6.13 kB │ gzip: 1.27 kB
../public/vue-assets/assets/vue-mq-bh4L87Tr.css 6.64 kB │ gzip: 1.95 kB
../public/vue-assets/assets/invitation-EPR1t-bH.css 6.74 kB │ gzip: 2.22 kB
../public/vue-assets/assets/ondemand-C2m0YVGA.css 6.84 kB │ gzip: 2.02 kB
../public/vue-assets/assets/AskAnything-CyvEhHS1.css 8.60 kB │ gzip: 2.60 kB
../public/vue-assets/assets/AppButton-BIeoar_U.css 8.88 kB │ gzip: 2.04 kB
../public/vue-assets/assets/kiosk-BcheyWWL.css 9.56 kB │ gzip: 2.53 kB
../public/vue-assets/assets/AiCrmNotes-nNTe_mOY.css 10.62 kB │ gzip: 1.93 kB
../public/vue-assets/assets/deal-view-DJzG7PLp.css 11.70 kB │ gzip: 3.29 kB
../public/vue-assets/assets/tokens-DZgWrfKd.css 12.44 kB │ gzip: 2.82 kB
../public/vue-assets/assets/tokens-DL2BPbai.css 12.89 kB │ gzip: 2.87 kB
../public/vue-assets/assets/playlists-Cy3t3kYU.css 13.54 kB │ gzip: 3.16 kB
../public/vue-assets/assets/PhoneField-D_kGOah0.css 13.75 kB │ gzip: 3.44 kB
../public/vue-assets/assets/ListView-Dt0qkyuY.css 17.07 kB │ gzip: 4.11 kB
../public/vue-assets/assets/intl-tel-input-DgmgTINs.css 17.11 kB │ gzip: 5.37 kB
../public/vue-assets/assets/AppFormField-DvpfRzi8.css 18.98 kB │ gzip: 4.42 kB
../public/vue-assets/assets/OrgSettingsLayout-BogdXtgY.css 21.98 kB │ gzip: 5.51 kB
../public/vue-assets/assets/dashboard-DQw3TKyk.css 22.74 kB │ gzip: 4.35 kB
../public/vue-assets/assets/deal-insights-CfX3UNzh.css 29.66 kB │ gzip: 6.44 kB
../public/vue-assets/assets/team-insights-CjVhm0JN.css 42.31 kB │ gzip: 8.46 kB
../public/vue-assets/assets/playback-CvJP5tX1.css 44.55 kB │ gzip: 10.36 kB
../public/vue-assets/assets/video-js-skin-DYZluGb-.css 47.73 kB │ gzip: 12.63 kB
../public/vue-assets/assets/assets-CAbfI4CY.css 83.38 kB │ gzip: 51.71 kB
../public/vue-assets/assets/logged-in-layout-1mAqkmnS.css 153.67 kB │ gzip: 27.18 kB
../public/vue-assets/assets/assets-xH6dx_9q.css 259.71 kB │ gzip: 181.71 kB
../public/vue-assets/assets/directives-DJJeJJOP.js 0.53 kB │ gzip: 0.36 kB │ map: 0.32 kB
../public/vue-assets/assets/jenesius-vue-modal-BuBhyl83.js 0.54 kB │ gzip: 0.37 kB │ map: 0.47 kB
../public/vue-assets/assets/spark-D_-Wgfar.js 0.54 kB │ gzip: 0.35 kB │ map: 0.40 kB
../public/vue-assets/assets/url-messenger-_CGQa-lH.js 0.56 kB │ gzip: 0.37 kB │ map: 0.46 kB
../public/vue-assets/assets/wavy-bg-Cec-_GDj.js 0.57 kB │ gzip: 0.36 kB │ map: 0.35 kB
../public/vue-assets/assets/component-css-class-CtO0AVgW.js 0.61 kB │ gzip: 0.40 kB │ map: 0.91 kB
../public/vue-assets/assets/theme-B5VdyMN_.js 0.63 kB │ gzip: 0.41 kB │ map: 0.49 kB
../public/vue-assets/assets/pick-C8Pw7kHx.js 0.71 kB │ gzip: 0.45 kB │ map: 1.59 kB
../public/vue-assets/assets/utils-BjsEoLDB.js 0.77 kB │ gzip: 0.50 kB │ map: 1.68 kB
../public/vue-assets/assets/throttle-siTOKEMt.js 0.78 kB │ gzip: 0.49 kB │ map: 3.24 kB
../public/vue-assets/assets/lastFilters-B_B_O5LY.js 0.80 kB │ gzip: 0.51 kB │ map: 1.12 kB
../public/vue-assets/assets/useAuthState-Bpx8WpBc.js 0.81 kB │ gz...
|
[{"role":"AXTextArea","text [{"role":"AXTextArea","text":"info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ nvm use 24\nNow using node v24.11.1 (npm v11.6.2)\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yanr build\nzsh: command not found: yanr\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yanrn build\nzsh: command not found: yanrn\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yarn build \n[dotenv@17.3.1] injecting env (0) from .env -- tip: ⚡️\u0000 secrets for agents: https://dotenvx.com/as2\nvite v8.0.0 building client environment for production...\n✓ 4656 modules transformed.\n[sentry-vite-plugin] Warning: No auth token provided. Will not create release. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\n[sentry-vite-plugin] Warning: No auth token provided. Will not upload source maps. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\ncomputing gzip size...\n../public/vue-assets/index.html 4.58 kB │ gzip: 1.15 kB\n../public/vue-assets/assets/job-adder-B2LcgC4o.png 5.41 kB\n../public/vue-assets/assets/dixa-DzT89tKh.png 7.87 kB\n../public/vue-assets/assets/planhat-CQycOTMW.svg 8.61 kB │ gzip: 3.48 kB\n../public/vue-assets/assets/funding-circle-9iGnDGsz.png 9.43 kB\n../public/vue-assets/assets/cision-uOdx_YiN.svg 14.41 kB │ gzip: 6.32 kB\n../public/vue-assets/assets/les-mills-DWabpmYc.png 15.73 kB\n../public/vue-assets/assets/superside-btT37L9L.png 26.69 kB\n../public/vue-assets/assets/flags-a2kmUSbF.webp 28.18 kB\n../public/vue-assets/assets/quinyx-RGQNHx6o.png 63.17 kB\n../public/vue-assets/assets/flags@2x-gR6KPp3x.webp 66.44 kB\n../public/vue-assets/assets/bg-marketing-CAIHIJMj.png 70.66 kB\n../public/vue-assets/.vite/manifest.json 85.73 kB │ gzip: 8.33 kB\n../public/vue-assets/assets/jiminny-score-DtTbKZaD.svg 131.93 kB │ gzip: 86.07 kB\n../public/vue-assets/assets/wavy-bg-vnCaCm9m.webm 670.97 kB\n../public/vue-assets/assets/wavy-bg-Op6dRIjR.mp4 14,484.63 kB\n../public/vue-assets/assets/AppForm-EEGPL5K8.css 0.04 kB │ gzip: 0.06 kB\n../public/vue-assets/assets/RecipientsCell-DNOSS8k1.css 0.12 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/softphone-coach-Cy_9nuQZ.css 0.13 kB │ gzip: 0.10 kB\n../public/vue-assets/assets/locked-CHS8VPur.css 0.15 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/mobile-KPnGhnTp.css 0.16 kB │ gzip: 0.14 kB\n../public/vue-assets/assets/BuildInfo-CxNQv_OV.css 0.20 kB │ gzip: 0.17 kB\n../public/vue-assets/assets/AvatarsStack-C0Nvfh1V.css 0.28 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/LogoLong100-ByQTvUY3.css 0.32 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/connect--xBDzLbc.css 0.34 kB │ gzip: 0.21 kB\n../public/vue-assets/assets/KioskBanner-xU3FRd8g.css 0.41 kB │ gzip: 0.27 kB\n../public/vue-assets/assets/extension-installed-BPBhG13P.css 0.41 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/FollowModal-A0rJmIOK.css 0.45 kB │ gzip: 0.23 kB\n../public/vue-assets/assets/GenericMessage-DutAlObK.css 0.49 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/textarea-caret-DCiNUER_.css 0.51 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/AppLinks-BznfGcBR.css 0.58 kB │ gzip: 0.33 kB\n../public/vue-assets/assets/SeekBtn-_VMPIk6v.css 0.63 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/emoji-input-kJWRbWf8.css 0.68 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/activity-preview-BDxP2S0-.css 0.70 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/ListLoader-DQqAw-8s.css 0.73 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/filters-Bgb6cQD6.css 0.75 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/AiContext-Bg-zjjbA.css 0.99 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/vue3-daterange-picker-izPOBj3P.css 1.02 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/PrimaryButton-CJj2FzKS.css 1.08 kB │ gzip: 0.34 kB\n../public/vue-assets/assets/other-BTuPXtu_.css 1.15 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/jenesius-vue-modal-DDcfejHO.css 1.17 kB │ gzip: 0.48 kB\n../public/vue-assets/assets/TabEmptyState-DOlIO68k.css 1.21 kB │ gzip: 0.53 kB\n../public/vue-assets/assets/AppAlert-BlSJKiqj.css 1.29 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/useActivityCustomerName-CBjfIUfJ.css 1.42 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/UserAvatar-co8l9mMk.css 1.51 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/basic-modal-Dx7ZBbcA.css 1.55 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/login-DYbcBesM.css 1.58 kB │ gzip: 0.61 kB\n../public/vue-assets/assets/DrawerWidget-poAmqspk.css 1.67 kB │ gzip: 0.69 kB\n../public/vue-assets/assets/ActionItems-5FkZSo3G.css 1.79 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/join-conference-DQ6Yl7eU.css 1.82 kB │ gzip: 0.75 kB\n../public/vue-assets/assets/InputText-C-DxDNSV.css 1.95 kB │ gzip: 0.71 kB\n../public/vue-assets/assets/GoogleLikeButton-DvQHxbqR.css 2.11 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/usePusherEventListener-CDj8aSNp.css 2.20 kB │ gzip: 0.73 kB\n../public/vue-assets/assets/onboard-IzGiQ-EC.css 2.32 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/ai-reports-manage-DaynTl62.css 2.34 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/StatusBadge-B4N12dzU.css 2.35 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/GridView-CsP1Jk-k.css 2.36 kB │ gzip: 0.90 kB\n../public/vue-assets/assets/WelcomeLayout-6AX86p6F.css 2.38 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/snackbar-CS_iqvrv.css 2.45 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/ai-reports-mtqGqPPq.css 2.47 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/Comment-gmAfiuer.css 2.63 kB │ gzip: 1.00 kB\n../public/vue-assets/assets/meeting-consent-BasRvxE3.css 2.66 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/DealRiskList-CwxmDnSC.css 2.67 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/add-to-playlist-modal-C_ad1hht.css 2.84 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/live-_zT5qBc1.css 3.60 kB │ gzip: 1.16 kB\n../public/vue-assets/assets/RadioField-DDp8Y71G.css 3.64 kB │ gzip: 1.20 kB\n../public/vue-assets/assets/vue-multiselect-BkQNV7oL.css 3.80 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/activity-preview-result-Db1Da81T.css 3.89 kB │ gzip: 1.15 kB\n../public/vue-assets/assets/liquor-tree-CB5oh8v1.css 4.08 kB │ gzip: 1.24 kB\n../public/vue-assets/assets/sentry-CSjHcvmn.css 4.47 kB │ gzip: 1.08 kB\n../public/vue-assets/assets/AiAutomation-Cj1t8v5y.css 4.61 kB │ gzip: 0.84 kB\n../public/vue-assets/assets/export-portal-CyIUVTEe.css 5.34 kB │ gzip: 1.65 kB\n../public/vue-assets/assets/InputField-C4hoKJ7Z.css 6.13 kB │ gzip: 1.27 kB\n../public/vue-assets/assets/vue-mq-bh4L87Tr.css 6.64 kB │ gzip: 1.95 kB\n../public/vue-assets/assets/invitation-EPR1t-bH.css 6.74 kB │ gzip: 2.22 kB\n../public/vue-assets/assets/ondemand-C2m0YVGA.css 6.84 kB │ gzip: 2.02 kB\n../public/vue-assets/assets/AskAnything-CyvEhHS1.css 8.60 kB │ gzip: 2.60 kB\n../public/vue-assets/assets/AppButton-BIeoar_U.css 8.88 kB │ gzip: 2.04 kB\n../public/vue-assets/assets/kiosk-BcheyWWL.css 9.56 kB │ gzip: 2.53 kB\n../public/vue-assets/assets/AiCrmNotes-nNTe_mOY.css 10.62 kB │ gzip: 1.93 kB\n../public/vue-assets/assets/deal-view-DJzG7PLp.css 11.70 kB │ gzip: 3.29 kB\n../public/vue-assets/assets/tokens-B6_hRKul.css 12.95 kB │ gzip: 2.92 kB\n../public/vue-assets/assets/tokens-CoAAv8do.css 13.41 kB │ gzip: 2.97 kB\n../public/vue-assets/assets/playlists-Cy3t3kYU.css 13.54 kB │ gzip: 3.16 kB\n../public/vue-assets/assets/PhoneField-D_kGOah0.css 13.75 kB │ gzip: 3.44 kB\n../public/vue-assets/assets/ListView-Dt0qkyuY.css 17.07 kB │ gzip: 4.11 kB\n../public/vue-assets/assets/intl-tel-input-DgmgTINs.css 17.11 kB │ gzip: 5.37 kB\n../public/vue-assets/assets/AppFormField-DvpfRzi8.css 18.98 kB │ gzip: 4.42 kB\n../public/vue-assets/assets/OrgSettingsLayout-BogdXtgY.css 21.98 kB │ gzip: 5.51 kB\n../public/vue-assets/assets/dashboard-DQw3TKyk.css 22.74 kB │ gzip: 4.35 kB\n../public/vue-assets/assets/deal-insights-CfX3UNzh.css 29.66 kB │ gzip: 6.44 kB\n../public/vue-assets/assets/team-insights-CjVhm0JN.css 42.31 kB │ gzip: 8.46 kB\n../public/vue-assets/assets/playback-CvJP5tX1.css 44.55 kB │ gzip: 10.36 kB\n../public/vue-assets/assets/video-js-skin-DYZluGb-.css 47.73 kB │ gzip: 12.63 kB\n../public/vue-assets/assets/assets-CAbfI4CY.css 83.38 kB │ gzip: 51.71 kB\n../public/vue-assets/assets/logged-in-layout-1mAqkmnS.css 153.67 kB │ gzip: 27.18 kB\n../public/vue-assets/assets/assets-xH6dx_9q.css 259.71 kB │ gzip: 181.71 kB\n../public/vue-assets/assets/directives-DJJeJJOP.js 0.53 kB │ gzip: 0.36 kB │ map: 0.32 kB\n../public/vue-assets/assets/jenesius-vue-modal-BuBhyl83.js 0.54 kB │ gzip: 0.37 kB │ map: 0.47 kB\n../public/vue-assets/assets/spark-D_-Wgfar.js 0.54 kB │ gzip: 0.35 kB │ map: 0.40 kB\n../public/vue-assets/assets/url-messenger-_CGQa-lH.js 0.56 kB │ gzip: 0.37 kB │ map: 0.46 kB\n../public/vue-assets/assets/wavy-bg-Cec-_GDj.js 0.57 kB │ gzip: 0.36 kB │ map: 0.35 kB\n../public/vue-assets/assets/component-css-class-CtO0AVgW.js 0.61 kB │ gzip: 0.40 kB │ map: 0.91 kB\n../public/vue-assets/assets/theme-CrLnsUSQ.js 0.63 kB │ gzip: 0.41 kB │ map: 0.49 kB\n../public/vue-assets/assets/pick-jGxSsmQW.js 0.71 kB │ gzip: 0.45 kB │ map: 1.59 kB\n../public/vue-assets/assets/utils-BjsEoLDB.js 0.77 kB │ gzip: 0.50 kB │ map: 1.68 kB\n../public/vue-assets/assets/throttle-DE_etUX9.js 0.78 kB │ gzip: 0.49 kB │ map: 3.24 kB\n../public/vue-assets/assets/lastFilters-CjyI5phg.js 0.80 kB │ gzip: 0.51 kB │ map: 1.12 kB\n../public/vue-assets/assets/useAuthState-Bpx8WpBc.js 0.81 kB │ gzip: 0.50 kB │ map: 1.64 kB\n../public/vue-assets/assets/v-focus-618yf9WO.js 0.85 kB │ gzip: 0.52 kB │ map: 1.42 kB\n../public/vue-assets/assets/ListLoader-bfQmkVGu.js 0.87 kB │ gzip: 0.54 kB │ map: 2.90 kB\n../public/vue-assets/assets/utils-CAMifZzY.js 0.88 kB │ gzip: 0.53 kB │ map: 2.45 kB\n../public/vue-assets/assets/mobileApp-QFLS3kId.js 0.89 kB │ gzip: 0.54 kB │ map: 1.12 kB\n../public/vue-assets/assets/pickBy-DV-Svw8z.js 0.95 kB │ gzip: 0.59 kB │ map: 2.57 kB\n../public/vue-assets/assets/BuildInfo-CIGre86L.js 1.00 kB │ gzip: 0.65 kB │ map: 1.48 kB\n../public/vue-assets/assets/LogoShort100-D_SQdBYB.js 1.01 kB │ gzip: 0.64 kB │ map: 1.23 kB\n../public/vue-assets/assets/useDrawerModal-CmhXI-pZ.js 1.04 kB │ gzip: 0.65 kB │ map: 1.94 kB\n../public/vue-assets/assets/_getAllKeysIn-CkMQAnJa.js 1.12 kB │ gzip: 0.67 kB │ map: 4.83 kB\n../public/vue-assets/assets/useAutosizeTextarea-DyMwh_20.js 1.13 kB │ gzip: 0.70 kB │ map: 3.16 kB\n../public/vue-assets/assets/planhat-DPA36Hcn.js 1.21 kB │ gzip: 0.71 kB │ map: 2.18 kB\n../public/vue-assets/assets/useStoreModule-Cx6UoGVJ.js 1.35 kB │ gzip: 0.72 kB │ map: 6.03 kB\n../public/vue-assets/assets/GenericMessage-BxGsrfvw.js 1.39 kB │ gzip: 0.75 kB │ map: 1.90 kB\n../public/vue-assets/assets/BrowserExtensionInstaller-DimyBPIi.js 1.45 kB │ gzip: 0.78 kB │ map: 2.05 kB\n../public/vue-assets/assets/debounce-C6V4-Yml.js 1.53 kB │ gzip: 0.83 kB │ map: 8.61 kB\n../public/vue-assets/assets/extension-installed-C_yEm2e-.js 1.60 kB │ gzip: 0.91 kB │ map: 3.20 kB\n../public/vue-assets/assets/RecipientsCell-BS9gDGey.js 1.61 kB │ gzip: 0.93 kB │ map: 4.04 kB\n../public/vue-assets/assets/PrimaryButton-DaKR3UAG.js 1.61 kB │ gzip: 0.87 kB │ map: 3.73 kB\n../public/vue-assets/assets/KioskBanner-DyDQY_lm.js 1.66 kB │ gzip: 1.00 kB │ map: 2.88 kB\n../public/vue-assets/assets/LogoLong100-Hid0kvjR.js 1.78 kB │ gzip: 1.08 kB │ map: 5.91 kB\n../public/vue-assets/assets/GoogleLikeButton-CGFy3nbz.js 1.80 kB │ gzip: 0.84 kB │ map: 4.71 kB\n../public/vue-assets/assets/AppAlert-DFp0nftl.js 1.86 kB │ gzip: 1.01 kB │ map: 6.51 kB\n../public/vue-assets/assets/usePusherEventListener-DRJ7JhID.js 1.93 kB │ gzip: 1.08 kB │ map: 9.59 kB\n../public/vue-assets/assets/AppForm-BgtH5xsM.js 2.01 kB │ gzip: 1.10 kB │ map: 6.89 kB\n../public/vue-assets/assets/vee-validate-rules-IFeGZHb3.js 2.02 kB │ gzip: 0.89 kB │ map: 22.31 kB\n../public/vue-assets/assets/literals-CXSwYC8y.js 2.05 kB │ gzip: 0.97 kB │ map: 3.08 kB\n../public/vue-assets/assets/TabEmptyState-pk8vRxJt.js 2.16 kB │ gzip: 1.12 kB │ map: 8.26 kB\n../public/vue-assets/assets/Replies-BrhP0P7D.js 2.23 kB │ gzip: 1.14 kB │ map: 2.40 kB\n../public/vue-assets/assets/settings-DWZF-G8N.js 2.34 kB │ gzip: 1.23 kB │ map: 1.73 kB\n../public/vue-assets/assets/SeekBtn-CMp8sSUA.js 2.47 kB │ gzip: 1.18 kB │ map: 9.27 kB\n../public/vue-assets/assets/AiAutomation-CnVa_KZl.js 2.59 kB │ gzip: 1.37 kB │ map: 4.45 kB\n../public/vue-assets/assets/locked-B2ThxTAd.js 2.59 kB │ gzip: 1.42 kB │ map: 4.00 kB\n../public/vue-assets/assets/DrawerWidget-BPC-tCgo.js 2.68 kB │ gzip: 1.33 kB │ map: 6.95 kB\n../public/vue-assets/assets/vue-infinite-scroll-D0cI4gH6.js 2.72 kB │ gzip: 1.28 kB │ map: 9.99 kB\n../public/vue-assets/assets/useActivityCustomerName-CYuaZj-p.js 2.87 kB │ gzip: 1.41 kB │ map: 6.27 kB\n../public/vue-assets/assets/other-k3In5q1l.js 2.97 kB │ gzip: 1.50 kB │ map: 3.38 kB\n../public/vue-assets/assets/InputDropdown-CBM8xiPT.js 3.33 kB │ gzip: 1.61 kB │ map: 6.02 kB\n../public/vue-assets/assets/AvatarsStack-hxex9Ic8.js 3.40 kB │ gzip: 1.51 kB │ map: 9.54 kB\n../public/vue-assets/assets/activity-preview-BZrDAfqV.js 3.45 kB │ gzip: 1.69 kB │ map: 10.11 kB\n../public/vue-assets/assets/FollowModal-SMYvVwqG.js 3.70 kB │ gzip: 1.81 kB │ map: 8.86 kB\n../public/vue-assets/assets/softphone-coach-DJLlDZUr.js 3.74 kB │ gzip: 1.96 kB │ map: 5.03 kB\n../public/vue-assets/assets/store-DdBy-CTd.js 4.10 kB │ gzip: 2.00 kB │ map: 18.73 kB\n../public/vue-assets/assets/AiContext-BYAvUsG9.js 4.34 kB │ gzip: 2.08 kB │ map: 13.11 kB\n../public/vue-assets/assets/meeting-consent-EdnBkMBr.js 4.43 kB │ gzip: 2.04 kB │ map: 9.72 kB\n../public/vue-assets/assets/vue-multiselect-VB2Agtp1.js 4.49 kB │ gzip: 1.93 kB │ map: 14.85 kB\n../public/vue-assets/assets/snackbarNotifications-DjDIGkWd.js 4.83 kB │ gzip: 1.58 kB │ map: 11.62 kB\n../public/vue-assets/assets/invitation-B1SNfWGY.js 4.88 kB │ gzip: 2.21 kB │ map: 19.38 kB\n../public/vue-assets/assets/connect-DzHqvqqR.js 5.17 kB │ gzip: 2.28 kB │ map: 10.35 kB\n../public/vue-assets/assets/textarea-caret-B2HdIdpZ.js 5.25 kB │ gzip: 2.41 kB │ map: 14.48 kB\n../public/vue-assets/assets/snackbar-Bq-YQ7pz.js 5.48 kB │ gzip: 2.37 kB │ map: 20.43 kB\n../public/vue-assets/assets/RadioField-2SeSZB7D.js 5.65 kB │ gzip: 2.22 kB │ map: 15.82 kB\n../public/vue-assets/assets/filters-D_eXZdIL.js 5.76 kB │ gzip: 2.17 kB │ map: 16.36 kB\n../public/vue-assets/assets/useActivityHelper-DbifQcp6.js 5.84 kB │ gzip: 2.51 kB │ map: 14.26 kB\n../public/vue-assets/assets/vue-scrollto-ul3pFdrb.js 5.97 kB │ gzip: 2.72 kB │ map: 23.98 kB\n../public/vue-assets/assets/join-conference-CYJkDTD8.js 6.25 kB │ gzip: 2.82 kB │ map: 24.66 kB\n../public/vue-assets/assets/pluralize-BeKVJaE0.js 6.34 kB │ gzip: 2.70 kB │ map: 18.28 kB\n../public/vue-assets/assets/login-DkwzcZhV.js 7.02 kB │ gzip: 3.16 kB │ map: 17.51 kB\n../public/vue-assets/assets/AppLinks-Bg20Hslb.js 7.14 kB │ gzip: 2.80 kB │ map: 14.87 kB\n../public/vue-assets/assets/InputField-CkdrqKBp.js 7.20 kB │ gzip: 2.58 kB │ map: 19.39 kB\n../public/vue-assets/assets/UserAvatar-UKkSAS4R.js 7.93 kB │ gzip: 3.32 kB │ map: 34.91 kB\n../public/vue-assets/assets/InputText-CiBJ3oHz.js 7.99 kB │ gzip: 3.05 kB │ map: 20.64 kB\n../public/vue-assets/assets/AiCrmNotes-BZWjw9pN.js 8.27 kB │ gzip: 3.32 kB │ map: 34.03 kB\n../public/vue-assets/assets/vue-mq-CmpUzQtD.js 8.84 kB │ gzip: 3.54 kB │ map: 23.69 kB\n../public/vue-assets/assets/pro-duotone-svg-icons-BSBZV3-t.js 9.51 kB │ gzip: 3.43 kB │ map: 3,146.68 kB\n../public/vue-assets/assets/activity-preview-result-DLO6BXq1.js 10.65 kB │ gzip: 3.98 kB │ map: 38.51 kB\n../public/vue-assets/assets/pro-regular-svg-icons-CWpSBR20.js 13.10 kB │ gzip: 4.64 kB │ map: 3,178.15 kB\n../public/vue-assets/assets/add-to-playlist-modal-prxYCVfk.js 14.99 kB │ gzip: 5.98 kB │ map: 51.34 kB\n../public/vue-assets/assets/ai-reports-B3i0pft-.js 15.17 kB │ gzip: 6.03 kB │ map: 51.85 kB\n../public/vue-assets/assets/export-portal-54ZutGpG.js 15.68 kB │ gzip: 6.22 kB │ map: 52.78 kB\n../public/vue-assets/assets/Comment-BpW-5w7g.js 15.92 kB │ gzip: 5.72 kB │ map: 39.96 kB\n../public/vue-assets/assets/useragent-DQ4F_O30.js 18.02 kB │ gzip: 8.06 kB │ map: 68.31 kB\n../public/vue-assets/assets/linkify-B_q6MgwI.js 18.58 kB │ gzip: 10.37 kB │ map: 88.73 kB\n../public/vue-assets/assets/ai-reports-manage-Ber3EVyb.js 18.92 kB │ gzip: 6.75 kB │ map: 66.14 kB\n../public/vue-assets/assets/dist-C2hisF0L.js 19.32 kB │ gzip: 7.87 kB │ map: 390.77 kB\n../public/vue-assets/assets/pro-solid-svg-icons-DBOkpln3.js 19.46 kB │ gzip: 6.72 kB │ map: 2,692.14 kB\n../public/vue-assets/assets/purify.es-BxOw6oE6.js 21.49 kB │ gzip: 8.71 kB │ map: 83.43 kB\n../public/vue-assets/assets/ActionItems-BuNWx0BA.js 21.92 kB │ gzip: 8.54 kB │ map: 76.05 kB\n../public/vue-assets/assets/mobile-BMtylYi6.js 23.34 kB │ gzip: 9.71 kB │ map: 50.90 kB\n../public/vue-assets/assets/basic-modal-BdXsnFwC.js 23.87 kB │ gzip: 9.06 kB │ map: 64.15 kB\n../public/vue-assets/assets/GridView-CP7J7-qo.js 26.60 kB │ gzip: 10.05 kB │ map: 92.74 kB\n../public/vue-assets/assets/ondemand-kg1j3nIA.js 26.88 kB │ gzip: 9.39 kB │ map: 73.94 kB\n../public/vue-assets/assets/CrmLink-DKYsnHnx.js 27.91 kB │ gzip: 10.18 kB │ map: 93.18 kB\n../public/vue-assets/assets/liquor-tree-COUefof4.js 30.75 kB │ gzip: 9.58 kB │ map: 78.74 kB\n../public/vue-assets/assets/DealRiskList-C8q7faXl.js 34.39 kB │ gzip: 10.62 kB │ map: 115.18 kB\n../public/vue-assets/assets/AskAnything-BgSY9SKi.js 39.50 kB │ gzip: 14.97 kB │ map: 173.20 kB\n../public/vue-assets/assets/lib-CwM9toD2.js 39.69 kB │ gzip: 12.70 kB │ map: 138.34 kB\n../public/vue-assets/assets/AppFormField-COtjyq5c.js 41.91 kB │ gzip: 12.69 kB │ map: 150.73 kB\n../public/vue-assets/assets/deal-view-bksUVN5E.js 43.22 kB │ gzip: 14.34 kB │ map: 150.62 kB\n../public/vue-assets/assets/exports-D1lmea4O.js 47.84 kB │ gzip: 16.46 kB │ map: 294.48 kB\n../public/vue-assets/assets/playlists-Yn3gKP2z.js 48.28 kB │ gzip: 15.07 kB │ map: 153.25 kB\n../public/vue-assets/assets/callScoringTemplates-zeRn40uL.js 55.13 kB │ gzip: 13.28 kB │ map: 65.85 kB\n../public/vue-assets/assets/_copyObject-USkOnlaQ.js 61.28 kB │ gzip: 20.09 kB │ map: 239.59 kB\n../public/vue-assets/assets/pusher-znYCfz7U.js 62.98 kB │ gzip: 18.88 kB │ map: 219.27 kB\n../public/vue-assets/assets/onboard-BqxRx4zw.js 63.11 kB │ gzip: 21.85 kB │ map: 201.38 kB\n../public/vue-assets/assets/StatusBadge-C2H2xj9g.js 64.66 kB │ gzip: 22.96 kB │ map: 244.72 kB\n../public/vue-assets/assets/kiosk-BDlz4mpq.js 79.60 kB │ gzip: 22.65 kB │ map: 300.68 kB\n../public/vue-assets/assets/preload-helper-DCvhahzG.js 82.59 kB │ gzip: 27.46 kB │ map: 3,452.35 kB\n../public/vue-assets/assets/deal-insights-BnVEi9yc.js 94.84 kB │ gzip: 28.16 kB │ map: 292.79 kB\n../public/vue-assets/assets/ListView-CN2ZtlC7.js 115.71 kB │ gzip: 33.78 kB │ map: 308.10 kB\n../public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js 117.59 kB │ gzip: 38.71 kB │ map: 500.60 kB\n../public/vue-assets/assets/WelcomeLayout-B6wd32HG.js 120.67 kB │ gzip: 34.16 kB │ map: 258.56 kB\n../public/vue-assets/assets/dashboard-XCNXJG27.js 128.71 kB │ gzip: 40.05 kB │ map: 410.48 kB\n../public/vue-assets/assets/emoji-input-CSq87OVy.js 129.28 kB │ gzip: 36.72 kB │ map: 266.15 kB\n../public/vue-assets/assets/AppButton-D3qMdODr.js 133.44 kB │ gzip: 43.03 kB │ map: 516.67 kB\n../public/vue-assets/assets/sentry-DMzthP2u.js 164.28 kB │ gzip: 52.24 kB │ map: 831.82 kB\n../public/vue-assets/assets/OrgSettingsLayout-DYzOhe6q.js 176.33 kB │ gzip: 56.15 kB │ map: 623.43 kB\n../public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js 180.40 kB │ gzip: 67.85 kB │ map: 836.88 kB\n../public/vue-assets/assets/playback-DRpCBZO9.js 198.79 kB │ gzip: 61.85 kB │ map: 684.87 kB\n../public/vue-assets/assets/index.module-Bjlhgfd1.js 218.14 kB │ gzip: 64.16 kB │ map: 1,108.20 kB\n../public/vue-assets/assets/intl-tel-input-BW4mv40Q.js 264.94 kB │ gzip: 60.31 kB │ map: 475.61 kB\n../public/vue-assets/assets/team-insights-CPpPTAKV.js 298.57 kB │ gzip: 77.22 kB │ map: 959.96 kB\n../public/vue-assets/assets/popper-CQwVcrX4.js 307.13 kB │ gzip: 103.86 kB │ map: 1,245.28 kB\n../public/vue-assets/assets/PhoneField-CwCIoAYm.js 343.99 kB │ gzip: 84.90 kB │ map: 849.05 kB\n../public/vue-assets/assets/live-Cfb59XkF.js 367.43 kB │ gzip: 97.05 kB │ map: 792.41 kB\n../public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BNO485xV.js 689.63 kB │ gzip: 202.81 kB │ map: 3,016.64 kB\n../public/vue-assets/assets/index-1Grk9P2e.js 825.23 kB │ gzip: 72.55 kB │ map: 436.62 kB\n../public/vue-assets/assets/logged-in-layout-DArs1VTa.js 1,402.70 kB │ gzip: 438.08 kB │ map: 6,283.55 kB\n\n[PLUGIN_TIMINGS] Warning: Your build spent significant time in plugins. Here is a breakdown:\n - vite:css (61%)\n - vite-plugin-externals (12%)\n - vite:vue (9%)\n - sentry-vite-plugin (8%)\n - vite:worker (6%)\nSee https://rolldown.rs/options/checks#plugintimings for more details.\n\n[plugin builtin:vite-reporter] \n(!) Some chunks are larger than 500 kB after minification. Consider:\n- Using dynamic import() to code-split the application\n- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting\n- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.\n✓ built in 22.57s\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yarn install\n➤ YN0000: · Yarn 4.12.0\n➤ YN0000: ┌ Resolution step\n➤ YN0000: └ Completed\n➤ YN0000: ┌ Post-resolution validation\n➤ YN0002: │ root-workspace-0b6124@workspace:. doesn't provide @testing-library/dom (pea6ced), requested by @testing-library/user-event.\n➤ YN0002: │ root-workspace-0b6124@workspace:. doesn't provide noty (p5bdb0d), requested by vue-components.\n➤ YN0086: │ Some peer dependencies are incorrectly met by your project; run yarn explain peer-requirements <hash> for details, where <hash> is the six-letter p-prefixed code.\n➤ YN0086: │ Some peer dependencies are incorrectly met by dependencies; run yarn explain peer-requirements for details.\n➤ YN0000: └ Completed\n➤ YN0000: ┌ Fetch step\n➤ YN0000: └ Completed in 1s 655ms\n➤ YN0000: ┌ Link step\n➤ YN0008: │ root-workspace-0b6124@workspace:. must be rebuilt because its dependency tree changed\n➤ YN0000: └ Completed in 0s 969ms\n➤ YN0000: · Done with warnings in 3s 14ms\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yarn build\n[dotenv@17.3.1] injecting env (0) from .env -- tip: ⚙️\u0000 override existing env vars with { override: true }\nvite v8.0.0 building client environment for production...\n✓ 4656 modules transformed.\n[sentry-vite-plugin] Warning: No auth token provided. Will not create release. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\n[sentry-vite-plugin] Warning: No auth token provided. Will not upload source maps. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\ncomputing gzip size...\n../public/vue-assets/index.html 4.58 kB │ gzip: 1.15 kB\n../public/vue-assets/assets/job-adder-B2LcgC4o.png 5.41 kB\n../public/vue-assets/assets/dixa-DzT89tKh.png 7.87 kB\n../public/vue-assets/assets/planhat-CQycOTMW.svg 8.61 kB │ gzip: 3.48 kB\n../public/vue-assets/assets/funding-circle-9iGnDGsz.png 9.43 kB\n../public/vue-assets/assets/cision-uOdx_YiN.svg 14.41 kB │ gzip: 6.32 kB\n../public/vue-assets/assets/les-mills-DWabpmYc.png 15.73 kB\n../public/vue-assets/assets/superside-btT37L9L.png 26.69 kB\n../public/vue-assets/assets/flags-a2kmUSbF.webp 28.18 kB\n../public/vue-assets/assets/quinyx-RGQNHx6o.png 63.17 kB\n../public/vue-assets/assets/flags@2x-gR6KPp3x.webp 66.44 kB\n../public/vue-assets/assets/bg-marketing-CAIHIJMj.png 70.66 kB\n../public/vue-assets/.vite/manifest.json 85.73 kB │ gzip: 8.32 kB\n../public/vue-assets/assets/jiminny-score-DtTbKZaD.svg 131.93 kB │ gzip: 86.07 kB\n../public/vue-assets/assets/wavy-bg-vnCaCm9m.webm 670.97 kB\n../public/vue-assets/assets/wavy-bg-Op6dRIjR.mp4 14,484.63 kB\n../public/vue-assets/assets/AppForm-EEGPL5K8.css 0.04 kB │ gzip: 0.06 kB\n../public/vue-assets/assets/RecipientsCell-DNOSS8k1.css 0.12 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/softphone-coach-Cy_9nuQZ.css 0.13 kB │ gzip: 0.10 kB\n../public/vue-assets/assets/locked-CHS8VPur.css 0.15 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/mobile-KPnGhnTp.css 0.16 kB │ gzip: 0.14 kB\n../public/vue-assets/assets/BuildInfo-CxNQv_OV.css 0.20 kB │ gzip: 0.17 kB\n../public/vue-assets/assets/AvatarsStack-C0Nvfh1V.css 0.28 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/LogoLong100-ByQTvUY3.css 0.32 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/connect--xBDzLbc.css 0.34 kB │ gzip: 0.21 kB\n../public/vue-assets/assets/KioskBanner-xU3FRd8g.css 0.41 kB │ gzip: 0.27 kB\n../public/vue-assets/assets/extension-installed-BPBhG13P.css 0.41 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/FollowModal-A0rJmIOK.css 0.45 kB │ gzip: 0.23 kB\n../public/vue-assets/assets/GenericMessage-DutAlObK.css 0.49 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/textarea-caret-DCiNUER_.css 0.51 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/AppLinks-BznfGcBR.css 0.58 kB │ gzip: 0.33 kB\n../public/vue-assets/assets/SeekBtn-_VMPIk6v.css 0.63 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/emoji-input-kJWRbWf8.css 0.68 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/activity-preview-BDxP2S0-.css 0.70 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/ListLoader-DQqAw-8s.css 0.73 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/filters-Bgb6cQD6.css 0.75 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/AiContext-Bg-zjjbA.css 0.99 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/vue3-daterange-picker-izPOBj3P.css 1.02 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/PrimaryButton-CJj2FzKS.css 1.08 kB │ gzip: 0.34 kB\n../public/vue-assets/assets/other-BTuPXtu_.css 1.15 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/jenesius-vue-modal-DDcfejHO.css 1.17 kB │ gzip: 0.48 kB\n../public/vue-assets/assets/TabEmptyState-DOlIO68k.css 1.21 kB │ gzip: 0.53 kB\n../public/vue-assets/assets/AppAlert-BlSJKiqj.css 1.29 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/useActivityCustomerName-CBjfIUfJ.css 1.42 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/UserAvatar-co8l9mMk.css 1.51 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/basic-modal-Dx7ZBbcA.css 1.55 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/login-DYbcBesM.css 1.58 kB │ gzip: 0.61 kB\n../public/vue-assets/assets/DrawerWidget-poAmqspk.css 1.67 kB │ gzip: 0.69 kB\n../public/vue-assets/assets/ActionItems-5FkZSo3G.css 1.79 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/join-conference-DQ6Yl7eU.css 1.82 kB │ gzip: 0.75 kB\n../public/vue-assets/assets/InputText-C-DxDNSV.css 1.95 kB │ gzip: 0.71 kB\n../public/vue-assets/assets/GoogleLikeButton-DvQHxbqR.css 2.11 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/usePusherEventListener-CDj8aSNp.css 2.20 kB │ gzip: 0.73 kB\n../public/vue-assets/assets/onboard-IzGiQ-EC.css 2.32 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/ai-reports-manage-DaynTl62.css 2.34 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/StatusBadge-B4N12dzU.css 2.35 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/GridView-CsP1Jk-k.css 2.36 kB │ gzip: 0.90 kB\n../public/vue-assets/assets/WelcomeLayout-6AX86p6F.css 2.38 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/snackbar-CS_iqvrv.css 2.45 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/ai-reports-mtqGqPPq.css 2.47 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/Comment-gmAfiuer.css 2.63 kB │ gzip: 1.00 kB\n../public/vue-assets/assets/meeting-consent-BasRvxE3.css 2.66 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/DealRiskList-CwxmDnSC.css 2.67 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/add-to-playlist-modal-C_ad1hht.css 2.84 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/live-_zT5qBc1.css 3.60 kB │ gzip: 1.16 kB\n../public/vue-assets/assets/RadioField-DDp8Y71G.css 3.64 kB │ gzip: 1.20 kB\n../public/vue-assets/assets/vue-multiselect-BkQNV7oL.css 3.80 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/activity-preview-result-Db1Da81T.css 3.89 kB │ gzip: 1.15 kB\n../public/vue-assets/assets/liquor-tree-CB5oh8v1.css 4.08 kB │ gzip: 1.24 kB\n../public/vue-assets/assets/sentry-CSjHcvmn.css 4.47 kB │ gzip: 1.08 kB\n../public/vue-assets/assets/AiAutomation-Cj1t8v5y.css 4.61 kB │ gzip: 0.84 kB\n../public/vue-assets/assets/export-portal-CyIUVTEe.css 5.34 kB │ gzip: 1.65 kB\n../public/vue-assets/assets/InputField-C4hoKJ7Z.css 6.13 kB │ gzip: 1.27 kB\n../public/vue-assets/assets/vue-mq-bh4L87Tr.css 6.64 kB │ gzip: 1.95 kB\n../public/vue-assets/assets/invitation-EPR1t-bH.css 6.74 kB │ gzip: 2.22 kB\n../public/vue-assets/assets/ondemand-C2m0YVGA.css 6.84 kB │ gzip: 2.02 kB\n../public/vue-assets/assets/AskAnything-CyvEhHS1.css 8.60 kB │ gzip: 2.60 kB\n../public/vue-assets/assets/AppButton-BIeoar_U.css 8.88 kB │ gzip: 2.04 kB\n../public/vue-assets/assets/kiosk-BcheyWWL.css 9.56 kB │ gzip: 2.53 kB\n../public/vue-assets/assets/AiCrmNotes-nNTe_mOY.css 10.62 kB │ gzip: 1.93 kB\n../public/vue-assets/assets/deal-view-DJzG7PLp.css 11.70 kB │ gzip: 3.29 kB\n../public/vue-assets/assets/tokens-DZgWrfKd.css 12.44 kB │ gzip: 2.82 kB\n../public/vue-assets/assets/tokens-DL2BPbai.css 12.89 kB │ gzip: 2.87 kB\n../public/vue-assets/assets/playlists-Cy3t3kYU.css 13.54 kB │ gzip: 3.16 kB\n../public/vue-assets/assets/PhoneField-D_kGOah0.css 13.75 kB │ gzip: 3.44 kB\n../public/vue-assets/assets/ListView-Dt0qkyuY.css 17.07 kB │ gzip: 4.11 kB\n../public/vue-assets/assets/intl-tel-input-DgmgTINs.css 17.11 kB │ gzip: 5.37 kB\n../public/vue-assets/assets/AppFormField-DvpfRzi8.css 18.98 kB │ gzip: 4.42 kB\n../public/vue-assets/assets/OrgSettingsLayout-BogdXtgY.css 21.98 kB │ gzip: 5.51 kB\n../public/vue-assets/assets/dashboard-DQw3TKyk.css 22.74 kB │ gzip: 4.35 kB\n../public/vue-assets/assets/deal-insights-CfX3UNzh.css 29.66 kB │ gzip: 6.44 kB\n../public/vue-assets/assets/team-insights-CjVhm0JN.css 42.31 kB │ gzip: 8.46 kB\n../public/vue-assets/assets/playback-CvJP5tX1.css 44.55 kB │ gzip: 10.36 kB\n../public/vue-assets/assets/video-js-skin-DYZluGb-.css 47.73 kB │ gzip: 12.63 kB\n../public/vue-assets/assets/assets-CAbfI4CY.css 83.38 kB │ gzip: 51.71 kB\n../public/vue-assets/assets/logged-in-layout-1mAqkmnS.css 153.67 kB │ gzip: 27.18 kB\n../public/vue-assets/assets/assets-xH6dx_9q.css 259.71 kB │ gzip: 181.71 kB\n../public/vue-assets/assets/directives-DJJeJJOP.js 0.53 kB │ gzip: 0.36 kB │ map: 0.32 kB\n../public/vue-assets/assets/jenesius-vue-modal-BuBhyl83.js 0.54 kB │ gzip: 0.37 kB │ map: 0.47 kB\n../public/vue-assets/assets/spark-D_-Wgfar.js 0.54 kB │ gzip: 0.35 kB │ map: 0.40 kB\n../public/vue-assets/assets/url-messenger-_CGQa-lH.js 0.56 kB │ gzip: 0.37 kB │ map: 0.46 kB\n../public/vue-assets/assets/wavy-bg-Cec-_GDj.js 0.57 kB │ gzip: 0.36 kB │ map: 0.35 kB\n../public/vue-assets/assets/component-css-class-CtO0AVgW.js 0.61 kB │ gzip: 0.40 kB │ map: 0.91 kB\n../public/vue-assets/assets/theme-B5VdyMN_.js 0.63 kB │ gzip: 0.41 kB │ map: 0.49 kB\n../public/vue-assets/assets/pick-C8Pw7kHx.js 0.71 kB │ gzip: 0.45 kB │ map: 1.59 kB\n../public/vue-assets/assets/utils-BjsEoLDB.js 0.77 kB │ gzip: 0.50 kB │ map: 1.68 kB\n../public/vue-assets/assets/throttle-siTOKEMt.js 0.78 kB │ gzip: 0.49 kB │ map: 3.24 kB\n../public/vue-assets/assets/lastFilters-B_B_O5LY.js 0.80 kB │ gzip: 0.51 kB │ map: 1.12 kB\n../public/vue-assets/assets/useAuthState-Bpx8WpBc.js 0.81 kB │ gzip: 0.50 kB │ map: 1.64 kB\n../public/vue-assets/assets/v-focus-618yf9WO.js 0.85 kB │ gzip: 0.52 kB │ map: 1.42 kB\n../public/vue-assets/assets/ListLoader-bfQmkVGu.js 0.87 kB │ gzip: 0.54 kB │ map: 2.90 kB\n../public/vue-assets/assets/utils-CAMifZzY.js 0.88 kB │ gzip: 0.53 kB │ map: 2.45 kB\n../public/vue-assets/assets/mobileApp-QFLS3kId.js 0.89 kB │ gzip: 0.54 kB │ map: 1.12 kB\n../public/vue-assets/assets/pickBy-CsQ1aO-4.js 0.95 kB │ gzip: 0.59 kB │ map: 2.57 kB\n../public/vue-assets/assets/BuildInfo-CIGre86L.js 1.00 kB │ gzip: 0.65 kB │ map: 1.48 kB\n../public/vue-assets/assets/LogoShort100-D_SQdBYB.js 1.01 kB │ gzip: 0.64 kB │ map: 1.23 kB\n../public/vue-assets/assets/useDrawerModal-CmhXI-pZ.js 1.04 kB │ gzip: 0.65 kB │ map: 1.94 kB\n../public/vue-assets/assets/_getAllKeysIn-CkMQAnJa.js 1.12 kB │ gzip: 0.67 kB │ map: 4.83 kB\n../public/vue-assets/assets/useAutosizeTextarea-DyMwh_20.js 1.13 kB │ gzip: 0.70 kB │ map: 3.16 kB\n../public/vue-assets/assets/planhat-DPA36Hcn.js 1.21 kB │ gzip: 0.71 kB │ map: 2.18 kB\n../public/vue-assets/assets/useStoreModule-Cx6UoGVJ.js 1.35 kB │ gzip: 0.72 kB │ map: 6.03 kB\n../public/vue-assets/assets/GenericMessage-BxGsrfvw.js 1.39 kB │ gzip: 0.75 kB │ map: 1.90 kB\n../public/vue-assets/assets/BrowserExtensionInstaller-DimyBPIi.js 1.45 kB │ gzip: 0.78 kB │ map: 2.05 kB\n../public/vue-assets/assets/debounce-7ycH7TWx.js 1.53 kB │ gzip: 0.83 kB │ map: 8.61 kB\n../public/vue-assets/assets/extension-installed-DUcV_uoi.js 1.60 kB │ gzip: 0.91 kB │ map: 3.20 kB\n../public/vue-assets/assets/RecipientsCell-BS9gDGey.js 1.61 kB │ gzip: 0.93 kB │ map: 4.04 kB\n../public/vue-assets/assets/PrimaryButton-DaKR3UAG.js 1.61 kB │ gzip: 0.87 kB │ map: 3.73 kB\n../public/vue-assets/assets/KioskBanner-CYQu1FG7.js 1.66 kB │ gzip: 1.00 kB │ map: 2.88 kB\n../public/vue-assets/assets/LogoLong100-Hid0kvjR.js 1.78 kB │ gzip: 1.08 kB │ map: 5.91 kB\n../public/vue-assets/assets/GoogleLikeButton-CGFy3nbz.js 1.80 kB │ gzip: 0.84 kB │ map: 4.71 kB\n../public/vue-assets/assets/AppAlert-DFp0nftl.js 1.86 kB │ gzip: 1.01 kB │ map: 6.51 kB\n../public/vue-assets/assets/usePusherEventListener-BjhdeObt.js 1.93 kB │ gzip: 1.08 kB │ map: 9.59 kB\n../public/vue-assets/assets/AppForm-BgtH5xsM.js 2.01 kB │ gzip: 1.10 kB │ map: 6.89 kB\n../public/vue-assets/assets/vee-validate-rules-IFeGZHb3.js 2.02 kB │ gzip: 0.89 kB │ map: 22.31 kB\n../public/vue-assets/assets/literals-CXSwYC8y.js 2.05 kB │ gzip: 0.97 kB │ map: 3.08 kB\n../public/vue-assets/assets/TabEmptyState-pk8vRxJt.js 2.16 kB │ gzip: 1.12 kB │ map: 8.26 kB\n../public/vue-assets/assets/Replies-B2cExxmx.js 2.23 kB │ gzip: 1.14 kB │ map: 2.40 kB\n../public/vue-assets/assets/settings--S7_RYRE.js 2.34 kB │ gzip: 1.24 kB │ map: 1.73 kB\n../public/vue-assets/assets/SeekBtn-CMp8sSUA.js 2.47 kB │ gzip: 1.18 kB │ map: 9.27 kB\n../public/vue-assets/assets/AiAutomation-cuK1068l.js 2.59 kB │ gzip: 1.37 kB │ map: 4.45 kB\n../public/vue-assets/assets/locked-DxPLOs1S.js 2.59 kB │ gzip: 1.42 kB │ map: 4.00 kB\n../public/vue-assets/assets/DrawerWidget-BPC-tCgo.js 2.68 kB │ gzip: 1.33 kB │ map: 6.95 kB\n../public/vue-assets/assets/vue-infinite-scroll-D0cI4gH6.js 2.72 kB │ gzip: 1.28 kB │ map: 9.99 kB\n../public/vue-assets/assets/useActivityCustomerName-CYuaZj-p.js 2.87 kB │ gzip: 1.41 kB │ map: 6.27 kB\n../public/vue-assets/assets/other-CVxaD0_B.js 2.97 kB │ gzip: 1.50 kB │ map: 3.38 kB\n../public/vue-assets/assets/InputDropdown-CBM8xiPT.js 3.33 kB │ gzip: 1.61 kB │ map: 6.02 kB\n../public/vue-assets/assets/AvatarsStack-hxex9Ic8.js 3.40 kB │ gzip: 1.51 kB │ map: 9.54 kB\n../public/vue-assets/assets/activity-preview--ydYatRe.js 3.45 kB │ gzip: 1.68 kB │ map: 10.11 kB\n../public/vue-assets/assets/FollowModal-SMYvVwqG.js 3.70 kB │ gzip: 1.81 kB │ map: 8.86 kB\n../public/vue-assets/assets/softphone-coach-CkLTUCy6.js 3.74 kB │ gzip: 1.96 kB │ map: 5.03 kB\n../public/vue-assets/assets/store-DPy-uEfS.js 4.10 kB │ gzip: 2.00 kB │ map: 18.73 kB\n../public/vue-assets/assets/AiContext-D36CpnKB.js 4.34 kB │ gzip: 2.08 kB │ map: 13.11 kB\n../public/vue-assets/assets/meeting-consent-C0YRJ_xR.js 4.43 kB │ gzip: 2.05 kB │ map: 9.72 kB\n../public/vue-assets/assets/vue-multiselect-VB2Agtp1.js 4.49 kB │ gzip: 1.93 kB │ map: 14.85 kB\n../public/vue-assets/assets/snackbarNotifications-DjDIGkWd.js 4.83 kB │ gzip: 1.58 kB │ map: 11.62 kB\n../public/vue-assets/assets/invitation--7sddGcP.js 4.88 kB │ gzip: 2.21 kB │ map: 19.38 kB\n../public/vue-assets/assets/connect-Bfq7LjjQ.js 5.17 kB │ gzip: 2.28 kB │ map: 10.35 kB\n../public/vue-assets/assets/textarea-caret-B2HdIdpZ.js 5.25 kB │ gzip: 2.41 kB │ map: 14.48 kB\n../public/vue-assets/assets/snackbar-Bq-YQ7pz.js 5.48 kB │ gzip: 2.37 kB │ map: 20.43 kB\n../public/vue-assets/assets/RadioField-2SeSZB7D.js 5.65 kB │ gzip: 2.22 kB │ map: 15.82 kB\n../public/vue-assets/assets/filters-D_eXZdIL.js 5.76 kB │ gzip: 2.17 kB │ map: 16.36 kB\n../public/vue-assets/assets/useActivityHelper-Dj5DULYF.js 5.84 kB │ gzip: 2.51 kB │ map: 14.26 kB\n../public/vue-assets/assets/vue-scrollto-ul3pFdrb.js 5.97 kB │ gzip: 2.72 kB │ map: 23.98 kB\n../public/vue-assets/assets/join-conference-gY3HLgqP.js 6.25 kB │ gzip: 2.83 kB │ map: 24.66 kB\n../public/vue-assets/assets/pluralize-BeKVJaE0.js 6.34 kB │ gzip: 2.70 kB │ map: 18.28 kB\n../public/vue-assets/assets/login-Bz_Gi9sn.js 7.02 kB │ gzip: 3.16 kB │ map: 17.51 kB\n../public/vue-assets/assets/AppLinks-Bg20Hslb.js 7.14 kB │ gzip: 2.80 kB │ map: 14.87 kB\n../public/vue-assets/assets/InputField-CkdrqKBp.js 7.20 kB │ gzip: 2.58 kB │ map: 19.39 kB\n../public/vue-assets/assets/UserAvatar-UKkSAS4R.js 7.93 kB │ gzip: 3.32 kB │ map: 34.91 kB\n../public/vue-assets/assets/InputText-Dgwsn6KA.js 7.99 kB │ gzip: 3.05 kB │ map: 20.64 kB\n../public/vue-assets/assets/AiCrmNotes-DNS51mtN.js 8.27 kB │ gzip: 3.32 kB │ map: 34.03 kB\n../public/vue-assets/assets/vue-mq-CmpUzQtD.js 8.84 kB │ gzip: 3.54 kB │ map: 23.69 kB\n../public/vue-assets/assets/pro-duotone-svg-icons-BSBZV3-t.js 9.51 kB │ gzip: 3.43 kB │ map: 3,146.68 kB\n../public/vue-assets/assets/activity-preview-result-CIGPrkbw.js 10.65 kB │ gzip: 3.98 kB │ map: 38.51 kB\n../public/vue-assets/assets/pro-regular-svg-icons-CWpSBR20.js 13.10 kB │ gzip: 4.64 kB │ map: 3,178.15 kB\n../public/vue-assets/assets/add-to-playlist-modal-CPwNAOXA.js 14.99 kB │ gzip: 5.98 kB │ map: 51.34 kB\n../public/vue-assets/assets/ai-reports-DAEn44FQ.js 15.17 kB │ gzip: 6.04 kB │ map: 51.85 kB\n../public/vue-assets/assets/export-portal-DB0CksQW.js 15.68 kB │ gzip: 6.22 kB │ map: 52.78 kB\n../public/vue-assets/assets/Comment-ClBvXCXq.js 15.92 kB │ gzip: 5.72 kB │ map: 39.96 kB\n../public/vue-assets/assets/useragent-DQ4F_O30.js 18.02 kB │ gzip: 8.06 kB │ map: 68.31 kB\n../public/vue-assets/assets/linkify-B_q6MgwI.js 18.58 kB │ gzip: 10.37 kB │ map: 88.73 kB\n../public/vue-assets/assets/ai-reports-manage-BTQmbI4-.js 18.92 kB │ gzip: 6.76 kB │ map: 66.14 kB\n../public/vue-assets/assets/dist-C2hisF0L.js 19.32 kB │ gzip: 7.87 kB │ map: 390.77 kB\n../public/vue-assets/assets/pro-solid-svg-icons-DBOkpln3.js 19.46 kB │ gzip: 6.72 kB │ map: 2,692.14 kB\n../public/vue-assets/assets/purify.es-BxOw6oE6.js 21.49 kB │ gzip: 8.71 kB │ map: 83.43 kB\n../public/vue-assets/assets/ActionItems-CqgLpVFE.js 21.92 kB │ gzip: 8.54 kB │ map: 76.05 kB\n../public/vue-assets/assets/mobile-4e6kQ9l_.js 23.34 kB │ gzip: 9.71 kB │ map: 50.90 kB\n../public/vue-assets/assets/basic-modal-BdXsnFwC.js 23.87 kB │ gzip: 9.06 kB │ map: 64.15 kB\n../public/vue-assets/assets/GridView-CocIYUaz.js 26.60 kB │ gzip: 10.05 kB │ map: 92.74 kB\n../public/vue-assets/assets/ondemand-CE8XLH98.js 26.88 kB │ gzip: 9.38 kB │ map: 73.94 kB\n../public/vue-assets/assets/CrmLink-DKYsnHnx.js 27.91 kB │ gzip: 10.18 kB │ map: 93.18 kB\n../public/vue-assets/assets/liquor-tree-COUefof4.js 30.75 kB │ gzip: 9.58 kB │ map: 78.74 kB\n../public/vue-assets/assets/DealRiskList-3DSxnTNQ.js 34.39 kB │ gzip: 10.62 kB │ map: 115.18 kB\n../public/vue-assets/assets/AskAnything-BQ36EoGS.js 39.50 kB │ gzip: 14.97 kB │ map: 173.20 kB\n../public/vue-assets/assets/lib-CwM9toD2.js 39.69 kB │ gzip: 12.70 kB │ map: 138.34 kB\n../public/vue-assets/assets/AppFormField-CqCXZwrk.js 41.91 kB │ gzip: 12.69 kB │ map: 150.73 kB\n../public/vue-assets/assets/deal-view-B92qaj32.js 43.22 kB │ gzip: 14.34 kB │ map: 150.62 kB\n../public/vue-assets/assets/exports-D1lmea4O.js 47.84 kB │ gzip: 16.46 kB │ map: 294.48 kB\n../public/vue-assets/assets/playlists-u3UeUUz5.js 48.28 kB │ gzip: 15.07 kB │ map: 153.25 kB\n../public/vue-assets/assets/callScoringTemplates-zeRn40uL.js 55.13 kB │ gzip: 13.28 kB │ map: 65.85 kB\n../public/vue-assets/assets/_copyObject-USkOnlaQ.js 61.28 kB │ gzip: 20.09 kB │ map: 239.59 kB\n../public/vue-assets/assets/pusher-znYCfz7U.js 62.98 kB │ gzip: 18.88 kB │ map: 219.27 kB\n../public/vue-assets/assets/onboard-CCnYJmCP.js 63.11 kB │ gzip: 21.85 kB │ map: 201.38 kB\n../public/vue-assets/assets/StatusBadge-CNGFZm8P.js 64.66 kB │ gzip: 22.96 kB │ map: 244.72 kB\n../public/vue-assets/assets/kiosk-D6N5-ivP.js 79.60 kB │ gzip: 22.65 kB │ map: 300.68 kB\n../public/vue-assets/assets/preload-helper-DCvhahzG.js 82.59 kB │ gzip: 27.46 kB │ map: 3,452.35 kB\n../public/vue-assets/assets/deal-insights-jlkz65MZ.js 94.84 kB │ gzip: 28.16 kB │ map: 292.79 kB\n../public/vue-assets/assets/ListView-DmKqYVi1.js 115.71 kB │ gzip: 33.78 kB │ map: 308.10 kB\n../public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js 117.59 kB │ gzip: 38.71 kB │ map: 500.60 kB\n../public/vue-assets/assets/WelcomeLayout-B6wd32HG.js 120.67 kB │ gzip: 34.16 kB │ map: 258.56 kB\n../public/vue-assets/assets/dashboard-BWoBIPKA.js 128.71 kB │ gzip: 40.05 kB │ map: 410.48 kB\n../public/vue-assets/assets/emoji-input-CSq87OVy.js 129.28 kB │ gzip: 36.72 kB │ map: 266.15 kB\n../public/vue-assets/assets/AppButton-D3qMdODr.js 133.44 kB │ gzip: 43.03 kB │ map: 516.67 kB\n../public/vue-assets/assets/sentry-icPKypZa.js 164.28 kB │ gzip: 52.24 kB │ map: 831.82 kB\n../public/vue-assets/assets/OrgSettingsLayout-BZ-J0giH.js 176.33 kB │ gzip: 56.15 kB │ map: 623.43 kB\n../public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js 180.40 kB │ gzip: 67.85 kB │ map: 836.88 kB\n../public/vue-assets/assets/playback-DKsqp_aL.js 198.79 kB │ gzip: 61.85 kB │ map: 684.87 kB\n../public/vue-assets/assets/index.module-Bjlhgfd1.js 218.14 kB │ gzip: 64.16 kB │ map: 1,108.20 kB\n../public/vue-assets/assets/intl-tel-input-BW4mv40Q.js 264.94 kB │ gzip: 60.31 kB │ map: 475.61 kB\n../public/vue-assets/assets/team-insights-D9P6ojjD.js 298.57 kB │ gzip: 77.22 kB │ map: 959.96 kB\n../public/vue-assets/assets/popper-CQwVcrX4.js 307.13 kB │ gzip: 103.86 kB │ map: 1,245.28 kB\n../public/vue-assets/assets/PhoneField-CwCIoAYm.js 343.99 kB │ gzip: 84.90 kB │ map: 849.05 kB\n../public/vue-assets/assets/live-DWumv-QD.js 367.43 kB │ gzip: 97.05 kB │ map: 792.41 kB\n../public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BNO485xV.js 689.63 kB │ gzip: 202.81 kB │ map: 3,016.64 kB\n../public/vue-assets/assets/index-DiEs6xIb.js 825.23 kB │ gzip: 72.54 kB │ map: 436.62 kB\n../public/vue-assets/assets/logged-in-layout-Cq1ztH5O.js 1,402.70 kB │ gzip: 438.08 kB │ map: 6,283.55 kB\n\n[plugin builtin:vite-reporter] \n(!) Some chunks are larger than 500 kB after minification. Consider:\n- Using dynamic import() to code-split the application\n- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting\n- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.\n✓ built in 13.59s\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yarn build\n[dotenv@17.3.1] injecting env (0) from .env -- tip: ⚙️\u0000 load multiple .env files with { path: ['.env.local', '.env'] }\nvite v8.0.0 building client environment for production...\n✓ 4656 modules transformed.\n[sentry-vite-plugin] Warning: No auth token provided. Will not create release. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\n[sentry-vite-plugin] Warning: No auth token provided. Will not upload source maps. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\ncomputing gzip size...\n../public/vue-assets/index.html 4.58 kB │ gzip: 1.16 kB\n../public/vue-assets/assets/job-adder-B2LcgC4o.png 5.41 kB\n../public/vue-assets/assets/dixa-DzT89tKh.png 7.87 kB\n../public/vue-assets/assets/planhat-CQycOTMW.svg 8.61 kB │ gzip: 3.48 kB\n../public/vue-assets/assets/funding-circle-9iGnDGsz.png 9.43 kB\n../public/vue-assets/assets/cision-uOdx_YiN.svg 14.41 kB │ gzip: 6.32 kB\n../public/vue-assets/assets/les-mills-DWabpmYc.png 15.73 kB\n../public/vue-assets/assets/superside-btT37L9L.png 26.69 kB\n../public/vue-assets/assets/flags-a2kmUSbF.webp 28.18 kB\n../public/vue-assets/assets/quinyx-RGQNHx6o.png 63.17 kB\n../public/vue-assets/assets/flags@2x-gR6KPp3x.webp 66.44 kB\n../public/vue-assets/assets/bg-marketing-CAIHIJMj.png 70.66 kB\n../public/vue-assets/.vite/manifest.json 85.73 kB │ gzip: 8.33 kB\n../public/vue-assets/assets/jiminny-score-DtTbKZaD.svg 131.93 kB │ gzip: 86.07 kB\n../public/vue-assets/assets/wavy-bg-vnCaCm9m.webm 670.97 kB\n../public/vue-assets/assets/wavy-bg-Op6dRIjR.mp4 14,484.63 kB\n../public/vue-assets/assets/AppForm-EEGPL5K8.css 0.04 kB │ gzip: 0.06 kB\n../public/vue-assets/assets/RecipientsCell-DNOSS8k1.css 0.12 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/softphone-coach-Cy_9nuQZ.css 0.13 kB │ gzip: 0.10 kB\n../public/vue-assets/assets/locked-CHS8VPur.css 0.15 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/mobile-KPnGhnTp.css 0.16 kB │ gzip: 0.14 kB\n../public/vue-assets/assets/BuildInfo-CxNQv_OV.css 0.20 kB │ gzip: 0.17 kB\n../public/vue-assets/assets/AvatarsStack-C0Nvfh1V.css 0.28 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/LogoLong100-ByQTvUY3.css 0.32 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/connect--xBDzLbc.css 0.34 kB │ gzip: 0.21 kB\n../public/vue-assets/assets/KioskBanner-xU3FRd8g.css 0.41 kB │ gzip: 0.27 kB\n../public/vue-assets/assets/extension-installed-BPBhG13P.css 0.41 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/FollowModal-A0rJmIOK.css 0.45 kB │ gzip: 0.23 kB\n../public/vue-assets/assets/GenericMessage-DutAlObK.css 0.49 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/textarea-caret-DCiNUER_.css 0.51 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/AppLinks-BznfGcBR.css 0.58 kB │ gzip: 0.33 kB\n../public/vue-assets/assets/SeekBtn-_VMPIk6v.css 0.63 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/emoji-input-kJWRbWf8.css 0.68 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/activity-preview-BDxP2S0-.css 0.70 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/ListLoader-DQqAw-8s.css 0.73 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/filters-Bgb6cQD6.css 0.75 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/AiContext-Bg-zjjbA.css 0.99 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/vue3-daterange-picker-izPOBj3P.css 1.02 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/PrimaryButton-CJj2FzKS.css 1.08 kB │ gzip: 0.34 kB\n../public/vue-assets/assets/other-BTuPXtu_.css 1.15 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/jenesius-vue-modal-DDcfejHO.css 1.17 kB │ gzip: 0.48 kB\n../public/vue-assets/assets/TabEmptyState-DOlIO68k.css 1.21 kB │ gzip: 0.53 kB\n../public/vue-assets/assets/AppAlert-BlSJKiqj.css 1.29 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/useActivityCustomerName-CBjfIUfJ.css 1.42 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/UserAvatar-co8l9mMk.css 1.51 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/basic-modal-Dx7ZBbcA.css 1.55 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/login-DYbcBesM.css 1.58 kB │ gzip: 0.61 kB\n../public/vue-assets/assets/DrawerWidget-poAmqspk.css 1.67 kB │ gzip: 0.69 kB\n../public/vue-assets/assets/ActionItems-5FkZSo3G.css 1.79 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/join-conference-DQ6Yl7eU.css 1.82 kB │ gzip: 0.75 kB\n../public/vue-assets/assets/InputText-C-DxDNSV.css 1.95 kB │ gzip: 0.71 kB\n../public/vue-assets/assets/GoogleLikeButton-DvQHxbqR.css 2.11 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/usePusherEventListener-CDj8aSNp.css 2.20 kB │ gzip: 0.73 kB\n../public/vue-assets/assets/onboard-IzGiQ-EC.css 2.32 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/ai-reports-manage-DaynTl62.css 2.34 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/StatusBadge-B4N12dzU.css 2.35 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/GridView-CsP1Jk-k.css 2.36 kB │ gzip: 0.90 kB\n../public/vue-assets/assets/WelcomeLayout-6AX86p6F.css 2.38 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/snackbar-CS_iqvrv.css 2.45 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/ai-reports-mtqGqPPq.css 2.47 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/Comment-gmAfiuer.css 2.63 kB │ gzip: 1.00 kB\n../public/vue-assets/assets/meeting-consent-BasRvxE3.css 2.66 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/DealRiskList-CwxmDnSC.css 2.67 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/add-to-playlist-modal-C_ad1hht.css 2.84 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/live-_zT5qBc1.css 3.60 kB │ gzip: 1.16 kB\n../public/vue-assets/assets/RadioField-DDp8Y71G.css 3.64 kB │ gzip: 1.20 kB\n../public/vue-assets/assets/vue-multiselect-BkQNV7oL.css 3.80 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/activity-preview-result-Db1Da81T.css 3.89 kB │ gzip: 1.15 kB\n../public/vue-assets/assets/liquor-tree-CB5oh8v1.css 4.08 kB │ gzip: 1.24 kB\n../public/vue-assets/assets/sentry-CSjHcvmn.css 4.47 kB │ gzip: 1.08 kB\n../public/vue-assets/assets/AiAutomation-Cj1t8v5y.css 4.61 kB │ gzip: 0.84 kB\n../public/vue-assets/assets/export-portal-CyIUVTEe.css 5.34 kB │ gzip: 1.65 kB\n../public/vue-assets/assets/InputField-C4hoKJ7Z.css 6.13 kB │ gzip: 1.27 kB\n../public/vue-assets/assets/vue-mq-bh4L87Tr.css 6.64 kB │ gzip: 1.95 kB\n../public/vue-assets/assets/invitation-EPR1t-bH.css 6.74 kB │ gzip: 2.22 kB\n../public/vue-assets/assets/ondemand-C2m0YVGA.css 6.84 kB │ gzip: 2.02 kB\n../public/vue-assets/assets/AskAnything-CyvEhHS1.css 8.60 kB │ gzip: 2.60 kB\n../public/vue-assets/assets/AppButton-BIeoar_U.css 8.88 kB │ gzip: 2.04 kB\n../public/vue-assets/assets/kiosk-BcheyWWL.css 9.56 kB │ gzip: 2.53 kB\n../public/vue-assets/assets/AiCrmNotes-nNTe_mOY.css 10.62 kB │ gzip: 1.93 kB\n../public/vue-assets/assets/deal-view-DJzG7PLp.css 11.70 kB │ gzip: 3.29 kB\n../public/vue-assets/assets/tokens-DZgWrfKd.css 12.44 kB │ gzip: 2.82 kB\n../public/vue-assets/assets/tokens-DL2BPbai.css 12.89 kB │ gzip: 2.87 kB\n../public/vue-assets/assets/playlists-Cy3t3kYU.css 13.54 kB │ gzip: 3.16 kB\n../public/vue-assets/assets/PhoneField-D_kGOah0.css 13.75 kB │ gzip: 3.44 kB\n../public/vue-assets/assets/ListView-Dt0qkyuY.css 17.07 kB │ gzip: 4.11 kB\n../public/vue-assets/assets/intl-tel-input-DgmgTINs.css 17.11 kB │ gzip: 5.37 kB\n../public/vue-assets/assets/AppFormField-DvpfRzi8.css 18.98 kB │ gzip: 4.42 kB\n../public/vue-assets/assets/OrgSettingsLayout-BogdXtgY.css 21.98 kB │ gzip: 5.51 kB\n../public/vue-assets/assets/dashboard-DQw3TKyk.css 22.74 kB │ gzip: 4.35 kB\n../public/vue-assets/assets/deal-insights-CfX3UNzh.css 29.66 kB │ gzip: 6.44 kB\n../public/vue-assets/assets/team-insights-CjVhm0JN.css 42.31 kB │ gzip: 8.46 kB\n../public/vue-assets/assets/playback-CvJP5tX1.css 44.55 kB │ gzip: 10.36 kB\n../public/vue-assets/assets/video-js-skin-DYZluGb-.css 47.73 kB │ gzip: 12.63 kB\n../public/vue-assets/assets/assets-CAbfI4CY.css 83.38 kB │ gzip: 51.71 kB\n../public/vue-assets/assets/logged-in-layout-1mAqkmnS.css 153.67 kB │ gzip: 27.18 kB\n../public/vue-assets/assets/assets-xH6dx_9q.css 259.71 kB │ gzip: 181.71 kB\n../public/vue-assets/assets/directives-DJJeJJOP.js 0.53 kB │ gzip: 0.36 kB │ map: 0.32 kB\n../public/vue-assets/assets/jenesius-vue-modal-BuBhyl83.js 0.54 kB │ gzip: 0.37 kB │ map: 0.47 kB\n../public/vue-assets/assets/spark-D_-Wgfar.js 0.54 kB │ gzip: 0.35 kB │ map: 0.40 kB\n../public/vue-assets/assets/url-messenger-_CGQa-lH.js 0.56 kB │ gzip: 0.37 kB │ map: 0.46 kB\n../public/vue-assets/assets/wavy-bg-Cec-_GDj.js 0.57 kB │ gzip: 0.36 kB │ map: 0.35 kB\n../public/vue-assets/assets/component-css-class-CtO0AVgW.js 0.61 kB │ gzip: 0.40 kB │ map: 0.91 kB\n../public/vue-assets/assets/theme-CrWye7yo.js 0.63 kB │ gzip: 0.41 kB │ map: 0.49 kB\n../public/vue-assets/assets/pick-D-kTQpNV.js 0.71 kB │ gzip: 0.45 kB │ map: 1.59 kB\n../public/vue-assets/assets/utils-BjsEoLDB.js 0.77 kB │ gzip: 0.50 kB │ map: 1.68 kB\n../public/vue-assets/assets/throttle-CEV1oVAM.js 0.78 kB │ gzip: 0.49 kB │ map: 3.24 kB\n../public/vue-assets/assets/lastFilters-D3xNTqAt.js 0.80 kB │ gzip: 0.51 kB │ map: 1.12 kB\n../public/vue-assets/assets/useAuthState-Bpx8WpBc.js 0.81 kB │ gzip: 0.50 kB │ map: 1.64 kB\n../public/vue-assets/assets/v-focus-618yf9WO.js 0.85 kB │ gzip: 0.52 kB │ map: 1.42 kB\n../public/vue-assets/assets/ListLoader-bfQmkVGu.js 0.87 kB │ gzip: 0.54 kB │ map: 2.90 kB\n../public/vue-assets/assets/utils-CAMifZzY.js 0.88 kB │ gzip: 0.53 kB │ map: 2.45 kB\n../public/vue-assets/assets/mobileApp-QFLS3kId.js 0.89 kB │ gzip: 0.54 kB │ map: 1.12 kB\n../public/vue-assets/assets/pickBy-DftTc1_7.js 0.95 kB │ gzip: 0.59 kB │ map: 2.57 kB\n../public/vue-assets/assets/BuildInfo-CIGre86L.js 1.00 kB │ gzip: 0.65 kB │ map: 1.48 kB\n../public/vue-assets/assets/LogoShort100-D_SQdBYB.js 1.01 kB │ gzip: 0.64 kB │ map: 1.23 kB\n../public/vue-assets/assets/useDrawerModal-CmhXI-pZ.js 1.04 kB │ gzip: 0.65 kB │ map: 1.94 kB\n../public/vue-assets/assets/_getAllKeysIn-CkMQAnJa.js 1.12 kB │ gzip: 0.67 kB │ map: 4.83 kB\n../public/vue-assets/assets/useAutosizeTextarea-DyMwh_20.js 1.13 kB │ gzip: 0.70 kB │ map: 3.16 kB\n../public/vue-assets/assets/planhat-DPA36Hcn.js 1.21 kB │ gzip: 0.71 kB │ map: 2.18 kB\n../public/vue-assets/assets/useStoreModule-Cx6UoGVJ.js 1.35 kB │ gzip: 0.72 kB │ map: 6.03 kB\n../public/vue-assets/assets/GenericMessage-BxGsrfvw.js 1.39 kB │ gzip: 0.75 kB │ map: 1.90 kB\n../public/vue-assets/assets/BrowserExtensionInstaller-DimyBPIi.js 1.45 kB │ gzip: 0.78 kB │ map: 2.05 kB\n../public/vue-assets/assets/debounce-CK0K0ozg.js 1.53 kB │ gzip: 0.83 kB │ map: 8.61 kB\n../public/vue-assets/assets/extension-installed-DLwqdmK8.js 1.60 kB │ gzip: 0.91 kB │ map: 3.20 kB\n../public/vue-assets/assets/RecipientsCell-BS9gDGey.js 1.61 kB │ gzip: 0.93 kB │ map: 4.04 kB\n../public/vue-assets/assets/PrimaryButton-DaKR3UAG.js 1.61 kB │ gzip: 0.87 kB │ map: 3.73 kB\n../public/vue-assets/assets/KioskBanner-D8zd6tGl.js 1.66 kB │ gzip: 1.00 kB │ map: 2.88 kB\n../public/vue-assets/assets/LogoLong100-Hid0kvjR.js 1.78 kB │ gzip: 1.08 kB │ map: 5.91 kB\n../public/vue-assets/assets/GoogleLikeButton-CGFy3nbz.js 1.80 kB │ gzip: 0.84 kB │ map: 4.71 kB\n../public/vue-assets/assets/AppAlert-DFp0nftl.js 1.86 kB │ gzip: 1.01 kB │ map: 6.51 kB\n../public/vue-assets/assets/usePusherEventListener-D8AEMpQD.js 1.93 kB │ gzip: 1.08 kB │ map: 9.59 kB\n../public/vue-assets/assets/AppForm-BgtH5xsM.js 2.01 kB │ gzip: 1.10 kB │ map: 6.89 kB\n../public/vue-assets/assets/vee-validate-rules-IFeGZHb3.js 2.02 kB │ gzip: 0.89 kB │ map: 22.31 kB\n../public/vue-assets/assets/literals-CXSwYC8y.js 2.05 kB │ gzip: 0.97 kB │ map: 3.08 kB\n../public/vue-assets/assets/TabEmptyState-pk8vRxJt.js 2.16 kB │ gzip: 1.12 kB │ map: 8.26 kB\n../public/vue-assets/assets/Replies-DZPkx2YQ.js 2.23 kB │ gzip: 1.14 kB │ map: 2.40 kB\n../public/vue-assets/assets/settings-DHjh0S4O.js 2.34 kB │ gzip: 1.24 kB │ map: 1.73 kB\n../public/vue-assets/assets/SeekBtn-CMp8sSUA.js 2.47 kB │ gzip: 1.18 kB │ map: 9.27 kB\n../public/vue-assets/assets/AiAutomation-CTc8r0pe.js 2.59 kB │ gzip: 1.37 kB │ map: 4.45 kB\n../public/vue-assets/assets/locked-CHodD5F8.js 2.59 kB │ gzip: 1.43 kB │ map: 4.00 kB\n../public/vue-assets/assets/DrawerWidget-BPC-tCgo.js 2.68 kB │ gzip: 1.33 kB │ map: 6.95 kB\n../public/vue-assets/assets/vue-infinite-scroll-D0cI4gH6.js 2.72 kB │ gzip: 1.28 kB │ map: 9.99 kB\n../public/vue-assets/assets/useActivityCustomerName-CYuaZj-p.js 2.87 kB │ gzip: 1.41 kB │ map: 6.27 kB\n../public/vue-assets/assets/other-CS9P7JeF.js 2.97 kB │ gzip: 1.50 kB │ map: 3.38 kB\n../public/vue-assets/assets/InputDropdown-CBM8xiPT.js 3.33 kB │ gzip: 1.61 kB │ map: 6.02 kB\n../public/vue-assets/assets/AvatarsStack-hxex9Ic8.js 3.40 kB │ gzip: 1.51 kB │ map: 9.54 kB\n../public/vue-assets/assets/activity-preview-Dkxw0lkq.js 3.45 kB │ gzip: 1.68 kB │ map: 10.11 kB\n../public/vue-assets/assets/FollowModal-SMYvVwqG.js 3.70 kB │ gzip: 1.81 kB │ map: 8.86 kB\n../public/vue-assets/assets/softphone-coach-BdRuUFYY.js 3.74 kB │ gzip: 1.96 kB │ map: 5.03 kB\n../public/vue-assets/assets/store-Cploop1A.js 4.10 kB │ gzip: 2.00 kB │ map: 18.73 kB\n../public/vue-assets/assets/AiContext-Cz-Y-wOJ.js 4.34 kB │ gzip: 2.08 kB │ map: 13.11 kB\n../public/vue-assets/assets/meeting-consent-DZKBS9eW.js 4.43 kB │ gzip: 2.05 kB │ map: 9.72 kB\n../public/vue-assets/assets/vue-multiselect-VB2Agtp1.js 4.49 kB │ gzip: 1.93 kB │ map: 14.85 kB\n../public/vue-assets/assets/snackbarNotifications-DjDIGkWd.js 4.83 kB │ gzip: 1.58 kB │ map: 11.62 kB\n../public/vue-assets/assets/invitation-DUWFWQvr.js 4.88 kB │ gzip: 2.21 kB │ map: 19.38 kB\n../public/vue-assets/assets/textarea-caret-B2HdIdpZ.js 5.25 kB │ gzip: 2.41 kB │ map: 14.48 kB\n../public/vue-assets/assets/connect-LR_dB_VC.js 5.25 kB │ gzip: 2.30 kB │ map: 10.52 kB\n../public/vue-assets/assets/snackbar-Bq-YQ7pz.js 5.48 kB │ gzip: 2.37 kB │ map: 20.43 kB\n../public/vue-assets/assets/RadioField-2SeSZB7D.js 5.65 kB │ gzip: 2.22 kB │ map: 15.82 kB\n../public/vue-assets/assets/filters-D_eXZdIL.js 5.76 kB │ gzip: 2.17 kB │ map: 16.36 kB\n../public/vue-assets/assets/useActivityHelper-C6KkT8e1.js 5.84 kB │ gzip: 2.51 kB │ map: 14.26 kB\n../public/vue-assets/assets/vue-scrollto-ul3pFdrb.js 5.97 kB │ gzip: 2.72 kB │ map: 23.98 kB\n../public/vue-assets/assets/join-conference-D5dWNU9i.js 6.25 kB │ gzip: 2.82 kB │ map: 24.66 kB\n../public/vue-assets/assets/pluralize-BeKVJaE0.js 6.34 kB │ gzip: 2.70 kB │ map: 18.28 kB\n../public/vue-assets/assets/login-DGWP8lD_.js 7.02 kB │ gzip: 3.16 kB │ map: 17.51 kB\n../public/vue-assets/assets/AppLinks-Bg20Hslb.js 7.14 kB │ gzip: 2.80 kB │ map: 14.87 kB\n../public/vue-assets/assets/InputField-CkdrqKBp.js 7.20 kB │ gzip: 2.58 kB │ map: 19.39 kB\n../public/vue-assets/assets/UserAvatar-UKkSAS4R.js 7.93 kB │ gzip: 3.32 kB │ map: 34.91 kB\n../public/vue-assets/assets/InputText-zafDvh_H.js 7.99 kB │ gzip: 3.05 kB │ map: 20.64 kB\n../public/vue-assets/assets/AiCrmNotes-Co9u0t34.js 8.27 kB │ gzip: 3.32 kB │ map: 34.03 kB\n../public/vue-assets/assets/vue-mq-CmpUzQtD.js 8.84 kB │ gzip: 3.54 kB │ map: 23.69 kB\n../public/vue-assets/assets/pro-duotone-svg-icons-BSBZV3-t.js 9.51 kB │ gzip: 3.43 kB │ map: 3,146.68 kB\n../public/vue-assets/assets/activity-preview-result-C820ZfzV.js 10.65 kB │ gzip: 3.98 kB │ map: 38.51 kB\n../public/vue-assets/assets/pro-regular-svg-icons-CWpSBR20.js 13.10 kB │ gzip: 4.64 kB │ map: 3,178.15 kB\n../public/vue-assets/assets/add-to-playlist-modal-CfDB_Ohb.js 14.99 kB │ gzip: 5.98 kB │ map: 51.34 kB\n../public/vue-assets/assets/ai-reports-BhbjZb-1.js 15.17 kB │ gzip: 6.04 kB │ map: 51.85 kB\n../public/vue-assets/assets/export-portal-BM3w9d6h.js 15.68 kB │ gzip: 6.22 kB │ map: 52.78 kB\n../public/vue-assets/assets/Comment-Kj7lCx0-.js 15.92 kB │ gzip: 5.72 kB │ map: 39.96 kB\n../public/vue-assets/assets/useragent-DQ4F_O30.js 18.02 kB │ gzip: 8.06 kB │ map: 68.31 kB\n../public/vue-assets/assets/linkify-B_q6MgwI.js 18.58 kB │ gzip: 10.37 kB │ map: 88.73 kB\n../public/vue-assets/assets/ai-reports-manage-ulzm9gk2.js 18.92 kB │ gzip: 6.76 kB │ map: 66.14 kB\n../public/vue-assets/assets/dist-C2hisF0L.js 19.32 kB │ gzip: 7.87 kB │ map: 390.77 kB\n../public/vue-assets/assets/pro-solid-svg-icons-DBOkpln3.js 19.46 kB │ gzip: 6.72 kB │ map: 2,692.14 kB\n../public/vue-assets/assets/purify.es-BxOw6oE6.js 21.49 kB │ gzip: 8.71 kB │ map: 83.43 kB\n../public/vue-assets/assets/ActionItems-C9INej-u.js 21.92 kB │ gzip: 8.54 kB │ map: 76.05 kB\n../public/vue-assets/assets/mobile-C-6FGhqy.js 23.34 kB │ gzip: 9.71 kB │ map: 50.90 kB\n../public/vue-assets/assets/basic-modal-BdXsnFwC.js 23.87 kB │ gzip: 9.06 kB │ map: 64.15 kB\n../public/vue-assets/assets/GridView-mZhlfeUe.js 26.60 kB │ gzip: 10.05 kB │ map: 92.74 kB\n../public/vue-assets/assets/ondemand-mq9zQOVK.js 26.88 kB │ gzip: 9.39 kB │ map: 73.94 kB\n../public/vue-assets/assets/CrmLink-DKYsnHnx.js 27.91 kB │ gzip: 10.18 kB │ map: 93.18 kB\n../public/vue-assets/assets/liquor-tree-COUefof4.js 30.75 kB │ gzip: 9.58 kB │ map: 78.74 kB\n../public/vue-assets/assets/DealRiskList-BUgW-21G.js 34.39 kB │ gzip: 10.62 kB │ map: 115.18 kB\n../public/vue-assets/assets/AskAnything-CVCNDjsB.js 39.50 kB │ gzip: 14.97 kB │ map: 173.20 kB\n../public/vue-assets/assets/lib-CwM9toD2.js 39.69 kB │ gzip: 12.70 kB │ map: 138.34 kB\n../public/vue-assets/assets/AppFormField-Cu4TbCsZ.js 41.91 kB │ gzip: 12.69 kB │ map: 150.73 kB\n../public/vue-assets/assets/deal-view-CoZZkbGt.js 43.22 kB │ gzip: 14.34 kB │ map: 150.62 kB\n../public/vue-assets/assets/exports-D1lmea4O.js 47.84 kB │ gzip: 16.46 kB │ map: 294.48 kB\n../public/vue-assets/assets/playlists-BpFWtNz1.js 48.28 kB │ gzip: 15.08 kB │ map: 153.25 kB\n../public/vue-assets/assets/callScoringTemplates-zeRn40uL.js 55.13 kB │ gzip: 13.28 kB │ map: 65.85 kB\n../public/vue-assets/assets/_copyObject-USkOnlaQ.js 61.28 kB │ gzip: 20.09 kB │ map: 239.59 kB\n../public/vue-assets/assets/pusher-znYCfz7U.js 62.98 kB │ gzip: 18.88 kB │ map: 219.27 kB\n../public/vue-assets/assets/onboard-D27tiAEp.js 63.11 kB │ gzip: 21.85 kB │ map: 201.38 kB\n../public/vue-assets/assets/StatusBadge-Dmn6sJ5_.js 64.66 kB │ gzip: 22.96 kB │ map: 244.72 kB\n../public/vue-assets/assets/kiosk-DO4wa1Ld.js 79.60 kB │ gzip: 22.65 kB │ map: 300.68 kB\n../public/vue-assets/assets/preload-helper-DCvhahzG.js 82.59 kB │ gzip: 27.46 kB │ map: 3,452.35 kB\n../public/vue-assets/assets/deal-insights-BKxR7Ag9.js 94.84 kB │ gzip: 28.17 kB │ map: 292.79 kB\n../public/vue-assets/assets/ListView-V1cngeeb.js 115.71 kB │ gzip: 33.78 kB │ map: 308.10 kB\n../public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js 117.59 kB │ gzip: 38.71 kB │ map: 500.60 kB\n../public/vue-assets/assets/WelcomeLayout-B6wd32HG.js 120.67 kB │ gzip: 34.16 kB │ map: 258.56 kB\n../public/vue-assets/assets/dashboard-D0nLYfJh.js 128.71 kB │ gzip: 40.05 kB │ map: 410.48 kB\n../public/vue-assets/assets/emoji-input-CSq87OVy.js 129.28 kB │ gzip: 36.72 kB │ map: 266.15 kB\n../public/vue-assets/assets/AppButton-D3qMdODr.js 133.44 kB │ gzip: 43.03 kB │ map: 516.67 kB\n../public/vue-assets/assets/sentry-zRF85Jni.js 164.28 kB │ gzip: 52.24 kB │ map: 831.82 kB\n../public/vue-assets/assets/OrgSettingsLayout-DXmpAhyV.js 176.33 kB │ gzip: 56.15 kB │ map: 623.43 kB\n../public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js 180.40 kB │ gzip: 67.85 kB │ map: 836.88 kB\n../public/vue-assets/assets/playback-BerPTevk.js 198.79 kB │ gzip: 61.85 kB │ map: 684.87 kB\n../public/vue-assets/assets/index.module-Bjlhgfd1.js 218.14 kB │ gzip: 64.16 kB │ map: 1,108.20 kB\n../public/vue-assets/assets/intl-tel-input-BW4mv40Q.js 264.94 kB │ gzip: 60.31 kB │ map: 475.61 kB\n../public/vue-assets/assets/team-insights-Rfo09TvE.js 298.57 kB │ gzip: 77.21 kB │ map: 959.96 kB\n../public/vue-assets/assets/popper-CQwVcrX4.js 307.13 kB │ gzip: 103.86 kB │ map: 1,245.28 kB\n../public/vue-assets/assets/PhoneField-CwCIoAYm.js 343.99 kB │ gzip: 84.90 kB │ map: 849.05 kB\n../public/vue-assets/assets/live-BkQge7FT.js 367.43 kB │ gzip: 97.05 kB │ map: 792.41 kB\n../public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BNO485xV.js 689.63 kB │ gzip: 202.81 kB │ map: 3,016.64 kB\n../public/vue-assets/assets/index-RakUX3TF.js 825.23 kB │ gzip: 72.54 kB │ map: 436.62 kB\n../public/vue-assets/assets/logged-in-layout-WZXlxzkM.js 1,402.70 kB │ gzip: 438.07 kB │ map: 6,283.55 kB\n\n[plugin builtin:vite-reporter] \n(!) Some chunks are larger than 500 kB after minification. Consider:\n- Using dynamic import() to code-split the application\n- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting\n- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.\n✓ built in 25.43s\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yarn build\n[dotenv@17.3.1] injecting env (0) from .env -- tip: ⚡️\u0000 secrets for agents: https://dotenvx.com/as2\nvite v8.0.0 building client environment for production...\n✓ 4656 modules transformed.\n[sentry-vite-plugin] Warning: No auth token provided. Will not create release. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\n[sentry-vite-plugin] Warning: No auth token provided. Will not upload source maps. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\ncomputing gzip size...\n../public/vue-assets/index.html 4.58 kB │ gzip: 1.16 kB\n../public/vue-assets/assets/job-adder-B2LcgC4o.png 5.41 kB\n../public/vue-assets/assets/dixa-DzT89tKh.png 7.87 kB\n../public/vue-assets/assets/planhat-CQycOTMW.svg 8.61 kB │ gzip: 3.48 kB\n../public/vue-assets/assets/funding-circle-9iGnDGsz.png 9.43 kB\n../public/vue-assets/assets/cision-uOdx_YiN.svg 14.41 kB │ gzip: 6.32 kB\n../public/vue-assets/assets/les-mills-DWabpmYc.png 15.73 kB\n../public/vue-assets/assets/superside-btT37L9L.png 26.69 kB\n../public/vue-assets/assets/flags-a2kmUSbF.webp 28.18 kB\n../public/vue-assets/assets/quinyx-RGQNHx6o.png 63.17 kB\n../public/vue-assets/assets/flags@2x-gR6KPp3x.webp 66.44 kB\n../public/vue-assets/assets/bg-marketing-CAIHIJMj.png 70.66 kB\n../public/vue-assets/.vite/manifest.json 85.73 kB │ gzip: 8.32 kB\n../public/vue-assets/assets/jiminny-score-DtTbKZaD.svg 131.93 kB │ gzip: 86.07 kB\n../public/vue-assets/assets/wavy-bg-vnCaCm9m.webm 670.97 kB\n../public/vue-assets/assets/wavy-bg-Op6dRIjR.mp4 14,484.63 kB\n../public/vue-assets/assets/AppForm-EEGPL5K8.css 0.04 kB │ gzip: 0.06 kB\n../public/vue-assets/assets/RecipientsCell-DNOSS8k1.css 0.12 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/softphone-coach-Cy_9nuQZ.css 0.13 kB │ gzip: 0.10 kB\n../public/vue-assets/assets/locked-CHS8VPur.css 0.15 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/mobile-KPnGhnTp.css 0.16 kB │ gzip: 0.14 kB\n../public/vue-assets/assets/BuildInfo-CxNQv_OV.css 0.20 kB │ gzip: 0.17 kB\n../public/vue-assets/assets/AvatarsStack-C0Nvfh1V.css 0.28 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/LogoLong100-ByQTvUY3.css 0.32 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/connect--xBDzLbc.css 0.34 kB │ gzip: 0.21 kB\n../public/vue-assets/assets/KioskBanner-xU3FRd8g.css 0.41 kB │ gzip: 0.27 kB\n../public/vue-assets/assets/extension-installed-BPBhG13P.css 0.41 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/FollowModal-A0rJmIOK.css 0.45 kB │ gzip: 0.23 kB\n../public/vue-assets/assets/GenericMessage-DutAlObK.css 0.49 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/textarea-caret-DCiNUER_.css 0.51 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/AppLinks-BznfGcBR.css 0.58 kB │ gzip: 0.33 kB\n../public/vue-assets/assets/SeekBtn-_VMPIk6v.css 0.63 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/emoji-input-kJWRbWf8.css 0.68 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/activity-preview-BDxP2S0-.css 0.70 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/ListLoader-DQqAw-8s.css 0.73 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/filters-Bgb6cQD6.css 0.75 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/AiContext-Bg-zjjbA.css 0.99 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/vue3-daterange-picker-izPOBj3P.css 1.02 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/PrimaryButton-CJj2FzKS.css 1.08 kB │ gzip: 0.34 kB\n../public/vue-assets/assets/other-BTuPXtu_.css 1.15 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/jenesius-vue-modal-DDcfejHO.css 1.17 kB │ gzip: 0.48 kB\n../public/vue-assets/assets/TabEmptyState-DOlIO68k.css 1.21 kB │ gzip: 0.53 kB\n../public/vue-assets/assets/AppAlert-BlSJKiqj.css 1.29 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/useActivityCustomerName-CBjfIUfJ.css 1.42 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/UserAvatar-co8l9mMk.css 1.51 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/basic-modal-Dx7ZBbcA.css 1.55 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/login-DYbcBesM.css 1.58 kB │ gzip: 0.61 kB\n../public/vue-assets/assets/DrawerWidget-poAmqspk.css 1.67 kB │ gzip: 0.69 kB\n../public/vue-assets/assets/ActionItems-5FkZSo3G.css 1.79 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/join-conference-DQ6Yl7eU.css 1.82 kB │ gzip: 0.75 kB\n../public/vue-assets/assets/InputText-C-DxDNSV.css 1.95 kB │ gzip: 0.71 kB\n../public/vue-assets/assets/GoogleLikeButton-DvQHxbqR.css 2.11 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/usePusherEventListener-CDj8aSNp.css 2.20 kB │ gzip: 0.73 kB\n../public/vue-assets/assets/onboard-IzGiQ-EC.css 2.32 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/ai-reports-manage-DaynTl62.css 2.34 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/StatusBadge-B4N12dzU.css 2.35 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/GridView-CsP1Jk-k.css 2.36 kB │ gzip: 0.90 kB\n../public/vue-assets/assets/WelcomeLayout-6AX86p6F.css 2.38 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/snackbar-CS_iqvrv.css 2.45 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/ai-reports-mtqGqPPq.css 2.47 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/Comment-gmAfiuer.css 2.63 kB │ gzip: 1.00 kB\n../public/vue-assets/assets/meeting-consent-BasRvxE3.css 2.66 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/DealRiskList-CwxmDnSC.css 2.67 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/add-to-playlist-modal-C_ad1hht.css 2.84 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/live-_zT5qBc1.css 3.60 kB │ gzip: 1.16 kB\n../public/vue-assets/assets/RadioField-DDp8Y71G.css 3.64 kB │ gzip: 1.20 kB\n../public/vue-assets/assets/vue-multiselect-BkQNV7oL.css 3.80 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/activity-preview-result-Db1Da81T.css 3.89 kB │ gzip: 1.15 kB\n../public/vue-assets/assets/liquor-tree-CB5oh8v1.css 4.08 kB │ gzip: 1.24 kB\n../public/vue-assets/assets/sentry-CSjHcvmn.css 4.47 kB │ gzip: 1.08 kB\n../public/vue-assets/assets/AiAutomation-Cj1t8v5y.css 4.61 kB │ gzip: 0.84 kB\n../public/vue-assets/assets/export-portal-CyIUVTEe.css 5.34 kB │ gzip: 1.65 kB\n../public/vue-assets/assets/InputField-C4hoKJ7Z.css 6.13 kB │ gzip: 1.27 kB\n../public/vue-assets/assets/vue-mq-bh4L87Tr.css 6.64 kB │ gzip: 1.95 kB\n../public/vue-assets/assets/invitation-EPR1t-bH.css 6.74 kB │ gzip: 2.22 kB\n../public/vue-assets/assets/ondemand-C2m0YVGA.css 6.84 kB │ gzip: 2.02 kB\n../public/vue-assets/assets/AskAnything-CyvEhHS1.css 8.60 kB │ gzip: 2.60 kB\n../public/vue-assets/assets/AppButton-BIeoar_U.css 8.88 kB │ gzip: 2.04 kB\n../public/vue-assets/assets/kiosk-BcheyWWL.css 9.56 kB │ gzip: 2.53 kB\n../public/vue-assets/assets/AiCrmNotes-nNTe_mOY.css 10.62 kB │ gzip: 1.93 kB\n../public/vue-assets/assets/deal-view-DJzG7PLp.css 11.70 kB │ gzip: 3.29 kB\n../public/vue-assets/assets/tokens-DZgWrfKd.css 12.44 kB │ gzip: 2.82 kB\n../public/vue-assets/assets/tokens-DL2BPbai.css 12.89 kB │ gzip: 2.87 kB\n../public/vue-assets/assets/playlists-Cy3t3kYU.css 13.54 kB │ gzip: 3.16 kB\n../public/vue-assets/assets/PhoneField-D_kGOah0.css 13.75 kB │ gzip: 3.44 kB\n../public/vue-assets/assets/ListView-Dt0qkyuY.css 17.07 kB │ gzip: 4.11 kB\n../public/vue-assets/assets/intl-tel-input-DgmgTINs.css 17.11 kB │ gzip: 5.37 kB\n../public/vue-assets/assets/AppFormField-DvpfRzi8.css 18.98 kB │ gzip: 4.42 kB\n../public/vue-assets/assets/OrgSettingsLayout-BogdXtgY.css 21.98 kB │ gzip: 5.51 kB\n../public/vue-assets/assets/dashboard-DQw3TKyk.css 22.74 kB │ gzip: 4.35 kB\n../public/vue-assets/assets/deal-insights-CfX3UNzh.css 29.66 kB │ gzip: 6.44 kB\n../public/vue-assets/assets/team-insights-CjVhm0JN.css 42.31 kB │ gzip: 8.46 kB\n../public/vue-assets/assets/playback-CvJP5tX1.css 44.55 kB │ gzip: 10.36 kB\n../public/vue-assets/assets/video-js-skin-DYZluGb-.css 47.73 kB │ gzip: 12.63 kB\n../public/vue-assets/assets/assets-CAbfI4CY.css 83.38 kB │ gzip: 51.71 kB\n../public/vue-assets/assets/logged-in-layout-1mAqkmnS.css 153.67 kB │ gzip: 27.18 kB\n../public/vue-assets/assets/assets-xH6dx_9q.css 259.71 kB │ gzip: 181.71 kB\n../public/vue-assets/assets/directives-DJJeJJOP.js 0.53 kB │ gzip: 0.36 kB │ map: 0.32 kB\n../public/vue-assets/assets/jenesius-vue-modal-BuBhyl83.js 0.54 kB │ gzip: 0.37 kB │ map: 0.47 kB\n../public/vue-assets/assets/spark-D_-Wgfar.js 0.54 kB │ gzip: 0.35 kB │ map: 0.40 kB\n../public/vue-assets/assets/url-messenger-_CGQa-lH.js 0.56 kB │ gzip: 0.37 kB │ map: 0.46 kB\n../public/vue-assets/assets/wavy-bg-Cec-_GDj.js 0.57 kB │ gzip: 0.36 kB │ map: 0.35 kB\n../public/vue-assets/assets/component-css-class-CtO0AVgW.js 0.61 kB │ gzip: 0.40 kB │ map: 0.91 kB\n../public/vue-assets/assets/theme-Cy-WIInU.js 0.63 kB │ gzip: 0.42 kB │ map: 0.49 kB\n../public/vue-assets/assets/pick-BGRRe-Qj.js 0.71 kB │ gzip: 0.45 kB │ map: 1.59 kB\n../public/vue-assets/assets/utils-BjsEoLDB.js 0.77 kB │ gzip: 0.50 kB │ map: 1.68 kB\n../public/vue-assets/assets/throttle-DmtOZ8h1.js 0.78 kB │ gzip: 0.49 kB │ map: 3.24 kB\n../public/vue-assets/assets/lastFilters-CzSBGyMt.js 0.80 kB │ gzip: 0.51 kB │ map: 1.12 kB\n../public/vue-assets/assets/useAuthState-Bpx8WpBc.js 0.81 kB │ gzip: 0.50 kB │ map: 1.64 kB\n../public/vue-assets/assets/v-focus-618yf9WO.js 0.85 kB │ gzip: 0.52 kB │ map: 1.42 kB\n../public/vue-assets/assets/ListLoader-bfQmkVGu.js 0.87 kB │ gzip: 0.54 kB │ map: 2.90 kB\n../public/vue-assets/assets/utils-CAMifZzY.js 0.88 kB │ gzip: 0.53 kB │ map: 2.45 kB\n../public/vue-assets/assets/mobileApp-QFLS3kId.js 0.89 kB │ gzip: 0.54 kB │ map: 1.12 kB\n../public/vue-assets/assets/pickBy-BtwCuAYd.js 0.95 kB │ gzip: 0.59 kB │ map: 2.57 kB\n../public/vue-assets/assets/BuildInfo-CIGre86L.js 1.00 kB │ gzip: 0.65 kB │ map: 1.48 kB\n../public/vue-assets/assets/LogoShort100-D_SQdBYB.js 1.01 kB │ gzip: 0.64 kB │ map: 1.23 kB\n../public/vue-assets/assets/useDrawerModal-CmhXI-pZ.js 1.04 kB │ gzip: 0.65 kB │ map: 1.94 kB\n../public/vue-assets/assets/_getAllKeysIn-CkMQAnJa.js 1.12 kB │ gzip: 0.67 kB │ map: 4.83 kB\n../public/vue-assets/assets/useAutosizeTextarea-DyMwh_20.js 1.13 kB │ gzip: 0.70 kB │ map: 3.16 kB\n../public/vue-assets/assets/planhat-DPA36Hcn.js 1.21 kB │ gzip: 0.71 kB │ map: 2.18 kB\n../public/vue-assets/assets/useStoreModule-Cx6UoGVJ.js 1.35 kB │ gzip: 0.72 kB │ map: 6.03 kB\n../public/vue-assets/assets/GenericMessage-BxGsrfvw.js 1.39 kB │ gzip: 0.75 kB │ map: 1.90 kB\n../public/vue-assets/assets/BrowserExtensionInstaller-DimyBPIi.js 1.45 kB │ gzip: 0.78 kB │ map: 2.05 kB\n../public/vue-assets/assets/debounce-DG3xxJjJ.js 1.53 kB │ gzip: 0.83 kB │ map: 8.61 kB\n../public/vue-assets/assets/extension-installed-mV_4t574.js 1.60 kB │ gzip: 0.91 kB │ map: 3.20 kB\n../public/vue-assets/assets/RecipientsCell-BS9gDGey.js 1.61 kB │ gzip: 0.93 kB │ map: 4.04 kB\n../public/vue-assets/assets/PrimaryButton-DaKR3UAG.js 1.61 kB │ gzip: 0.87 kB │ map: 3.73 kB\n../public/vue-assets/assets/KioskBanner-g60shJd4.js 1.66 kB │ gzip: 1.00 kB │ map: 2.88 kB\n../public/vue-assets/assets/LogoLong100-Hid0kvjR.js 1.78 kB │ gzip: 1.08 kB │ map: 5.91 kB\n../public/vue-assets/assets/GoogleLikeButton-CGFy3nbz.js 1.80 kB │ gzip: 0.84 kB │ map: 4.71 kB\n../public/vue-assets/assets/AppAlert-DFp0nftl.js 1.86 kB │ gzip: 1.01 kB │ map: 6.51 kB\n../public/vue-assets/assets/usePusherEventListener-Z27Z5klt.js 1.93 kB │ gzip: 1.08 kB │ map: 9.59 kB\n../public/vue-assets/assets/AppForm-BgtH5xsM.js 2.01 kB │ gzip: 1.10 kB │ map: 6.89 kB\n../public/vue-assets/assets/vee-validate-rules-IFeGZHb3.js 2.02 kB │ gzip: 0.89 kB │ map: 22.31 kB\n../public/vue-assets/assets/literals-CXSwYC8y.js 2.05 kB │ gzip: 0.97 kB │ map: 3.08 kB\n../public/vue-assets/assets/TabEmptyState-pk8vRxJt.js 2.16 kB │ gzip: 1.12 kB │ map: 8.26 kB\n../public/vue-assets/assets/Replies-Cn_qGwMd.js 2.23 kB │ gzip: 1.14 kB │ map: 2.40 kB\n../public/vue-assets/assets/settings-BSSIkavG.js 2.34 kB │ gzip: 1.24 kB │ map: 1.73 kB\n../public/vue-assets/assets/SeekBtn-CMp8sSUA.js 2.47 kB │ gzip: 1.18 kB │ map: 9.27 kB\n../public/vue-assets/assets/AiAutomation-C5_UlocO.js 2.59 kB │ gzip: 1.37 kB │ map: 4.45 kB\n../public/vue-assets/assets/locked-Bad3U1k5.js 2.59 kB │ gzip: 1.43 kB │ map: 4.00 kB\n../public/vue-assets/assets/DrawerWidget-BPC-tCgo.js 2.68 kB │ gzip: 1.33 kB │ map: 6.95 kB\n../public/vue-assets/assets/vue-infinite-scroll-D0cI4gH6.js 2.72 kB │ gzip: 1.28 kB │ map: 9.99 kB\n../public/vue-assets/assets/useActivityCustomerName-CYuaZj-p.js 2.87 kB │ gzip: 1.41 kB │ map: 6.27 kB\n../public/vue-assets/assets/other-D11UHzKP.js 2.97 kB │ gzip: 1.51 kB │ map: 3.38 kB\n../public/vue-assets/assets/InputDropdown-CBM8xiPT.js 3.33 kB │ gzip: 1.61 kB │ map: 6.02 kB\n../public/vue-assets/assets/AvatarsStack-hxex9Ic8.js 3.40 kB │ gzip: 1.51 kB │ map: 9.54 kB\n../public/vue-assets/assets/activity-preview-CoaCdIkx.js 3.45 kB │ gzip: 1.68 kB │ map: 10.11 kB\n../public/vue-assets/assets/FollowModal-SMYvVwqG.js 3.70 kB │ gzip: 1.81 kB │ map: 8.86 kB\n../public/vue-assets/assets/softphone-coach-C452yEnj.js 3.74 kB │ gzip: 1.96 kB │ map: 5.03 kB\n../public/vue-assets/assets/store-CloJVGCY.js 4.10 kB │ gzip: 2.00 kB │ map: 18.73 kB\n../public/vue-assets/assets/AiContext-DJ4M3rJ1.js 4.34 kB │ gzip: 2.08 kB │ map: 13.11 kB\n../public/vue-assets/assets/meeting-consent-B24mqvl1.js 4.43 kB │ gzip: 2.05 kB │ map: 9.72 kB\n../public/vue-assets/assets/vue-multiselect-VB2Agtp1.js 4.49 kB │ gzip: 1.93 kB │ map: 14.85 kB\n../public/vue-assets/assets/snackbarNotifications-DjDIGkWd.js 4.83 kB │ gzip: 1.58 kB │ map: 11.62 kB\n../public/vue-assets/assets/invitation-B5F0aLwl.js 4.88 kB │ gzip: 2.21 kB │ map: 19.38 kB\n../public/vue-assets/assets/textarea-caret-B2HdIdpZ.js 5.25 kB │ gzip: 2.41 kB │ map: 14.48 kB\n../public/vue-assets/assets/connect-DvCp38Q2.js 5.31 kB │ gzip: 2.31 kB │ map: 10.62 kB\n../public/vue-assets/assets/snackbar-Bq-YQ7pz.js 5.48 kB │ gzip: 2.37 kB │ map: 20.43 kB\n../public/vue-assets/assets/RadioField-2SeSZB7D.js 5.65 kB │ gzip: 2.22 kB │ map: 15.82 kB\n../public/vue-assets/assets/filters-D_eXZdIL.js 5.76 kB │ gzip: 2.17 kB │ map: 16.36 kB\n../public/vue-assets/assets/useActivityHelper-BvWVN0nz.js 5.84 kB │ gzip: 2.51 kB │ map: 14.26 kB\n../public/vue-assets/assets/vue-scrollto-ul3pFdrb.js 5.97 kB │ gzip: 2.72 kB │ map: 23.98 kB\n../public/vue-assets/assets/join-conference-CLdXMCwD.js 6.25 kB │ gzip: 2.83 kB │ map: 24.66 kB\n../public/vue-assets/assets/pluralize-BeKVJaE0.js 6.34 kB │ gzip: 2.70 kB │ map: 18.28 kB\n../public/vue-assets/assets/login-PrWVOMsr.js 7.02 kB │ gzip: 3.17 kB │ map: 17.51 kB\n../public/vue-assets/assets/AppLinks-Bg20Hslb.js 7.14 kB │ gzip: 2.80 kB │ map: 14.87 kB\n../public/vue-assets/assets/InputField-CkdrqKBp.js 7.20 kB │ gzip: 2.58 kB │ map: 19.39 kB\n../public/vue-assets/assets/UserAvatar-UKkSAS4R.js 7.93 kB │ gzip: 3.32 kB │ map: 34.91 kB\n../public/vue-assets/assets/InputText-BH9WFuby.js 7.99 kB │ gzip: 3.05 kB │ map: 20.64 kB\n../public/vue-assets/assets/AiCrmNotes-DmPJHAz_.js 8.27 kB │ gzip: 3.32 kB │ map: 34.03 kB\n../public/vue-assets/assets/vue-mq-CmpUzQtD.js 8.84 kB │ gzip: 3.54 kB │ map: 23.69 kB\n../public/vue-assets/assets/pro-duotone-svg-icons-BSBZV3-t.js 9.51 kB │ gzip: 3.43 kB │ map: 3,146.68 kB\n../public/vue-assets/assets/activity-preview-result-CQou30yT.js 10.65 kB │ gzip: 3.98 kB │ map: 38.51 kB\n../public/vue-assets/assets/pro-regular-svg-icons-CWpSBR20.js 13.10 kB │ gzip: 4.64 kB │ map: 3,178.15 kB\n../public/vue-assets/assets/add-to-playlist-modal-BS8w3ovp.js 14.99 kB │ gzip: 5.98 kB │ map: 51.34 kB\n../public/vue-assets/assets/ai-reports-Cy_xmRg8.js 15.17 kB │ gzip: 6.04 kB │ map: 51.85 kB\n../public/vue-assets/assets/export-portal-CZ2b-FlS.js 15.68 kB │ gzip: 6.22 kB │ map: 52.78 kB\n../public/vue-assets/assets/Comment-CdvjTszI.js 15.92 kB │ gzip: 5.72 kB │ map: 39.96 kB\n../public/vue-assets/assets/useragent-DQ4F_O30.js 18.02 kB │ gzip: 8.06 kB │ map: 68.31 kB\n../public/vue-assets/assets/linkify-B_q6MgwI.js 18.58 kB │ gzip: 10.37 kB │ map: 88.73 kB\n../public/vue-assets/assets/ai-reports-manage-B1v5HdqS.js 18.92 kB │ gzip: 6.76 kB │ map: 66.14 kB\n../public/vue-assets/assets/dist-C2hisF0L.js 19.32 kB │ gzip: 7.87 kB │ map: 390.77 kB\n../public/vue-assets/assets/pro-solid-svg-icons-DBOkpln3.js 19.46 kB │ gzip: 6.72 kB │ map: 2,692.14 kB\n../public/vue-assets/assets/purify.es-BxOw6oE6.js 21.49 kB │ gzip: 8.71 kB │ map: 83.43 kB\n../public/vue-assets/assets/ActionItems-uKCSfiLo.js 21.92 kB │ gzip: 8.55 kB │ map: 76.05 kB\n../public/vue-assets/assets/mobile-zCtNIOGN.js 23.34 kB │ gzip: 9.71 kB │ map: 50.90 kB\n../public/vue-assets/assets/basic-modal-BdXsnFwC.js 23.87 kB │ gzip: 9.06 kB │ map: 64.15 kB\n../public/vue-assets/assets/GridView-D5_z7YdA.js 26.60 kB │ gzip: 10.05 kB │ map: 92.74 kB\n../public/vue-assets/assets/ondemand-mxFxJiHI.js 26.88 kB │ gzip: 9.39 kB │ map: 73.94 kB\n../public/vue-assets/assets/CrmLink-DKYsnHnx.js 27.91 kB │ gzip: 10.18 kB │ map: 93.18 kB\n../public/vue-assets/assets/liquor-tree-COUefof4.js 30.75 kB │ gzip: 9.58 kB │ map: 78.74 kB\n../public/vue-assets/assets/DealRiskList-D7gUVql5.js 34.39 kB │ gzip: 10.62 kB │ map: 115.18 kB\n../public/vue-assets/assets/AskAnything-DgI0NfRA.js 39.50 kB │ gzip: 14.97 kB │ map: 173.20 kB\n../public/vue-assets/assets/lib-CwM9toD2.js 39.69 kB │ gzip: 12.70 kB │ map: 138.34 kB\n../public/vue-assets/assets/AppFormField-BqQiLGFF.js 41.91 kB │ gzip: 12.69 kB │ map: 150.73 kB\n../public/vue-assets/assets/deal-view-D4YlPwr_.js 43.22 kB │ gzip: 14.35 kB │ map: 150.62 kB\n../public/vue-assets/assets/exports-D1lmea4O.js 47.84 kB │ gzip: 16.46 kB │ map: 294.48 kB\n../public/vue-assets/assets/playlists-BWAdERcJ.js 48.28 kB │ gzip: 15.07 kB │ map: 153.25 kB\n../public/vue-assets/assets/callScoringTemplates-zeRn40uL.js 55.13 kB │ gzip: 13.28 kB │ map: 65.85 kB\n../public/vue-assets/assets/_copyObject-USkOnlaQ.js 61.28 kB │ gzip: 20.09 kB │ map: 239.59 kB\n../public/vue-assets/assets/pusher-znYCfz7U.js 62.98 kB │ gzip: 18.88 kB │ map: 219.27 kB\n../public/vue-assets/assets/onboard-CyAPGoFk.js 63.11 kB │ gzip: 21.85 kB │ map: 201.38 kB\n../public/vue-assets/assets/StatusBadge-DNHiCr2i.js 64.66 kB │ gzip: 22.96 kB │ map: 244.72 kB\n../public/vue-assets/assets/kiosk-dfcpodo5.js 79.60 kB │ gzip: 22.65 kB │ map: 300.68 kB\n../public/vue-assets/assets/preload-helper-DCvhahzG.js 82.59 kB │ gzip: 27.46 kB │ map: 3,452.35 kB\n../public/vue-assets/assets/deal-insights-BVnPilVP.js 94.84 kB │ gzip: 28.17 kB │ map: 292.79 kB\n../public/vue-assets/assets/ListView-DJD6SV4A.js 115.71 kB │ gzip: 33.79 kB │ map: 308.10 kB\n../public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js 117.59 kB │ gzip: 38.71 kB │ map: 500.60 kB\n../public/vue-assets/assets/WelcomeLayout-B6wd32HG.js 120.67 kB │ gzip: 34.16 kB │ map: 258.56 kB\n../public/vue-assets/assets/dashboard-CsDOiLAi.js 128.71 kB │ gzip: 40.05 kB │ map: 410.48 kB\n../public/vue-assets/assets/emoji-input-CSq87OVy.js 129.28 kB │ gzip: 36.72 kB │ map: 266.15 kB\n../public/vue-assets/assets/AppButton-D3qMdODr.js 133.44 kB │ gzip: 43.03 kB │ map: 516.67 kB\n../public/vue-assets/assets/sentry-B3B1ZM6O.js 164.28 kB │ gzip: 52.24 kB │ map: 831.82 kB\n../public/vue-assets/assets/OrgSettingsLayout-DatDldIe.js 176.33 kB │ gzip: 56.15 kB │ map: 623.43 kB\n../public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js 180.40 kB │ gzip: 67.85 kB │ map: 836.88 kB\n../public/vue-assets/assets/playback-D1gm80qQ.js 198.79 kB │ gzip: 61.85 kB │ map: 684.87 kB\n../public/vue-assets/assets/index.module-Bjlhgfd1.js 218.14 kB │ gzip: 64.16 kB │ map: 1,108.20 kB\n../public/vue-assets/assets/intl-tel-input-BW4mv40Q.js 264.94 kB │ gzip: 60.31 kB │ map: 475.61 kB\n../public/vue-assets/assets/team-insights-DRugjYCA.js 298.57 kB │ gzip: 77.22 kB │ map: 959.96 kB\n../public/vue-assets/assets/popper-CQwVcrX4.js 307.13 kB │ gzip: 103.86 kB │ map: 1,245.28 kB\n../public/vue-assets/assets/PhoneField-CwCIoAYm.js 343.99 kB │ gzip: 84.90 kB │ map: 849.05 kB\n../public/vue-assets/assets/live-CxSmZv7h.js 367.43 kB │ gzip: 97.05 kB │ map: 792.41 kB\n../public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BNO485xV.js 689.63 kB │ gzip: 202.81 kB │ map: 3,016.64 kB\n../public/vue-assets/assets/index-Cp0YOK4U.js 825.23 kB │ gzip: 72.54 kB │ map: 436.62 kB\n../public/vue-assets/assets/logged-in-layout-CE9ox17M.js 1,402.70 kB │ gzip: 438.07 kB │ map: 6,283.55 kB\n\n✓ built in 26.01s\n[plugin builtin:vite-reporter] \n(!) Some chunks are larger than 500 kB after minification. Consider:\n- Using dynamic import() to code-split the application\n- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting\n- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $","depth":4,"value":"info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ nvm use 24\nNow using node v24.11.1 (npm v11.6.2)\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yanr build\nzsh: command not found: yanr\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yanrn build\nzsh: command not found: yanrn\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yarn build \n[dotenv@17.3.1] injecting env (0) from .env -- tip: ⚡️\u0000 secrets for agents: https://dotenvx.com/as2\nvite v8.0.0 building client environment for production...\n✓ 4656 modules transformed.\n[sentry-vite-plugin] Warning: No auth token provided. Will not create release. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\n[sentry-vite-plugin] Warning: No auth token provided. Will not upload source maps. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\ncomputing gzip size...\n../public/vue-assets/index.html 4.58 kB │ gzip: 1.15 kB\n../public/vue-assets/assets/job-adder-B2LcgC4o.png 5.41 kB\n../public/vue-assets/assets/dixa-DzT89tKh.png 7.87 kB\n../public/vue-assets/assets/planhat-CQycOTMW.svg 8.61 kB │ gzip: 3.48 kB\n../public/vue-assets/assets/funding-circle-9iGnDGsz.png 9.43 kB\n../public/vue-assets/assets/cision-uOdx_YiN.svg 14.41 kB │ gzip: 6.32 kB\n../public/vue-assets/assets/les-mills-DWabpmYc.png 15.73 kB\n../public/vue-assets/assets/superside-btT37L9L.png 26.69 kB\n../public/vue-assets/assets/flags-a2kmUSbF.webp 28.18 kB\n../public/vue-assets/assets/quinyx-RGQNHx6o.png 63.17 kB\n../public/vue-assets/assets/flags@2x-gR6KPp3x.webp 66.44 kB\n../public/vue-assets/assets/bg-marketing-CAIHIJMj.png 70.66 kB\n../public/vue-assets/.vite/manifest.json 85.73 kB │ gzip: 8.33 kB\n../public/vue-assets/assets/jiminny-score-DtTbKZaD.svg 131.93 kB │ gzip: 86.07 kB\n../public/vue-assets/assets/wavy-bg-vnCaCm9m.webm 670.97 kB\n../public/vue-assets/assets/wavy-bg-Op6dRIjR.mp4 14,484.63 kB\n../public/vue-assets/assets/AppForm-EEGPL5K8.css 0.04 kB │ gzip: 0.06 kB\n../public/vue-assets/assets/RecipientsCell-DNOSS8k1.css 0.12 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/softphone-coach-Cy_9nuQZ.css 0.13 kB │ gzip: 0.10 kB\n../public/vue-assets/assets/locked-CHS8VPur.css 0.15 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/mobile-KPnGhnTp.css 0.16 kB │ gzip: 0.14 kB\n../public/vue-assets/assets/BuildInfo-CxNQv_OV.css 0.20 kB │ gzip: 0.17 kB\n../public/vue-assets/assets/AvatarsStack-C0Nvfh1V.css 0.28 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/LogoLong100-ByQTvUY3.css 0.32 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/connect--xBDzLbc.css 0.34 kB │ gzip: 0.21 kB\n../public/vue-assets/assets/KioskBanner-xU3FRd8g.css 0.41 kB │ gzip: 0.27 kB\n../public/vue-assets/assets/extension-installed-BPBhG13P.css 0.41 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/FollowModal-A0rJmIOK.css 0.45 kB │ gzip: 0.23 kB\n../public/vue-assets/assets/GenericMessage-DutAlObK.css 0.49 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/textarea-caret-DCiNUER_.css 0.51 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/AppLinks-BznfGcBR.css 0.58 kB │ gzip: 0.33 kB\n../public/vue-assets/assets/SeekBtn-_VMPIk6v.css 0.63 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/emoji-input-kJWRbWf8.css 0.68 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/activity-preview-BDxP2S0-.css 0.70 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/ListLoader-DQqAw-8s.css 0.73 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/filters-Bgb6cQD6.css 0.75 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/AiContext-Bg-zjjbA.css 0.99 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/vue3-daterange-picker-izPOBj3P.css 1.02 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/PrimaryButton-CJj2FzKS.css 1.08 kB │ gzip: 0.34 kB\n../public/vue-assets/assets/other-BTuPXtu_.css 1.15 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/jenesius-vue-modal-DDcfejHO.css 1.17 kB │ gzip: 0.48 kB\n../public/vue-assets/assets/TabEmptyState-DOlIO68k.css 1.21 kB │ gzip: 0.53 kB\n../public/vue-assets/assets/AppAlert-BlSJKiqj.css 1.29 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/useActivityCustomerName-CBjfIUfJ.css 1.42 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/UserAvatar-co8l9mMk.css 1.51 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/basic-modal-Dx7ZBbcA.css 1.55 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/login-DYbcBesM.css 1.58 kB │ gzip: 0.61 kB\n../public/vue-assets/assets/DrawerWidget-poAmqspk.css 1.67 kB │ gzip: 0.69 kB\n../public/vue-assets/assets/ActionItems-5FkZSo3G.css 1.79 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/join-conference-DQ6Yl7eU.css 1.82 kB │ gzip: 0.75 kB\n../public/vue-assets/assets/InputText-C-DxDNSV.css 1.95 kB │ gzip: 0.71 kB\n../public/vue-assets/assets/GoogleLikeButton-DvQHxbqR.css 2.11 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/usePusherEventListener-CDj8aSNp.css 2.20 kB │ gzip: 0.73 kB\n../public/vue-assets/assets/onboard-IzGiQ-EC.css 2.32 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/ai-reports-manage-DaynTl62.css 2.34 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/StatusBadge-B4N12dzU.css 2.35 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/GridView-CsP1Jk-k.css 2.36 kB │ gzip: 0.90 kB\n../public/vue-assets/assets/WelcomeLayout-6AX86p6F.css 2.38 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/snackbar-CS_iqvrv.css 2.45 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/ai-reports-mtqGqPPq.css 2.47 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/Comment-gmAfiuer.css 2.63 kB │ gzip: 1.00 kB\n../public/vue-assets/assets/meeting-consent-BasRvxE3.css 2.66 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/DealRiskList-CwxmDnSC.css 2.67 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/add-to-playlist-modal-C_ad1hht.css 2.84 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/live-_zT5qBc1.css 3.60 kB │ gzip: 1.16 kB\n../public/vue-assets/assets/RadioField-DDp8Y71G.css 3.64 kB │ gzip: 1.20 kB\n../public/vue-assets/assets/vue-multiselect-BkQNV7oL.css 3.80 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/activity-preview-result-Db1Da81T.css 3.89 kB │ gzip: 1.15 kB\n../public/vue-assets/assets/liquor-tree-CB5oh8v1.css 4.08 kB │ gzip: 1.24 kB\n../public/vue-assets/assets/sentry-CSjHcvmn.css 4.47 kB │ gzip: 1.08 kB\n../public/vue-assets/assets/AiAutomation-Cj1t8v5y.css 4.61 kB │ gzip: 0.84 kB\n../public/vue-assets/assets/export-portal-CyIUVTEe.css 5.34 kB │ gzip: 1.65 kB\n../public/vue-assets/assets/InputField-C4hoKJ7Z.css 6.13 kB │ gzip: 1.27 kB\n../public/vue-assets/assets/vue-mq-bh4L87Tr.css 6.64 kB │ gzip: 1.95 kB\n../public/vue-assets/assets/invitation-EPR1t-bH.css 6.74 kB │ gzip: 2.22 kB\n../public/vue-assets/assets/ondemand-C2m0YVGA.css 6.84 kB │ gzip: 2.02 kB\n../public/vue-assets/assets/AskAnything-CyvEhHS1.css 8.60 kB │ gzip: 2.60 kB\n../public/vue-assets/assets/AppButton-BIeoar_U.css 8.88 kB │ gzip: 2.04 kB\n../public/vue-assets/assets/kiosk-BcheyWWL.css 9.56 kB │ gzip: 2.53 kB\n../public/vue-assets/assets/AiCrmNotes-nNTe_mOY.css 10.62 kB │ gzip: 1.93 kB\n../public/vue-assets/assets/deal-view-DJzG7PLp.css 11.70 kB │ gzip: 3.29 kB\n../public/vue-assets/assets/tokens-B6_hRKul.css 12.95 kB │ gzip: 2.92 kB\n../public/vue-assets/assets/tokens-CoAAv8do.css 13.41 kB │ gzip: 2.97 kB\n../public/vue-assets/assets/playlists-Cy3t3kYU.css 13.54 kB │ gzip: 3.16 kB\n../public/vue-assets/assets/PhoneField-D_kGOah0.css 13.75 kB │ gzip: 3.44 kB\n../public/vue-assets/assets/ListView-Dt0qkyuY.css 17.07 kB │ gzip: 4.11 kB\n../public/vue-assets/assets/intl-tel-input-DgmgTINs.css 17.11 kB │ gzip: 5.37 kB\n../public/vue-assets/assets/AppFormField-DvpfRzi8.css 18.98 kB │ gzip: 4.42 kB\n../public/vue-assets/assets/OrgSettingsLayout-BogdXtgY.css 21.98 kB │ gzip: 5.51 kB\n../public/vue-assets/assets/dashboard-DQw3TKyk.css 22.74 kB │ gzip: 4.35 kB\n../public/vue-assets/assets/deal-insights-CfX3UNzh.css 29.66 kB │ gzip: 6.44 kB\n../public/vue-assets/assets/team-insights-CjVhm0JN.css 42.31 kB │ gzip: 8.46 kB\n../public/vue-assets/assets/playback-CvJP5tX1.css 44.55 kB │ gzip: 10.36 kB\n../public/vue-assets/assets/video-js-skin-DYZluGb-.css 47.73 kB │ gzip: 12.63 kB\n../public/vue-assets/assets/assets-CAbfI4CY.css 83.38 kB │ gzip: 51.71 kB\n../public/vue-assets/assets/logged-in-layout-1mAqkmnS.css 153.67 kB │ gzip: 27.18 kB\n../public/vue-assets/assets/assets-xH6dx_9q.css 259.71 kB │ gzip: 181.71 kB\n../public/vue-assets/assets/directives-DJJeJJOP.js 0.53 kB │ gzip: 0.36 kB │ map: 0.32 kB\n../public/vue-assets/assets/jenesius-vue-modal-BuBhyl83.js 0.54 kB │ gzip: 0.37 kB │ map: 0.47 kB\n../public/vue-assets/assets/spark-D_-Wgfar.js 0.54 kB │ gzip: 0.35 kB │ map: 0.40 kB\n../public/vue-assets/assets/url-messenger-_CGQa-lH.js 0.56 kB │ gzip: 0.37 kB │ map: 0.46 kB\n../public/vue-assets/assets/wavy-bg-Cec-_GDj.js 0.57 kB │ gzip: 0.36 kB │ map: 0.35 kB\n../public/vue-assets/assets/component-css-class-CtO0AVgW.js 0.61 kB │ gzip: 0.40 kB │ map: 0.91 kB\n../public/vue-assets/assets/theme-CrLnsUSQ.js 0.63 kB │ gzip: 0.41 kB │ map: 0.49 kB\n../public/vue-assets/assets/pick-jGxSsmQW.js 0.71 kB │ gzip: 0.45 kB │ map: 1.59 kB\n../public/vue-assets/assets/utils-BjsEoLDB.js 0.77 kB │ gzip: 0.50 kB │ map: 1.68 kB\n../public/vue-assets/assets/throttle-DE_etUX9.js 0.78 kB │ gzip: 0.49 kB │ map: 3.24 kB\n../public/vue-assets/assets/lastFilters-CjyI5phg.js 0.80 kB │ gzip: 0.51 kB │ map: 1.12 kB\n../public/vue-assets/assets/useAuthState-Bpx8WpBc.js 0.81 kB │ gzip: 0.50 kB │ map: 1.64 kB\n../public/vue-assets/assets/v-focus-618yf9WO.js 0.85 kB │ gzip: 0.52 kB │ map: 1.42 kB\n../public/vue-assets/assets/ListLoader-bfQmkVGu.js 0.87 kB │ gzip: 0.54 kB │ map: 2.90 kB\n../public/vue-assets/assets/utils-CAMifZzY.js 0.88 kB │ gzip: 0.53 kB │ map: 2.45 kB\n../public/vue-assets/assets/mobileApp-QFLS3kId.js 0.89 kB │ gzip: 0.54 kB │ map: 1.12 kB\n../public/vue-assets/assets/pickBy-DV-Svw8z.js 0.95 kB │ gzip: 0.59 kB │ map: 2.57 kB\n../public/vue-assets/assets/BuildInfo-CIGre86L.js 1.00 kB │ gzip: 0.65 kB │ map: 1.48 kB\n../public/vue-assets/assets/LogoShort100-D_SQdBYB.js 1.01 kB │ gzip: 0.64 kB │ map: 1.23 kB\n../public/vue-assets/assets/useDrawerModal-CmhXI-pZ.js 1.04 kB │ gzip: 0.65 kB │ map: 1.94 kB\n../public/vue-assets/assets/_getAllKeysIn-CkMQAnJa.js 1.12 kB │ gzip: 0.67 kB │ map: 4.83 kB\n../public/vue-assets/assets/useAutosizeTextarea-DyMwh_20.js 1.13 kB │ gzip: 0.70 kB │ map: 3.16 kB\n../public/vue-assets/assets/planhat-DPA36Hcn.js 1.21 kB │ gzip: 0.71 kB │ map: 2.18 kB\n../public/vue-assets/assets/useStoreModule-Cx6UoGVJ.js 1.35 kB │ gzip: 0.72 kB │ map: 6.03 kB\n../public/vue-assets/assets/GenericMessage-BxGsrfvw.js 1.39 kB │ gzip: 0.75 kB │ map: 1.90 kB\n../public/vue-assets/assets/BrowserExtensionInstaller-DimyBPIi.js 1.45 kB │ gzip: 0.78 kB │ map: 2.05 kB\n../public/vue-assets/assets/debounce-C6V4-Yml.js 1.53 kB │ gzip: 0.83 kB │ map: 8.61 kB\n../public/vue-assets/assets/extension-installed-C_yEm2e-.js 1.60 kB │ gzip: 0.91 kB │ map: 3.20 kB\n../public/vue-assets/assets/RecipientsCell-BS9gDGey.js 1.61 kB │ gzip: 0.93 kB │ map: 4.04 kB\n../public/vue-assets/assets/PrimaryButton-DaKR3UAG.js 1.61 kB │ gzip: 0.87 kB │ map: 3.73 kB\n../public/vue-assets/assets/KioskBanner-DyDQY_lm.js 1.66 kB │ gzip: 1.00 kB │ map: 2.88 kB\n../public/vue-assets/assets/LogoLong100-Hid0kvjR.js 1.78 kB │ gzip: 1.08 kB │ map: 5.91 kB\n../public/vue-assets/assets/GoogleLikeButton-CGFy3nbz.js 1.80 kB │ gzip: 0.84 kB │ map: 4.71 kB\n../public/vue-assets/assets/AppAlert-DFp0nftl.js 1.86 kB │ gzip: 1.01 kB │ map: 6.51 kB\n../public/vue-assets/assets/usePusherEventListener-DRJ7JhID.js 1.93 kB │ gzip: 1.08 kB │ map: 9.59 kB\n../public/vue-assets/assets/AppForm-BgtH5xsM.js 2.01 kB │ gzip: 1.10 kB │ map: 6.89 kB\n../public/vue-assets/assets/vee-validate-rules-IFeGZHb3.js 2.02 kB │ gzip: 0.89 kB │ map: 22.31 kB\n../public/vue-assets/assets/literals-CXSwYC8y.js 2.05 kB │ gzip: 0.97 kB │ map: 3.08 kB\n../public/vue-assets/assets/TabEmptyState-pk8vRxJt.js 2.16 kB │ gzip: 1.12 kB │ map: 8.26 kB\n../public/vue-assets/assets/Replies-BrhP0P7D.js 2.23 kB │ gzip: 1.14 kB │ map: 2.40 kB\n../public/vue-assets/assets/settings-DWZF-G8N.js 2.34 kB │ gzip: 1.23 kB │ map: 1.73 kB\n../public/vue-assets/assets/SeekBtn-CMp8sSUA.js 2.47 kB │ gzip: 1.18 kB │ map: 9.27 kB\n../public/vue-assets/assets/AiAutomation-CnVa_KZl.js 2.59 kB │ gzip: 1.37 kB │ map: 4.45 kB\n../public/vue-assets/assets/locked-B2ThxTAd.js 2.59 kB │ gzip: 1.42 kB │ map: 4.00 kB\n../public/vue-assets/assets/DrawerWidget-BPC-tCgo.js 2.68 kB │ gzip: 1.33 kB │ map: 6.95 kB\n../public/vue-assets/assets/vue-infinite-scroll-D0cI4gH6.js 2.72 kB │ gzip: 1.28 kB │ map: 9.99 kB\n../public/vue-assets/assets/useActivityCustomerName-CYuaZj-p.js 2.87 kB │ gzip: 1.41 kB │ map: 6.27 kB\n../public/vue-assets/assets/other-k3In5q1l.js 2.97 kB │ gzip: 1.50 kB │ map: 3.38 kB\n../public/vue-assets/assets/InputDropdown-CBM8xiPT.js 3.33 kB │ gzip: 1.61 kB │ map: 6.02 kB\n../public/vue-assets/assets/AvatarsStack-hxex9Ic8.js 3.40 kB │ gzip: 1.51 kB │ map: 9.54 kB\n../public/vue-assets/assets/activity-preview-BZrDAfqV.js 3.45 kB │ gzip: 1.69 kB │ map: 10.11 kB\n../public/vue-assets/assets/FollowModal-SMYvVwqG.js 3.70 kB │ gzip: 1.81 kB │ map: 8.86 kB\n../public/vue-assets/assets/softphone-coach-DJLlDZUr.js 3.74 kB │ gzip: 1.96 kB │ map: 5.03 kB\n../public/vue-assets/assets/store-DdBy-CTd.js 4.10 kB │ gzip: 2.00 kB │ map: 18.73 kB\n../public/vue-assets/assets/AiContext-BYAvUsG9.js 4.34 kB │ gzip: 2.08 kB │ map: 13.11 kB\n../public/vue-assets/assets/meeting-consent-EdnBkMBr.js 4.43 kB │ gzip: 2.04 kB │ map: 9.72 kB\n../public/vue-assets/assets/vue-multiselect-VB2Agtp1.js 4.49 kB │ gzip: 1.93 kB │ map: 14.85 kB\n../public/vue-assets/assets/snackbarNotifications-DjDIGkWd.js 4.83 kB │ gzip: 1.58 kB │ map: 11.62 kB\n../public/vue-assets/assets/invitation-B1SNfWGY.js 4.88 kB │ gzip: 2.21 kB │ map: 19.38 kB\n../public/vue-assets/assets/connect-DzHqvqqR.js 5.17 kB │ gzip: 2.28 kB │ map: 10.35 kB\n../public/vue-assets/assets/textarea-caret-B2HdIdpZ.js 5.25 kB │ gzip: 2.41 kB │ map: 14.48 kB\n../public/vue-assets/assets/snackbar-Bq-YQ7pz.js 5.48 kB │ gzip: 2.37 kB │ map: 20.43 kB\n../public/vue-assets/assets/RadioField-2SeSZB7D.js 5.65 kB │ gzip: 2.22 kB │ map: 15.82 kB\n../public/vue-assets/assets/filters-D_eXZdIL.js 5.76 kB │ gzip: 2.17 kB │ map: 16.36 kB\n../public/vue-assets/assets/useActivityHelper-DbifQcp6.js 5.84 kB │ gzip: 2.51 kB │ map: 14.26 kB\n../public/vue-assets/assets/vue-scrollto-ul3pFdrb.js 5.97 kB │ gzip: 2.72 kB │ map: 23.98 kB\n../public/vue-assets/assets/join-conference-CYJkDTD8.js 6.25 kB │ gzip: 2.82 kB │ map: 24.66 kB\n../public/vue-assets/assets/pluralize-BeKVJaE0.js 6.34 kB │ gzip: 2.70 kB │ map: 18.28 kB\n../public/vue-assets/assets/login-DkwzcZhV.js 7.02 kB │ gzip: 3.16 kB │ map: 17.51 kB\n../public/vue-assets/assets/AppLinks-Bg20Hslb.js 7.14 kB │ gzip: 2.80 kB │ map: 14.87 kB\n../public/vue-assets/assets/InputField-CkdrqKBp.js 7.20 kB │ gzip: 2.58 kB │ map: 19.39 kB\n../public/vue-assets/assets/UserAvatar-UKkSAS4R.js 7.93 kB │ gzip: 3.32 kB │ map: 34.91 kB\n../public/vue-assets/assets/InputText-CiBJ3oHz.js 7.99 kB │ gzip: 3.05 kB │ map: 20.64 kB\n../public/vue-assets/assets/AiCrmNotes-BZWjw9pN.js 8.27 kB │ gzip: 3.32 kB │ map: 34.03 kB\n../public/vue-assets/assets/vue-mq-CmpUzQtD.js 8.84 kB │ gzip: 3.54 kB │ map: 23.69 kB\n../public/vue-assets/assets/pro-duotone-svg-icons-BSBZV3-t.js 9.51 kB │ gzip: 3.43 kB │ map: 3,146.68 kB\n../public/vue-assets/assets/activity-preview-result-DLO6BXq1.js 10.65 kB │ gzip: 3.98 kB │ map: 38.51 kB\n../public/vue-assets/assets/pro-regular-svg-icons-CWpSBR20.js 13.10 kB │ gzip: 4.64 kB │ map: 3,178.15 kB\n../public/vue-assets/assets/add-to-playlist-modal-prxYCVfk.js 14.99 kB │ gzip: 5.98 kB │ map: 51.34 kB\n../public/vue-assets/assets/ai-reports-B3i0pft-.js 15.17 kB │ gzip: 6.03 kB │ map: 51.85 kB\n../public/vue-assets/assets/export-portal-54ZutGpG.js 15.68 kB │ gzip: 6.22 kB │ map: 52.78 kB\n../public/vue-assets/assets/Comment-BpW-5w7g.js 15.92 kB │ gzip: 5.72 kB │ map: 39.96 kB\n../public/vue-assets/assets/useragent-DQ4F_O30.js 18.02 kB │ gzip: 8.06 kB │ map: 68.31 kB\n../public/vue-assets/assets/linkify-B_q6MgwI.js 18.58 kB │ gzip: 10.37 kB │ map: 88.73 kB\n../public/vue-assets/assets/ai-reports-manage-Ber3EVyb.js 18.92 kB │ gzip: 6.75 kB │ map: 66.14 kB\n../public/vue-assets/assets/dist-C2hisF0L.js 19.32 kB │ gzip: 7.87 kB │ map: 390.77 kB\n../public/vue-assets/assets/pro-solid-svg-icons-DBOkpln3.js 19.46 kB │ gzip: 6.72 kB │ map: 2,692.14 kB\n../public/vue-assets/assets/purify.es-BxOw6oE6.js 21.49 kB │ gzip: 8.71 kB │ map: 83.43 kB\n../public/vue-assets/assets/ActionItems-BuNWx0BA.js 21.92 kB │ gzip: 8.54 kB │ map: 76.05 kB\n../public/vue-assets/assets/mobile-BMtylYi6.js 23.34 kB │ gzip: 9.71 kB │ map: 50.90 kB\n../public/vue-assets/assets/basic-modal-BdXsnFwC.js 23.87 kB │ gzip: 9.06 kB │ map: 64.15 kB\n../public/vue-assets/assets/GridView-CP7J7-qo.js 26.60 kB │ gzip: 10.05 kB │ map: 92.74 kB\n../public/vue-assets/assets/ondemand-kg1j3nIA.js 26.88 kB │ gzip: 9.39 kB │ map: 73.94 kB\n../public/vue-assets/assets/CrmLink-DKYsnHnx.js 27.91 kB │ gzip: 10.18 kB │ map: 93.18 kB\n../public/vue-assets/assets/liquor-tree-COUefof4.js 30.75 kB │ gzip: 9.58 kB │ map: 78.74 kB\n../public/vue-assets/assets/DealRiskList-C8q7faXl.js 34.39 kB │ gzip: 10.62 kB │ map: 115.18 kB\n../public/vue-assets/assets/AskAnything-BgSY9SKi.js 39.50 kB │ gzip: 14.97 kB │ map: 173.20 kB\n../public/vue-assets/assets/lib-CwM9toD2.js 39.69 kB │ gzip: 12.70 kB │ map: 138.34 kB\n../public/vue-assets/assets/AppFormField-COtjyq5c.js 41.91 kB │ gzip: 12.69 kB │ map: 150.73 kB\n../public/vue-assets/assets/deal-view-bksUVN5E.js 43.22 kB │ gzip: 14.34 kB │ map: 150.62 kB\n../public/vue-assets/assets/exports-D1lmea4O.js 47.84 kB │ gzip: 16.46 kB │ map: 294.48 kB\n../public/vue-assets/assets/playlists-Yn3gKP2z.js 48.28 kB │ gzip: 15.07 kB │ map: 153.25 kB\n../public/vue-assets/assets/callScoringTemplates-zeRn40uL.js 55.13 kB │ gzip: 13.28 kB │ map: 65.85 kB\n../public/vue-assets/assets/_copyObject-USkOnlaQ.js 61.28 kB │ gzip: 20.09 kB │ map: 239.59 kB\n../public/vue-assets/assets/pusher-znYCfz7U.js 62.98 kB │ gzip: 18.88 kB │ map: 219.27 kB\n../public/vue-assets/assets/onboard-BqxRx4zw.js 63.11 kB │ gzip: 21.85 kB │ map: 201.38 kB\n../public/vue-assets/assets/StatusBadge-C2H2xj9g.js 64.66 kB │ gzip: 22.96 kB │ map: 244.72 kB\n../public/vue-assets/assets/kiosk-BDlz4mpq.js 79.60 kB │ gzip: 22.65 kB │ map: 300.68 kB\n../public/vue-assets/assets/preload-helper-DCvhahzG.js 82.59 kB │ gzip: 27.46 kB │ map: 3,452.35 kB\n../public/vue-assets/assets/deal-insights-BnVEi9yc.js 94.84 kB │ gzip: 28.16 kB │ map: 292.79 kB\n../public/vue-assets/assets/ListView-CN2ZtlC7.js 115.71 kB │ gzip: 33.78 kB │ map: 308.10 kB\n../public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js 117.59 kB │ gzip: 38.71 kB │ map: 500.60 kB\n../public/vue-assets/assets/WelcomeLayout-B6wd32HG.js 120.67 kB │ gzip: 34.16 kB │ map: 258.56 kB\n../public/vue-assets/assets/dashboard-XCNXJG27.js 128.71 kB │ gzip: 40.05 kB │ map: 410.48 kB\n../public/vue-assets/assets/emoji-input-CSq87OVy.js 129.28 kB │ gzip: 36.72 kB │ map: 266.15 kB\n../public/vue-assets/assets/AppButton-D3qMdODr.js 133.44 kB │ gzip: 43.03 kB │ map: 516.67 kB\n../public/vue-assets/assets/sentry-DMzthP2u.js 164.28 kB │ gzip: 52.24 kB │ map: 831.82 kB\n../public/vue-assets/assets/OrgSettingsLayout-DYzOhe6q.js 176.33 kB │ gzip: 56.15 kB │ map: 623.43 kB\n../public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js 180.40 kB │ gzip: 67.85 kB │ map: 836.88 kB\n../public/vue-assets/assets/playback-DRpCBZO9.js 198.79 kB │ gzip: 61.85 kB │ map: 684.87 kB\n../public/vue-assets/assets/index.module-Bjlhgfd1.js 218.14 kB │ gzip: 64.16 kB │ map: 1,108.20 kB\n../public/vue-assets/assets/intl-tel-input-BW4mv40Q.js 264.94 kB │ gzip: 60.31 kB │ map: 475.61 kB\n../public/vue-assets/assets/team-insights-CPpPTAKV.js 298.57 kB │ gzip: 77.22 kB │ map: 959.96 kB\n../public/vue-assets/assets/popper-CQwVcrX4.js 307.13 kB │ gzip: 103.86 kB │ map: 1,245.28 kB\n../public/vue-assets/assets/PhoneField-CwCIoAYm.js 343.99 kB │ gzip: 84.90 kB │ map: 849.05 kB\n../public/vue-assets/assets/live-Cfb59XkF.js 367.43 kB │ gzip: 97.05 kB │ map: 792.41 kB\n../public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BNO485xV.js 689.63 kB │ gzip: 202.81 kB │ map: 3,016.64 kB\n../public/vue-assets/assets/index-1Grk9P2e.js 825.23 kB │ gzip: 72.55 kB │ map: 436.62 kB\n../public/vue-assets/assets/logged-in-layout-DArs1VTa.js 1,402.70 kB │ gzip: 438.08 kB │ map: 6,283.55 kB\n\n[PLUGIN_TIMINGS] Warning: Your build spent significant time in plugins. Here is a breakdown:\n - vite:css (61%)\n - vite-plugin-externals (12%)\n - vite:vue (9%)\n - sentry-vite-plugin (8%)\n - vite:worker (6%)\nSee https://rolldown.rs/options/checks#plugintimings for more details.\n\n[plugin builtin:vite-reporter] \n(!) Some chunks are larger than 500 kB after minification. Consider:\n- Using dynamic import() to code-split the application\n- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting\n- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.\n✓ built in 22.57s\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yarn install\n➤ YN0000: · Yarn 4.12.0\n➤ YN0000: ┌ Resolution step\n➤ YN0000: └ Completed\n➤ YN0000: ┌ Post-resolution validation\n➤ YN0002: │ root-workspace-0b6124@workspace:. doesn't provide @testing-library/dom (pea6ced), requested by @testing-library/user-event.\n➤ YN0002: │ root-workspace-0b6124@workspace:. doesn't provide noty (p5bdb0d), requested by vue-components.\n➤ YN0086: │ Some peer dependencies are incorrectly met by your project; run yarn explain peer-requirements <hash> for details, where <hash> is the six-letter p-prefixed code.\n➤ YN0086: │ Some peer dependencies are incorrectly met by dependencies; run yarn explain peer-requirements for details.\n➤ YN0000: └ Completed\n➤ YN0000: ┌ Fetch step\n➤ YN0000: └ Completed in 1s 655ms\n➤ YN0000: ┌ Link step\n➤ YN0008: │ root-workspace-0b6124@workspace:. must be rebuilt because its dependency tree changed\n➤ YN0000: └ Completed in 0s 969ms\n➤ YN0000: · Done with warnings in 3s 14ms\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yarn build\n[dotenv@17.3.1] injecting env (0) from .env -- tip: ⚙️\u0000 override existing env vars with { override: true }\nvite v8.0.0 building client environment for production...\n✓ 4656 modules transformed.\n[sentry-vite-plugin] Warning: No auth token provided. Will not create release. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\n[sentry-vite-plugin] Warning: No auth token provided. Will not upload source maps. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\ncomputing gzip size...\n../public/vue-assets/index.html 4.58 kB │ gzip: 1.15 kB\n../public/vue-assets/assets/job-adder-B2LcgC4o.png 5.41 kB\n../public/vue-assets/assets/dixa-DzT89tKh.png 7.87 kB\n../public/vue-assets/assets/planhat-CQycOTMW.svg 8.61 kB │ gzip: 3.48 kB\n../public/vue-assets/assets/funding-circle-9iGnDGsz.png 9.43 kB\n../public/vue-assets/assets/cision-uOdx_YiN.svg 14.41 kB │ gzip: 6.32 kB\n../public/vue-assets/assets/les-mills-DWabpmYc.png 15.73 kB\n../public/vue-assets/assets/superside-btT37L9L.png 26.69 kB\n../public/vue-assets/assets/flags-a2kmUSbF.webp 28.18 kB\n../public/vue-assets/assets/quinyx-RGQNHx6o.png 63.17 kB\n../public/vue-assets/assets/flags@2x-gR6KPp3x.webp 66.44 kB\n../public/vue-assets/assets/bg-marketing-CAIHIJMj.png 70.66 kB\n../public/vue-assets/.vite/manifest.json 85.73 kB │ gzip: 8.32 kB\n../public/vue-assets/assets/jiminny-score-DtTbKZaD.svg 131.93 kB │ gzip: 86.07 kB\n../public/vue-assets/assets/wavy-bg-vnCaCm9m.webm 670.97 kB\n../public/vue-assets/assets/wavy-bg-Op6dRIjR.mp4 14,484.63 kB\n../public/vue-assets/assets/AppForm-EEGPL5K8.css 0.04 kB │ gzip: 0.06 kB\n../public/vue-assets/assets/RecipientsCell-DNOSS8k1.css 0.12 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/softphone-coach-Cy_9nuQZ.css 0.13 kB │ gzip: 0.10 kB\n../public/vue-assets/assets/locked-CHS8VPur.css 0.15 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/mobile-KPnGhnTp.css 0.16 kB │ gzip: 0.14 kB\n../public/vue-assets/assets/BuildInfo-CxNQv_OV.css 0.20 kB │ gzip: 0.17 kB\n../public/vue-assets/assets/AvatarsStack-C0Nvfh1V.css 0.28 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/LogoLong100-ByQTvUY3.css 0.32 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/connect--xBDzLbc.css 0.34 kB │ gzip: 0.21 kB\n../public/vue-assets/assets/KioskBanner-xU3FRd8g.css 0.41 kB │ gzip: 0.27 kB\n../public/vue-assets/assets/extension-installed-BPBhG13P.css 0.41 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/FollowModal-A0rJmIOK.css 0.45 kB │ gzip: 0.23 kB\n../public/vue-assets/assets/GenericMessage-DutAlObK.css 0.49 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/textarea-caret-DCiNUER_.css 0.51 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/AppLinks-BznfGcBR.css 0.58 kB │ gzip: 0.33 kB\n../public/vue-assets/assets/SeekBtn-_VMPIk6v.css 0.63 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/emoji-input-kJWRbWf8.css 0.68 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/activity-preview-BDxP2S0-.css 0.70 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/ListLoader-DQqAw-8s.css 0.73 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/filters-Bgb6cQD6.css 0.75 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/AiContext-Bg-zjjbA.css 0.99 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/vue3-daterange-picker-izPOBj3P.css 1.02 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/PrimaryButton-CJj2FzKS.css 1.08 kB │ gzip: 0.34 kB\n../public/vue-assets/assets/other-BTuPXtu_.css 1.15 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/jenesius-vue-modal-DDcfejHO.css 1.17 kB │ gzip: 0.48 kB\n../public/vue-assets/assets/TabEmptyState-DOlIO68k.css 1.21 kB │ gzip: 0.53 kB\n../public/vue-assets/assets/AppAlert-BlSJKiqj.css 1.29 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/useActivityCustomerName-CBjfIUfJ.css 1.42 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/UserAvatar-co8l9mMk.css 1.51 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/basic-modal-Dx7ZBbcA.css 1.55 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/login-DYbcBesM.css 1.58 kB │ gzip: 0.61 kB\n../public/vue-assets/assets/DrawerWidget-poAmqspk.css 1.67 kB │ gzip: 0.69 kB\n../public/vue-assets/assets/ActionItems-5FkZSo3G.css 1.79 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/join-conference-DQ6Yl7eU.css 1.82 kB │ gzip: 0.75 kB\n../public/vue-assets/assets/InputText-C-DxDNSV.css 1.95 kB │ gzip: 0.71 kB\n../public/vue-assets/assets/GoogleLikeButton-DvQHxbqR.css 2.11 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/usePusherEventListener-CDj8aSNp.css 2.20 kB │ gzip: 0.73 kB\n../public/vue-assets/assets/onboard-IzGiQ-EC.css 2.32 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/ai-reports-manage-DaynTl62.css 2.34 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/StatusBadge-B4N12dzU.css 2.35 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/GridView-CsP1Jk-k.css 2.36 kB │ gzip: 0.90 kB\n../public/vue-assets/assets/WelcomeLayout-6AX86p6F.css 2.38 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/snackbar-CS_iqvrv.css 2.45 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/ai-reports-mtqGqPPq.css 2.47 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/Comment-gmAfiuer.css 2.63 kB │ gzip: 1.00 kB\n../public/vue-assets/assets/meeting-consent-BasRvxE3.css 2.66 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/DealRiskList-CwxmDnSC.css 2.67 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/add-to-playlist-modal-C_ad1hht.css 2.84 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/live-_zT5qBc1.css 3.60 kB │ gzip: 1.16 kB\n../public/vue-assets/assets/RadioField-DDp8Y71G.css 3.64 kB │ gzip: 1.20 kB\n../public/vue-assets/assets/vue-multiselect-BkQNV7oL.css 3.80 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/activity-preview-result-Db1Da81T.css 3.89 kB │ gzip: 1.15 kB\n../public/vue-assets/assets/liquor-tree-CB5oh8v1.css 4.08 kB │ gzip: 1.24 kB\n../public/vue-assets/assets/sentry-CSjHcvmn.css 4.47 kB │ gzip: 1.08 kB\n../public/vue-assets/assets/AiAutomation-Cj1t8v5y.css 4.61 kB │ gzip: 0.84 kB\n../public/vue-assets/assets/export-portal-CyIUVTEe.css 5.34 kB │ gzip: 1.65 kB\n../public/vue-assets/assets/InputField-C4hoKJ7Z.css 6.13 kB │ gzip: 1.27 kB\n../public/vue-assets/assets/vue-mq-bh4L87Tr.css 6.64 kB │ gzip: 1.95 kB\n../public/vue-assets/assets/invitation-EPR1t-bH.css 6.74 kB │ gzip: 2.22 kB\n../public/vue-assets/assets/ondemand-C2m0YVGA.css 6.84 kB │ gzip: 2.02 kB\n../public/vue-assets/assets/AskAnything-CyvEhHS1.css 8.60 kB │ gzip: 2.60 kB\n../public/vue-assets/assets/AppButton-BIeoar_U.css 8.88 kB │ gzip: 2.04 kB\n../public/vue-assets/assets/kiosk-BcheyWWL.css 9.56 kB │ gzip: 2.53 kB\n../public/vue-assets/assets/AiCrmNotes-nNTe_mOY.css 10.62 kB │ gzip: 1.93 kB\n../public/vue-assets/assets/deal-view-DJzG7PLp.css 11.70 kB │ gzip: 3.29 kB\n../public/vue-assets/assets/tokens-DZgWrfKd.css 12.44 kB │ gzip: 2.82 kB\n../public/vue-assets/assets/tokens-DL2BPbai.css 12.89 kB │ gzip: 2.87 kB\n../public/vue-assets/assets/playlists-Cy3t3kYU.css 13.54 kB │ gzip: 3.16 kB\n../public/vue-assets/assets/PhoneField-D_kGOah0.css 13.75 kB │ gzip: 3.44 kB\n../public/vue-assets/assets/ListView-Dt0qkyuY.css 17.07 kB │ gzip: 4.11 kB\n../public/vue-assets/assets/intl-tel-input-DgmgTINs.css 17.11 kB │ gzip: 5.37 kB\n../public/vue-assets/assets/AppFormField-DvpfRzi8.css 18.98 kB │ gzip: 4.42 kB\n../public/vue-assets/assets/OrgSettingsLayout-BogdXtgY.css 21.98 kB │ gzip: 5.51 kB\n../public/vue-assets/assets/dashboard-DQw3TKyk.css 22.74 kB │ gzip: 4.35 kB\n../public/vue-assets/assets/deal-insights-CfX3UNzh.css 29.66 kB │ gzip: 6.44 kB\n../public/vue-assets/assets/team-insights-CjVhm0JN.css 42.31 kB │ gzip: 8.46 kB\n../public/vue-assets/assets/playback-CvJP5tX1.css 44.55 kB │ gzip: 10.36 kB\n../public/vue-assets/assets/video-js-skin-DYZluGb-.css 47.73 kB │ gzip: 12.63 kB\n../public/vue-assets/assets/assets-CAbfI4CY.css 83.38 kB │ gzip: 51.71 kB\n../public/vue-assets/assets/logged-in-layout-1mAqkmnS.css 153.67 kB │ gzip: 27.18 kB\n../public/vue-assets/assets/assets-xH6dx_9q.css 259.71 kB │ gzip: 181.71 kB\n../public/vue-assets/assets/directives-DJJeJJOP.js 0.53 kB │ gzip: 0.36 kB │ map: 0.32 kB\n../public/vue-assets/assets/jenesius-vue-modal-BuBhyl83.js 0.54 kB │ gzip: 0.37 kB │ map: 0.47 kB\n../public/vue-assets/assets/spark-D_-Wgfar.js 0.54 kB │ gzip: 0.35 kB │ map: 0.40 kB\n../public/vue-assets/assets/url-messenger-_CGQa-lH.js 0.56 kB │ gzip: 0.37 kB │ map: 0.46 kB\n../public/vue-assets/assets/wavy-bg-Cec-_GDj.js 0.57 kB │ gzip: 0.36 kB │ map: 0.35 kB\n../public/vue-assets/assets/component-css-class-CtO0AVgW.js 0.61 kB │ gzip: 0.40 kB │ map: 0.91 kB\n../public/vue-assets/assets/theme-B5VdyMN_.js 0.63 kB │ gzip: 0.41 kB │ map: 0.49 kB\n../public/vue-assets/assets/pick-C8Pw7kHx.js 0.71 kB │ gzip: 0.45 kB │ map: 1.59 kB\n../public/vue-assets/assets/utils-BjsEoLDB.js 0.77 kB │ gzip: 0.50 kB │ map: 1.68 kB\n../public/vue-assets/assets/throttle-siTOKEMt.js 0.78 kB │ gzip: 0.49 kB │ map: 3.24 kB\n../public/vue-assets/assets/lastFilters-B_B_O5LY.js 0.80 kB │ gzip: 0.51 kB │ map: 1.12 kB\n../public/vue-assets/assets/useAuthState-Bpx8WpBc.js 0.81 kB │ gzip: 0.50 kB │ map: 1.64 kB\n../public/vue-assets/assets/v-focus-618yf9WO.js 0.85 kB │ gzip: 0.52 kB │ map: 1.42 kB\n../public/vue-assets/assets/ListLoader-bfQmkVGu.js 0.87 kB │ gzip: 0.54 kB │ map: 2.90 kB\n../public/vue-assets/assets/utils-CAMifZzY.js 0.88 kB │ gzip: 0.53 kB │ map: 2.45 kB\n../public/vue-assets/assets/mobileApp-QFLS3kId.js 0.89 kB │ gzip: 0.54 kB │ map: 1.12 kB\n../public/vue-assets/assets/pickBy-CsQ1aO-4.js 0.95 kB │ gzip: 0.59 kB │ map: 2.57 kB\n../public/vue-assets/assets/BuildInfo-CIGre86L.js 1.00 kB │ gzip: 0.65 kB │ map: 1.48 kB\n../public/vue-assets/assets/LogoShort100-D_SQdBYB.js 1.01 kB │ gzip: 0.64 kB │ map: 1.23 kB\n../public/vue-assets/assets/useDrawerModal-CmhXI-pZ.js 1.04 kB │ gzip: 0.65 kB │ map: 1.94 kB\n../public/vue-assets/assets/_getAllKeysIn-CkMQAnJa.js 1.12 kB │ gzip: 0.67 kB │ map: 4.83 kB\n../public/vue-assets/assets/useAutosizeTextarea-DyMwh_20.js 1.13 kB │ gzip: 0.70 kB │ map: 3.16 kB\n../public/vue-assets/assets/planhat-DPA36Hcn.js 1.21 kB │ gzip: 0.71 kB │ map: 2.18 kB\n../public/vue-assets/assets/useStoreModule-Cx6UoGVJ.js 1.35 kB │ gzip: 0.72 kB │ map: 6.03 kB\n../public/vue-assets/assets/GenericMessage-BxGsrfvw.js 1.39 kB │ gzip: 0.75 kB │ map: 1.90 kB\n../public/vue-assets/assets/BrowserExtensionInstaller-DimyBPIi.js 1.45 kB │ gzip: 0.78 kB │ map: 2.05 kB\n../public/vue-assets/assets/debounce-7ycH7TWx.js 1.53 kB │ gzip: 0.83 kB │ map: 8.61 kB\n../public/vue-assets/assets/extension-installed-DUcV_uoi.js 1.60 kB │ gzip: 0.91 kB │ map: 3.20 kB\n../public/vue-assets/assets/RecipientsCell-BS9gDGey.js 1.61 kB │ gzip: 0.93 kB │ map: 4.04 kB\n../public/vue-assets/assets/PrimaryButton-DaKR3UAG.js 1.61 kB │ gzip: 0.87 kB │ map: 3.73 kB\n../public/vue-assets/assets/KioskBanner-CYQu1FG7.js 1.66 kB │ gzip: 1.00 kB │ map: 2.88 kB\n../public/vue-assets/assets/LogoLong100-Hid0kvjR.js 1.78 kB │ gzip: 1.08 kB │ map: 5.91 kB\n../public/vue-assets/assets/GoogleLikeButton-CGFy3nbz.js 1.80 kB │ gzip: 0.84 kB │ map: 4.71 kB\n../public/vue-assets/assets/AppAlert-DFp0nftl.js 1.86 kB │ gzip: 1.01 kB │ map: 6.51 kB\n../public/vue-assets/assets/usePusherEventListener-BjhdeObt.js 1.93 kB │ gzip: 1.08 kB │ map: 9.59 kB\n../public/vue-assets/assets/AppForm-BgtH5xsM.js 2.01 kB │ gzip: 1.10 kB │ map: 6.89 kB\n../public/vue-assets/assets/vee-validate-rules-IFeGZHb3.js 2.02 kB │ gzip: 0.89 kB │ map: 22.31 kB\n../public/vue-assets/assets/literals-CXSwYC8y.js 2.05 kB │ gzip: 0.97 kB │ map: 3.08 kB\n../public/vue-assets/assets/TabEmptyState-pk8vRxJt.js 2.16 kB │ gzip: 1.12 kB │ map: 8.26 kB\n../public/vue-assets/assets/Replies-B2cExxmx.js 2.23 kB │ gzip: 1.14 kB │ map: 2.40 kB\n../public/vue-assets/assets/settings--S7_RYRE.js 2.34 kB │ gzip: 1.24 kB │ map: 1.73 kB\n../public/vue-assets/assets/SeekBtn-CMp8sSUA.js 2.47 kB │ gzip: 1.18 kB │ map: 9.27 kB\n../public/vue-assets/assets/AiAutomation-cuK1068l.js 2.59 kB │ gzip: 1.37 kB │ map: 4.45 kB\n../public/vue-assets/assets/locked-DxPLOs1S.js 2.59 kB │ gzip: 1.42 kB │ map: 4.00 kB\n../public/vue-assets/assets/DrawerWidget-BPC-tCgo.js 2.68 kB │ gzip: 1.33 kB │ map: 6.95 kB\n../public/vue-assets/assets/vue-infinite-scroll-D0cI4gH6.js 2.72 kB │ gzip: 1.28 kB │ map: 9.99 kB\n../public/vue-assets/assets/useActivityCustomerName-CYuaZj-p.js 2.87 kB │ gzip: 1.41 kB │ map: 6.27 kB\n../public/vue-assets/assets/other-CVxaD0_B.js 2.97 kB │ gzip: 1.50 kB │ map: 3.38 kB\n../public/vue-assets/assets/InputDropdown-CBM8xiPT.js 3.33 kB │ gzip: 1.61 kB │ map: 6.02 kB\n../public/vue-assets/assets/AvatarsStack-hxex9Ic8.js 3.40 kB │ gzip: 1.51 kB │ map: 9.54 kB\n../public/vue-assets/assets/activity-preview--ydYatRe.js 3.45 kB │ gzip: 1.68 kB │ map: 10.11 kB\n../public/vue-assets/assets/FollowModal-SMYvVwqG.js 3.70 kB │ gzip: 1.81 kB │ map: 8.86 kB\n../public/vue-assets/assets/softphone-coach-CkLTUCy6.js 3.74 kB │ gzip: 1.96 kB │ map: 5.03 kB\n../public/vue-assets/assets/store-DPy-uEfS.js 4.10 kB │ gzip: 2.00 kB │ map: 18.73 kB\n../public/vue-assets/assets/AiContext-D36CpnKB.js 4.34 kB │ gzip: 2.08 kB │ map: 13.11 kB\n../public/vue-assets/assets/meeting-consent-C0YRJ_xR.js 4.43 kB │ gzip: 2.05 kB │ map: 9.72 kB\n../public/vue-assets/assets/vue-multiselect-VB2Agtp1.js 4.49 kB │ gzip: 1.93 kB │ map: 14.85 kB\n../public/vue-assets/assets/snackbarNotifications-DjDIGkWd.js 4.83 kB │ gzip: 1.58 kB │ map: 11.62 kB\n../public/vue-assets/assets/invitation--7sddGcP.js 4.88 kB │ gzip: 2.21 kB │ map: 19.38 kB\n../public/vue-assets/assets/connect-Bfq7LjjQ.js 5.17 kB │ gzip: 2.28 kB │ map: 10.35 kB\n../public/vue-assets/assets/textarea-caret-B2HdIdpZ.js 5.25 kB │ gzip: 2.41 kB │ map: 14.48 kB\n../public/vue-assets/assets/snackbar-Bq-YQ7pz.js 5.48 kB │ gzip: 2.37 kB │ map: 20.43 kB\n../public/vue-assets/assets/RadioField-2SeSZB7D.js 5.65 kB │ gzip: 2.22 kB │ map: 15.82 kB\n../public/vue-assets/assets/filters-D_eXZdIL.js 5.76 kB │ gzip: 2.17 kB │ map: 16.36 kB\n../public/vue-assets/assets/useActivityHelper-Dj5DULYF.js 5.84 kB │ gzip: 2.51 kB │ map: 14.26 kB\n../public/vue-assets/assets/vue-scrollto-ul3pFdrb.js 5.97 kB │ gzip: 2.72 kB │ map: 23.98 kB\n../public/vue-assets/assets/join-conference-gY3HLgqP.js 6.25 kB │ gzip: 2.83 kB │ map: 24.66 kB\n../public/vue-assets/assets/pluralize-BeKVJaE0.js 6.34 kB │ gzip: 2.70 kB │ map: 18.28 kB\n../public/vue-assets/assets/login-Bz_Gi9sn.js 7.02 kB │ gzip: 3.16 kB │ map: 17.51 kB\n../public/vue-assets/assets/AppLinks-Bg20Hslb.js 7.14 kB │ gzip: 2.80 kB │ map: 14.87 kB\n../public/vue-assets/assets/InputField-CkdrqKBp.js 7.20 kB │ gzip: 2.58 kB │ map: 19.39 kB\n../public/vue-assets/assets/UserAvatar-UKkSAS4R.js 7.93 kB │ gzip: 3.32 kB │ map: 34.91 kB\n../public/vue-assets/assets/InputText-Dgwsn6KA.js 7.99 kB │ gzip: 3.05 kB │ map: 20.64 kB\n../public/vue-assets/assets/AiCrmNotes-DNS51mtN.js 8.27 kB │ gzip: 3.32 kB │ map: 34.03 kB\n../public/vue-assets/assets/vue-mq-CmpUzQtD.js 8.84 kB │ gzip: 3.54 kB │ map: 23.69 kB\n../public/vue-assets/assets/pro-duotone-svg-icons-BSBZV3-t.js 9.51 kB │ gzip: 3.43 kB │ map: 3,146.68 kB\n../public/vue-assets/assets/activity-preview-result-CIGPrkbw.js 10.65 kB │ gzip: 3.98 kB │ map: 38.51 kB\n../public/vue-assets/assets/pro-regular-svg-icons-CWpSBR20.js 13.10 kB │ gzip: 4.64 kB │ map: 3,178.15 kB\n../public/vue-assets/assets/add-to-playlist-modal-CPwNAOXA.js 14.99 kB │ gzip: 5.98 kB │ map: 51.34 kB\n../public/vue-assets/assets/ai-reports-DAEn44FQ.js 15.17 kB │ gzip: 6.04 kB │ map: 51.85 kB\n../public/vue-assets/assets/export-portal-DB0CksQW.js 15.68 kB │ gzip: 6.22 kB │ map: 52.78 kB\n../public/vue-assets/assets/Comment-ClBvXCXq.js 15.92 kB │ gzip: 5.72 kB │ map: 39.96 kB\n../public/vue-assets/assets/useragent-DQ4F_O30.js 18.02 kB │ gzip: 8.06 kB │ map: 68.31 kB\n../public/vue-assets/assets/linkify-B_q6MgwI.js 18.58 kB │ gzip: 10.37 kB │ map: 88.73 kB\n../public/vue-assets/assets/ai-reports-manage-BTQmbI4-.js 18.92 kB │ gzip: 6.76 kB │ map: 66.14 kB\n../public/vue-assets/assets/dist-C2hisF0L.js 19.32 kB │ gzip: 7.87 kB │ map: 390.77 kB\n../public/vue-assets/assets/pro-solid-svg-icons-DBOkpln3.js 19.46 kB │ gzip: 6.72 kB │ map: 2,692.14 kB\n../public/vue-assets/assets/purify.es-BxOw6oE6.js 21.49 kB │ gzip: 8.71 kB │ map: 83.43 kB\n../public/vue-assets/assets/ActionItems-CqgLpVFE.js 21.92 kB │ gzip: 8.54 kB │ map: 76.05 kB\n../public/vue-assets/assets/mobile-4e6kQ9l_.js 23.34 kB │ gzip: 9.71 kB │ map: 50.90 kB\n../public/vue-assets/assets/basic-modal-BdXsnFwC.js 23.87 kB │ gzip: 9.06 kB │ map: 64.15 kB\n../public/vue-assets/assets/GridView-CocIYUaz.js 26.60 kB │ gzip: 10.05 kB │ map: 92.74 kB\n../public/vue-assets/assets/ondemand-CE8XLH98.js 26.88 kB │ gzip: 9.38 kB │ map: 73.94 kB\n../public/vue-assets/assets/CrmLink-DKYsnHnx.js 27.91 kB │ gzip: 10.18 kB │ map: 93.18 kB\n../public/vue-assets/assets/liquor-tree-COUefof4.js 30.75 kB │ gzip: 9.58 kB │ map: 78.74 kB\n../public/vue-assets/assets/DealRiskList-3DSxnTNQ.js 34.39 kB │ gzip: 10.62 kB │ map: 115.18 kB\n../public/vue-assets/assets/AskAnything-BQ36EoGS.js 39.50 kB │ gzip: 14.97 kB │ map: 173.20 kB\n../public/vue-assets/assets/lib-CwM9toD2.js 39.69 kB │ gzip: 12.70 kB │ map: 138.34 kB\n../public/vue-assets/assets/AppFormField-CqCXZwrk.js 41.91 kB │ gzip: 12.69 kB │ map: 150.73 kB\n../public/vue-assets/assets/deal-view-B92qaj32.js 43.22 kB │ gzip: 14.34 kB │ map: 150.62 kB\n../public/vue-assets/assets/exports-D1lmea4O.js 47.84 kB │ gzip: 16.46 kB │ map: 294.48 kB\n../public/vue-assets/assets/playlists-u3UeUUz5.js 48.28 kB │ gzip: 15.07 kB │ map: 153.25 kB\n../public/vue-assets/assets/callScoringTemplates-zeRn40uL.js 55.13 kB │ gzip: 13.28 kB │ map: 65.85 kB\n../public/vue-assets/assets/_copyObject-USkOnlaQ.js 61.28 kB │ gzip: 20.09 kB │ map: 239.59 kB\n../public/vue-assets/assets/pusher-znYCfz7U.js 62.98 kB │ gzip: 18.88 kB │ map: 219.27 kB\n../public/vue-assets/assets/onboard-CCnYJmCP.js 63.11 kB │ gzip: 21.85 kB │ map: 201.38 kB\n../public/vue-assets/assets/StatusBadge-CNGFZm8P.js 64.66 kB │ gzip: 22.96 kB │ map: 244.72 kB\n../public/vue-assets/assets/kiosk-D6N5-ivP.js 79.60 kB │ gzip: 22.65 kB │ map: 300.68 kB\n../public/vue-assets/assets/preload-helper-DCvhahzG.js 82.59 kB │ gzip: 27.46 kB │ map: 3,452.35 kB\n../public/vue-assets/assets/deal-insights-jlkz65MZ.js 94.84 kB │ gzip: 28.16 kB │ map: 292.79 kB\n../public/vue-assets/assets/ListView-DmKqYVi1.js 115.71 kB │ gzip: 33.78 kB │ map: 308.10 kB\n../public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js 117.59 kB │ gzip: 38.71 kB │ map: 500.60 kB\n../public/vue-assets/assets/WelcomeLayout-B6wd32HG.js 120.67 kB │ gzip: 34.16 kB │ map: 258.56 kB\n../public/vue-assets/assets/dashboard-BWoBIPKA.js 128.71 kB │ gzip: 40.05 kB │ map: 410.48 kB\n../public/vue-assets/assets/emoji-input-CSq87OVy.js 129.28 kB │ gzip: 36.72 kB │ map: 266.15 kB\n../public/vue-assets/assets/AppButton-D3qMdODr.js 133.44 kB │ gzip: 43.03 kB │ map: 516.67 kB\n../public/vue-assets/assets/sentry-icPKypZa.js 164.28 kB │ gzip: 52.24 kB │ map: 831.82 kB\n../public/vue-assets/assets/OrgSettingsLayout-BZ-J0giH.js 176.33 kB │ gzip: 56.15 kB │ map: 623.43 kB\n../public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js 180.40 kB │ gzip: 67.85 kB │ map: 836.88 kB\n../public/vue-assets/assets/playback-DKsqp_aL.js 198.79 kB │ gzip: 61.85 kB │ map: 684.87 kB\n../public/vue-assets/assets/index.module-Bjlhgfd1.js 218.14 kB │ gzip: 64.16 kB │ map: 1,108.20 kB\n../public/vue-assets/assets/intl-tel-input-BW4mv40Q.js 264.94 kB │ gzip: 60.31 kB │ map: 475.61 kB\n../public/vue-assets/assets/team-insights-D9P6ojjD.js 298.57 kB │ gzip: 77.22 kB │ map: 959.96 kB\n../public/vue-assets/assets/popper-CQwVcrX4.js 307.13 kB │ gzip: 103.86 kB │ map: 1,245.28 kB\n../public/vue-assets/assets/PhoneField-CwCIoAYm.js 343.99 kB │ gzip: 84.90 kB │ map: 849.05 kB\n../public/vue-assets/assets/live-DWumv-QD.js 367.43 kB │ gzip: 97.05 kB │ map: 792.41 kB\n../public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BNO485xV.js 689.63 kB │ gzip: 202.81 kB │ map: 3,016.64 kB\n../public/vue-assets/assets/index-DiEs6xIb.js 825.23 kB │ gzip: 72.54 kB │ map: 436.62 kB\n../public/vue-assets/assets/logged-in-layout-Cq1ztH5O.js 1,402.70 kB │ gzip: 438.08 kB │ map: 6,283.55 kB\n\n[plugin builtin:vite-reporter] \n(!) Some chunks are larger than 500 kB after minification. Consider:\n- Using dynamic import() to code-split the application\n- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting\n- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.\n✓ built in 13.59s\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yarn build\n[dotenv@17.3.1] injecting env (0) from .env -- tip: ⚙️\u0000 load multiple .env files with { path: ['.env.local', '.env'] }\nvite v8.0.0 building client environment for production...\n✓ 4656 modules transformed.\n[sentry-vite-plugin] Warning: No auth token provided. Will not create release. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\n[sentry-vite-plugin] Warning: No auth token provided. Will not upload source maps. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\ncomputing gzip size...\n../public/vue-assets/index.html 4.58 kB │ gzip: 1.16 kB\n../public/vue-assets/assets/job-adder-B2LcgC4o.png 5.41 kB\n../public/vue-assets/assets/dixa-DzT89tKh.png 7.87 kB\n../public/vue-assets/assets/planhat-CQycOTMW.svg 8.61 kB │ gzip: 3.48 kB\n../public/vue-assets/assets/funding-circle-9iGnDGsz.png 9.43 kB\n../public/vue-assets/assets/cision-uOdx_YiN.svg 14.41 kB │ gzip: 6.32 kB\n../public/vue-assets/assets/les-mills-DWabpmYc.png 15.73 kB\n../public/vue-assets/assets/superside-btT37L9L.png 26.69 kB\n../public/vue-assets/assets/flags-a2kmUSbF.webp 28.18 kB\n../public/vue-assets/assets/quinyx-RGQNHx6o.png 63.17 kB\n../public/vue-assets/assets/flags@2x-gR6KPp3x.webp 66.44 kB\n../public/vue-assets/assets/bg-marketing-CAIHIJMj.png 70.66 kB\n../public/vue-assets/.vite/manifest.json 85.73 kB │ gzip: 8.33 kB\n../public/vue-assets/assets/jiminny-score-DtTbKZaD.svg 131.93 kB │ gzip: 86.07 kB\n../public/vue-assets/assets/wavy-bg-vnCaCm9m.webm 670.97 kB\n../public/vue-assets/assets/wavy-bg-Op6dRIjR.mp4 14,484.63 kB\n../public/vue-assets/assets/AppForm-EEGPL5K8.css 0.04 kB │ gzip: 0.06 kB\n../public/vue-assets/assets/RecipientsCell-DNOSS8k1.css 0.12 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/softphone-coach-Cy_9nuQZ.css 0.13 kB │ gzip: 0.10 kB\n../public/vue-assets/assets/locked-CHS8VPur.css 0.15 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/mobile-KPnGhnTp.css 0.16 kB │ gzip: 0.14 kB\n../public/vue-assets/assets/BuildInfo-CxNQv_OV.css 0.20 kB │ gzip: 0.17 kB\n../public/vue-assets/assets/AvatarsStack-C0Nvfh1V.css 0.28 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/LogoLong100-ByQTvUY3.css 0.32 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/connect--xBDzLbc.css 0.34 kB │ gzip: 0.21 kB\n../public/vue-assets/assets/KioskBanner-xU3FRd8g.css 0.41 kB │ gzip: 0.27 kB\n../public/vue-assets/assets/extension-installed-BPBhG13P.css 0.41 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/FollowModal-A0rJmIOK.css 0.45 kB │ gzip: 0.23 kB\n../public/vue-assets/assets/GenericMessage-DutAlObK.css 0.49 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/textarea-caret-DCiNUER_.css 0.51 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/AppLinks-BznfGcBR.css 0.58 kB │ gzip: 0.33 kB\n../public/vue-assets/assets/SeekBtn-_VMPIk6v.css 0.63 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/emoji-input-kJWRbWf8.css 0.68 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/activity-preview-BDxP2S0-.css 0.70 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/ListLoader-DQqAw-8s.css 0.73 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/filters-Bgb6cQD6.css 0.75 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/AiContext-Bg-zjjbA.css 0.99 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/vue3-daterange-picker-izPOBj3P.css 1.02 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/PrimaryButton-CJj2FzKS.css 1.08 kB │ gzip: 0.34 kB\n../public/vue-assets/assets/other-BTuPXtu_.css 1.15 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/jenesius-vue-modal-DDcfejHO.css 1.17 kB │ gzip: 0.48 kB\n../public/vue-assets/assets/TabEmptyState-DOlIO68k.css 1.21 kB │ gzip: 0.53 kB\n../public/vue-assets/assets/AppAlert-BlSJKiqj.css 1.29 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/useActivityCustomerName-CBjfIUfJ.css 1.42 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/UserAvatar-co8l9mMk.css 1.51 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/basic-modal-Dx7ZBbcA.css 1.55 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/login-DYbcBesM.css 1.58 kB │ gzip: 0.61 kB\n../public/vue-assets/assets/DrawerWidget-poAmqspk.css 1.67 kB │ gzip: 0.69 kB\n../public/vue-assets/assets/ActionItems-5FkZSo3G.css 1.79 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/join-conference-DQ6Yl7eU.css 1.82 kB │ gzip: 0.75 kB\n../public/vue-assets/assets/InputText-C-DxDNSV.css 1.95 kB │ gzip: 0.71 kB\n../public/vue-assets/assets/GoogleLikeButton-DvQHxbqR.css 2.11 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/usePusherEventListener-CDj8aSNp.css 2.20 kB │ gzip: 0.73 kB\n../public/vue-assets/assets/onboard-IzGiQ-EC.css 2.32 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/ai-reports-manage-DaynTl62.css 2.34 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/StatusBadge-B4N12dzU.css 2.35 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/GridView-CsP1Jk-k.css 2.36 kB │ gzip: 0.90 kB\n../public/vue-assets/assets/WelcomeLayout-6AX86p6F.css 2.38 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/snackbar-CS_iqvrv.css 2.45 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/ai-reports-mtqGqPPq.css 2.47 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/Comment-gmAfiuer.css 2.63 kB │ gzip: 1.00 kB\n../public/vue-assets/assets/meeting-consent-BasRvxE3.css 2.66 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/DealRiskList-CwxmDnSC.css 2.67 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/add-to-playlist-modal-C_ad1hht.css 2.84 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/live-_zT5qBc1.css 3.60 kB │ gzip: 1.16 kB\n../public/vue-assets/assets/RadioField-DDp8Y71G.css 3.64 kB │ gzip: 1.20 kB\n../public/vue-assets/assets/vue-multiselect-BkQNV7oL.css 3.80 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/activity-preview-result-Db1Da81T.css 3.89 kB │ gzip: 1.15 kB\n../public/vue-assets/assets/liquor-tree-CB5oh8v1.css 4.08 kB │ gzip: 1.24 kB\n../public/vue-assets/assets/sentry-CSjHcvmn.css 4.47 kB │ gzip: 1.08 kB\n../public/vue-assets/assets/AiAutomation-Cj1t8v5y.css 4.61 kB │ gzip: 0.84 kB\n../public/vue-assets/assets/export-portal-CyIUVTEe.css 5.34 kB │ gzip: 1.65 kB\n../public/vue-assets/assets/InputField-C4hoKJ7Z.css 6.13 kB │ gzip: 1.27 kB\n../public/vue-assets/assets/vue-mq-bh4L87Tr.css 6.64 kB │ gzip: 1.95 kB\n../public/vue-assets/assets/invitation-EPR1t-bH.css 6.74 kB │ gzip: 2.22 kB\n../public/vue-assets/assets/ondemand-C2m0YVGA.css 6.84 kB │ gzip: 2.02 kB\n../public/vue-assets/assets/AskAnything-CyvEhHS1.css 8.60 kB │ gzip: 2.60 kB\n../public/vue-assets/assets/AppButton-BIeoar_U.css 8.88 kB │ gzip: 2.04 kB\n../public/vue-assets/assets/kiosk-BcheyWWL.css 9.56 kB │ gzip: 2.53 kB\n../public/vue-assets/assets/AiCrmNotes-nNTe_mOY.css 10.62 kB │ gzip: 1.93 kB\n../public/vue-assets/assets/deal-view-DJzG7PLp.css 11.70 kB │ gzip: 3.29 kB\n../public/vue-assets/assets/tokens-DZgWrfKd.css 12.44 kB │ gzip: 2.82 kB\n../public/vue-assets/assets/tokens-DL2BPbai.css 12.89 kB │ gzip: 2.87 kB\n../public/vue-assets/assets/playlists-Cy3t3kYU.css 13.54 kB │ gzip: 3.16 kB\n../public/vue-assets/assets/PhoneField-D_kGOah0.css 13.75 kB │ gzip: 3.44 kB\n../public/vue-assets/assets/ListView-Dt0qkyuY.css 17.07 kB │ gzip: 4.11 kB\n../public/vue-assets/assets/intl-tel-input-DgmgTINs.css 17.11 kB │ gzip: 5.37 kB\n../public/vue-assets/assets/AppFormField-DvpfRzi8.css 18.98 kB │ gzip: 4.42 kB\n../public/vue-assets/assets/OrgSettingsLayout-BogdXtgY.css 21.98 kB │ gzip: 5.51 kB\n../public/vue-assets/assets/dashboard-DQw3TKyk.css 22.74 kB │ gzip: 4.35 kB\n../public/vue-assets/assets/deal-insights-CfX3UNzh.css 29.66 kB │ gzip: 6.44 kB\n../public/vue-assets/assets/team-insights-CjVhm0JN.css 42.31 kB │ gzip: 8.46 kB\n../public/vue-assets/assets/playback-CvJP5tX1.css 44.55 kB │ gzip: 10.36 kB\n../public/vue-assets/assets/video-js-skin-DYZluGb-.css 47.73 kB │ gzip: 12.63 kB\n../public/vue-assets/assets/assets-CAbfI4CY.css 83.38 kB │ gzip: 51.71 kB\n../public/vue-assets/assets/logged-in-layout-1mAqkmnS.css 153.67 kB │ gzip: 27.18 kB\n../public/vue-assets/assets/assets-xH6dx_9q.css 259.71 kB │ gzip: 181.71 kB\n../public/vue-assets/assets/directives-DJJeJJOP.js 0.53 kB │ gzip: 0.36 kB │ map: 0.32 kB\n../public/vue-assets/assets/jenesius-vue-modal-BuBhyl83.js 0.54 kB │ gzip: 0.37 kB │ map: 0.47 kB\n../public/vue-assets/assets/spark-D_-Wgfar.js 0.54 kB │ gzip: 0.35 kB │ map: 0.40 kB\n../public/vue-assets/assets/url-messenger-_CGQa-lH.js 0.56 kB │ gzip: 0.37 kB │ map: 0.46 kB\n../public/vue-assets/assets/wavy-bg-Cec-_GDj.js 0.57 kB │ gzip: 0.36 kB │ map: 0.35 kB\n../public/vue-assets/assets/component-css-class-CtO0AVgW.js 0.61 kB │ gzip: 0.40 kB │ map: 0.91 kB\n../public/vue-assets/assets/theme-CrWye7yo.js 0.63 kB │ gzip: 0.41 kB │ map: 0.49 kB\n../public/vue-assets/assets/pick-D-kTQpNV.js 0.71 kB │ gzip: 0.45 kB │ map: 1.59 kB\n../public/vue-assets/assets/utils-BjsEoLDB.js 0.77 kB │ gzip: 0.50 kB │ map: 1.68 kB\n../public/vue-assets/assets/throttle-CEV1oVAM.js 0.78 kB │ gzip: 0.49 kB │ map: 3.24 kB\n../public/vue-assets/assets/lastFilters-D3xNTqAt.js 0.80 kB │ gzip: 0.51 kB │ map: 1.12 kB\n../public/vue-assets/assets/useAuthState-Bpx8WpBc.js 0.81 kB │ gzip: 0.50 kB │ map: 1.64 kB\n../public/vue-assets/assets/v-focus-618yf9WO.js 0.85 kB │ gzip: 0.52 kB │ map: 1.42 kB\n../public/vue-assets/assets/ListLoader-bfQmkVGu.js 0.87 kB │ gzip: 0.54 kB │ map: 2.90 kB\n../public/vue-assets/assets/utils-CAMifZzY.js 0.88 kB │ gzip: 0.53 kB │ map: 2.45 kB\n../public/vue-assets/assets/mobileApp-QFLS3kId.js 0.89 kB │ gzip: 0.54 kB │ map: 1.12 kB\n../public/vue-assets/assets/pickBy-DftTc1_7.js 0.95 kB │ gzip: 0.59 kB │ map: 2.57 kB\n../public/vue-assets/assets/BuildInfo-CIGre86L.js 1.00 kB │ gzip: 0.65 kB │ map: 1.48 kB\n../public/vue-assets/assets/LogoShort100-D_SQdBYB.js 1.01 kB │ gzip: 0.64 kB │ map: 1.23 kB\n../public/vue-assets/assets/useDrawerModal-CmhXI-pZ.js 1.04 kB │ gzip: 0.65 kB │ map: 1.94 kB\n../public/vue-assets/assets/_getAllKeysIn-CkMQAnJa.js 1.12 kB │ gzip: 0.67 kB │ map: 4.83 kB\n../public/vue-assets/assets/useAutosizeTextarea-DyMwh_20.js 1.13 kB │ gzip: 0.70 kB │ map: 3.16 kB\n../public/vue-assets/assets/planhat-DPA36Hcn.js 1.21 kB │ gzip: 0.71 kB │ map: 2.18 kB\n../public/vue-assets/assets/useStoreModule-Cx6UoGVJ.js 1.35 kB │ gzip: 0.72 kB │ map: 6.03 kB\n../public/vue-assets/assets/GenericMessage-BxGsrfvw.js 1.39 kB │ gzip: 0.75 kB │ map: 1.90 kB\n../public/vue-assets/assets/BrowserExtensionInstaller-DimyBPIi.js 1.45 kB │ gzip: 0.78 kB │ map: 2.05 kB\n../public/vue-assets/assets/debounce-CK0K0ozg.js 1.53 kB │ gzip: 0.83 kB │ map: 8.61 kB\n../public/vue-assets/assets/extension-installed-DLwqdmK8.js 1.60 kB │ gzip: 0.91 kB │ map: 3.20 kB\n../public/vue-assets/assets/RecipientsCell-BS9gDGey.js 1.61 kB │ gzip: 0.93 kB │ map: 4.04 kB\n../public/vue-assets/assets/PrimaryButton-DaKR3UAG.js 1.61 kB │ gzip: 0.87 kB │ map: 3.73 kB\n../public/vue-assets/assets/KioskBanner-D8zd6tGl.js 1.66 kB │ gzip: 1.00 kB │ map: 2.88 kB\n../public/vue-assets/assets/LogoLong100-Hid0kvjR.js 1.78 kB │ gzip: 1.08 kB │ map: 5.91 kB\n../public/vue-assets/assets/GoogleLikeButton-CGFy3nbz.js 1.80 kB │ gzip: 0.84 kB │ map: 4.71 kB\n../public/vue-assets/assets/AppAlert-DFp0nftl.js 1.86 kB │ gzip: 1.01 kB │ map: 6.51 kB\n../public/vue-assets/assets/usePusherEventListener-D8AEMpQD.js 1.93 kB │ gzip: 1.08 kB │ map: 9.59 kB\n../public/vue-assets/assets/AppForm-BgtH5xsM.js 2.01 kB │ gzip: 1.10 kB │ map: 6.89 kB\n../public/vue-assets/assets/vee-validate-rules-IFeGZHb3.js 2.02 kB │ gzip: 0.89 kB │ map: 22.31 kB\n../public/vue-assets/assets/literals-CXSwYC8y.js 2.05 kB │ gzip: 0.97 kB │ map: 3.08 kB\n../public/vue-assets/assets/TabEmptyState-pk8vRxJt.js 2.16 kB │ gzip: 1.12 kB │ map: 8.26 kB\n../public/vue-assets/assets/Replies-DZPkx2YQ.js 2.23 kB │ gzip: 1.14 kB │ map: 2.40 kB\n../public/vue-assets/assets/settings-DHjh0S4O.js 2.34 kB │ gzip: 1.24 kB │ map: 1.73 kB\n../public/vue-assets/assets/SeekBtn-CMp8sSUA.js 2.47 kB │ gzip: 1.18 kB │ map: 9.27 kB\n../public/vue-assets/assets/AiAutomation-CTc8r0pe.js 2.59 kB │ gzip: 1.37 kB │ map: 4.45 kB\n../public/vue-assets/assets/locked-CHodD5F8.js 2.59 kB │ gzip: 1.43 kB │ map: 4.00 kB\n../public/vue-assets/assets/DrawerWidget-BPC-tCgo.js 2.68 kB │ gzip: 1.33 kB │ map: 6.95 kB\n../public/vue-assets/assets/vue-infinite-scroll-D0cI4gH6.js 2.72 kB │ gzip: 1.28 kB │ map: 9.99 kB\n../public/vue-assets/assets/useActivityCustomerName-CYuaZj-p.js 2.87 kB │ gzip: 1.41 kB │ map: 6.27 kB\n../public/vue-assets/assets/other-CS9P7JeF.js 2.97 kB │ gzip: 1.50 kB │ map: 3.38 kB\n../public/vue-assets/assets/InputDropdown-CBM8xiPT.js 3.33 kB │ gzip: 1.61 kB │ map: 6.02 kB\n../public/vue-assets/assets/AvatarsStack-hxex9Ic8.js 3.40 kB │ gzip: 1.51 kB │ map: 9.54 kB\n../public/vue-assets/assets/activity-preview-Dkxw0lkq.js 3.45 kB │ gzip: 1.68 kB │ map: 10.11 kB\n../public/vue-assets/assets/FollowModal-SMYvVwqG.js 3.70 kB │ gzip: 1.81 kB │ map: 8.86 kB\n../public/vue-assets/assets/softphone-coach-BdRuUFYY.js 3.74 kB │ gzip: 1.96 kB │ map: 5.03 kB\n../public/vue-assets/assets/store-Cploop1A.js 4.10 kB │ gzip: 2.00 kB │ map: 18.73 kB\n../public/vue-assets/assets/AiContext-Cz-Y-wOJ.js 4.34 kB │ gzip: 2.08 kB │ map: 13.11 kB\n../public/vue-assets/assets/meeting-consent-DZKBS9eW.js 4.43 kB │ gzip: 2.05 kB │ map: 9.72 kB\n../public/vue-assets/assets/vue-multiselect-VB2Agtp1.js 4.49 kB │ gzip: 1.93 kB │ map: 14.85 kB\n../public/vue-assets/assets/snackbarNotifications-DjDIGkWd.js 4.83 kB │ gzip: 1.58 kB │ map: 11.62 kB\n../public/vue-assets/assets/invitation-DUWFWQvr.js 4.88 kB │ gzip: 2.21 kB │ map: 19.38 kB\n../public/vue-assets/assets/textarea-caret-B2HdIdpZ.js 5.25 kB │ gzip: 2.41 kB │ map: 14.48 kB\n../public/vue-assets/assets/connect-LR_dB_VC.js 5.25 kB │ gzip: 2.30 kB │ map: 10.52 kB\n../public/vue-assets/assets/snackbar-Bq-YQ7pz.js 5.48 kB │ gzip: 2.37 kB │ map: 20.43 kB\n../public/vue-assets/assets/RadioField-2SeSZB7D.js 5.65 kB │ gzip: 2.22 kB │ map: 15.82 kB\n../public/vue-assets/assets/filters-D_eXZdIL.js 5.76 kB │ gzip: 2.17 kB │ map: 16.36 kB\n../public/vue-assets/assets/useActivityHelper-C6KkT8e1.js 5.84 kB │ gzip: 2.51 kB │ map: 14.26 kB\n../public/vue-assets/assets/vue-scrollto-ul3pFdrb.js 5.97 kB │ gzip: 2.72 kB │ map: 23.98 kB\n../public/vue-assets/assets/join-conference-D5dWNU9i.js 6.25 kB │ gzip: 2.82 kB │ map: 24.66 kB\n../public/vue-assets/assets/pluralize-BeKVJaE0.js 6.34 kB │ gzip: 2.70 kB │ map: 18.28 kB\n../public/vue-assets/assets/login-DGWP8lD_.js 7.02 kB │ gzip: 3.16 kB │ map: 17.51 kB\n../public/vue-assets/assets/AppLinks-Bg20Hslb.js 7.14 kB │ gzip: 2.80 kB │ map: 14.87 kB\n../public/vue-assets/assets/InputField-CkdrqKBp.js 7.20 kB │ gzip: 2.58 kB │ map: 19.39 kB\n../public/vue-assets/assets/UserAvatar-UKkSAS4R.js 7.93 kB │ gzip: 3.32 kB │ map: 34.91 kB\n../public/vue-assets/assets/InputText-zafDvh_H.js 7.99 kB │ gzip: 3.05 kB │ map: 20.64 kB\n../public/vue-assets/assets/AiCrmNotes-Co9u0t34.js 8.27 kB │ gzip: 3.32 kB │ map: 34.03 kB\n../public/vue-assets/assets/vue-mq-CmpUzQtD.js 8.84 kB │ gzip: 3.54 kB │ map: 23.69 kB\n../public/vue-assets/assets/pro-duotone-svg-icons-BSBZV3-t.js 9.51 kB │ gzip: 3.43 kB │ map: 3,146.68 kB\n../public/vue-assets/assets/activity-preview-result-C820ZfzV.js 10.65 kB │ gzip: 3.98 kB │ map: 38.51 kB\n../public/vue-assets/assets/pro-regular-svg-icons-CWpSBR20.js 13.10 kB │ gzip: 4.64 kB │ map: 3,178.15 kB\n../public/vue-assets/assets/add-to-playlist-modal-CfDB_Ohb.js 14.99 kB │ gzip: 5.98 kB │ map: 51.34 kB\n../public/vue-assets/assets/ai-reports-BhbjZb-1.js 15.17 kB │ gzip: 6.04 kB │ map: 51.85 kB\n../public/vue-assets/assets/export-portal-BM3w9d6h.js 15.68 kB │ gzip: 6.22 kB │ map: 52.78 kB\n../public/vue-assets/assets/Comment-Kj7lCx0-.js 15.92 kB │ gzip: 5.72 kB │ map: 39.96 kB\n../public/vue-assets/assets/useragent-DQ4F_O30.js 18.02 kB │ gzip: 8.06 kB │ map: 68.31 kB\n../public/vue-assets/assets/linkify-B_q6MgwI.js 18.58 kB │ gzip: 10.37 kB │ map: 88.73 kB\n../public/vue-assets/assets/ai-reports-manage-ulzm9gk2.js 18.92 kB │ gzip: 6.76 kB │ map: 66.14 kB\n../public/vue-assets/assets/dist-C2hisF0L.js 19.32 kB │ gzip: 7.87 kB │ map: 390.77 kB\n../public/vue-assets/assets/pro-solid-svg-icons-DBOkpln3.js 19.46 kB │ gzip: 6.72 kB │ map: 2,692.14 kB\n../public/vue-assets/assets/purify.es-BxOw6oE6.js 21.49 kB │ gzip: 8.71 kB │ map: 83.43 kB\n../public/vue-assets/assets/ActionItems-C9INej-u.js 21.92 kB │ gzip: 8.54 kB │ map: 76.05 kB\n../public/vue-assets/assets/mobile-C-6FGhqy.js 23.34 kB │ gzip: 9.71 kB │ map: 50.90 kB\n../public/vue-assets/assets/basic-modal-BdXsnFwC.js 23.87 kB │ gzip: 9.06 kB │ map: 64.15 kB\n../public/vue-assets/assets/GridView-mZhlfeUe.js 26.60 kB │ gzip: 10.05 kB │ map: 92.74 kB\n../public/vue-assets/assets/ondemand-mq9zQOVK.js 26.88 kB │ gzip: 9.39 kB │ map: 73.94 kB\n../public/vue-assets/assets/CrmLink-DKYsnHnx.js 27.91 kB │ gzip: 10.18 kB │ map: 93.18 kB\n../public/vue-assets/assets/liquor-tree-COUefof4.js 30.75 kB │ gzip: 9.58 kB │ map: 78.74 kB\n../public/vue-assets/assets/DealRiskList-BUgW-21G.js 34.39 kB │ gzip: 10.62 kB │ map: 115.18 kB\n../public/vue-assets/assets/AskAnything-CVCNDjsB.js 39.50 kB │ gzip: 14.97 kB │ map: 173.20 kB\n../public/vue-assets/assets/lib-CwM9toD2.js 39.69 kB │ gzip: 12.70 kB │ map: 138.34 kB\n../public/vue-assets/assets/AppFormField-Cu4TbCsZ.js 41.91 kB │ gzip: 12.69 kB │ map: 150.73 kB\n../public/vue-assets/assets/deal-view-CoZZkbGt.js 43.22 kB │ gzip: 14.34 kB │ map: 150.62 kB\n../public/vue-assets/assets/exports-D1lmea4O.js 47.84 kB │ gzip: 16.46 kB │ map: 294.48 kB\n../public/vue-assets/assets/playlists-BpFWtNz1.js 48.28 kB │ gzip: 15.08 kB │ map: 153.25 kB\n../public/vue-assets/assets/callScoringTemplates-zeRn40uL.js 55.13 kB │ gzip: 13.28 kB │ map: 65.85 kB\n../public/vue-assets/assets/_copyObject-USkOnlaQ.js 61.28 kB │ gzip: 20.09 kB │ map: 239.59 kB\n../public/vue-assets/assets/pusher-znYCfz7U.js 62.98 kB │ gzip: 18.88 kB │ map: 219.27 kB\n../public/vue-assets/assets/onboard-D27tiAEp.js 63.11 kB │ gzip: 21.85 kB │ map: 201.38 kB\n../public/vue-assets/assets/StatusBadge-Dmn6sJ5_.js 64.66 kB │ gzip: 22.96 kB │ map: 244.72 kB\n../public/vue-assets/assets/kiosk-DO4wa1Ld.js 79.60 kB │ gzip: 22.65 kB │ map: 300.68 kB\n../public/vue-assets/assets/preload-helper-DCvhahzG.js 82.59 kB │ gzip: 27.46 kB │ map: 3,452.35 kB\n../public/vue-assets/assets/deal-insights-BKxR7Ag9.js 94.84 kB │ gzip: 28.17 kB │ map: 292.79 kB\n../public/vue-assets/assets/ListView-V1cngeeb.js 115.71 kB │ gzip: 33.78 kB │ map: 308.10 kB\n../public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js 117.59 kB │ gzip: 38.71 kB │ map: 500.60 kB\n../public/vue-assets/assets/WelcomeLayout-B6wd32HG.js 120.67 kB │ gzip: 34.16 kB │ map: 258.56 kB\n../public/vue-assets/assets/dashboard-D0nLYfJh.js 128.71 kB │ gzip: 40.05 kB │ map: 410.48 kB\n../public/vue-assets/assets/emoji-input-CSq87OVy.js 129.28 kB │ gzip: 36.72 kB │ map: 266.15 kB\n../public/vue-assets/assets/AppButton-D3qMdODr.js 133.44 kB │ gzip: 43.03 kB │ map: 516.67 kB\n../public/vue-assets/assets/sentry-zRF85Jni.js 164.28 kB │ gzip: 52.24 kB │ map: 831.82 kB\n../public/vue-assets/assets/OrgSettingsLayout-DXmpAhyV.js 176.33 kB │ gzip: 56.15 kB │ map: 623.43 kB\n../public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js 180.40 kB │ gzip: 67.85 kB │ map: 836.88 kB\n../public/vue-assets/assets/playback-BerPTevk.js 198.79 kB │ gzip: 61.85 kB │ map: 684.87 kB\n../public/vue-assets/assets/index.module-Bjlhgfd1.js 218.14 kB │ gzip: 64.16 kB │ map: 1,108.20 kB\n../public/vue-assets/assets/intl-tel-input-BW4mv40Q.js 264.94 kB │ gzip: 60.31 kB │ map: 475.61 kB\n../public/vue-assets/assets/team-insights-Rfo09TvE.js 298.57 kB │ gzip: 77.21 kB │ map: 959.96 kB\n../public/vue-assets/assets/popper-CQwVcrX4.js 307.13 kB │ gzip: 103.86 kB │ map: 1,245.28 kB\n../public/vue-assets/assets/PhoneField-CwCIoAYm.js 343.99 kB │ gzip: 84.90 kB │ map: 849.05 kB\n../public/vue-assets/assets/live-BkQge7FT.js 367.43 kB │ gzip: 97.05 kB │ map: 792.41 kB\n../public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BNO485xV.js 689.63 kB │ gzip: 202.81 kB │ map: 3,016.64 kB\n../public/vue-assets/assets/index-RakUX3TF.js 825.23 kB │ gzip: 72.54 kB │ map: 436.62 kB\n../public/vue-assets/assets/logged-in-layout-WZXlxzkM.js 1,402.70 kB │ gzip: 438.07 kB │ map: 6,283.55 kB\n\n[plugin builtin:vite-reporter] \n(!) Some chunks are larger than 500 kB after minification. Consider:\n- Using dynamic import() to code-split the application\n- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting\n- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.\n✓ built in 25.43s\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yarn build\n[dotenv@17.3.1] injecting env (0) from .env -- tip: ⚡️\u0000 secrets for agents: https://dotenvx.com/as2\nvite v8.0.0 building client environment for production...\n✓ 4656 modules transformed.\n[sentry-vite-plugin] Warning: No auth token provided. Will not create release. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\n[sentry-vite-plugin] Warning: No auth token provided. Will not upload source maps. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\ncomputing gzip size...\n../public/vue-assets/index.html 4.58 kB │ gzip: 1.16 kB\n../public/vue-assets/assets/job-adder-B2LcgC4o.png 5.41 kB\n../public/vue-assets/assets/dixa-DzT89tKh.png 7.87 kB\n../public/vue-assets/assets/planhat-CQycOTMW.svg 8.61 kB │ gzip: 3.48 kB\n../public/vue-assets/assets/funding-circle-9iGnDGsz.png 9.43 kB\n../public/vue-assets/assets/cision-uOdx_YiN.svg 14.41 kB │ gzip: 6.32 kB\n../public/vue-assets/assets/les-mills-DWabpmYc.png 15.73 kB\n../public/vue-assets/assets/superside-btT37L9L.png 26.69 kB\n../public/vue-assets/assets/flags-a2kmUSbF.webp 28.18 kB\n../public/vue-assets/assets/quinyx-RGQNHx6o.png 63.17 kB\n../public/vue-assets/assets/flags@2x-gR6KPp3x.webp 66.44 kB\n../public/vue-assets/assets/bg-marketing-CAIHIJMj.png 70.66 kB\n../public/vue-assets/.vite/manifest.json 85.73 kB │ gzip: 8.32 kB\n../public/vue-assets/assets/jiminny-score-DtTbKZaD.svg 131.93 kB │ gzip: 86.07 kB\n../public/vue-assets/assets/wavy-bg-vnCaCm9m.webm 670.97 kB\n../public/vue-assets/assets/wavy-bg-Op6dRIjR.mp4 14,484.63 kB\n../public/vue-assets/assets/AppForm-EEGPL5K8.css 0.04 kB │ gzip: 0.06 kB\n../public/vue-assets/assets/RecipientsCell-DNOSS8k1.css 0.12 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/softphone-coach-Cy_9nuQZ.css 0.13 kB │ gzip: 0.10 kB\n../public/vue-assets/assets/locked-CHS8VPur.css 0.15 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/mobile-KPnGhnTp.css 0.16 kB │ gzip: 0.14 kB\n../public/vue-assets/assets/BuildInfo-CxNQv_OV.css 0.20 kB │ gzip: 0.17 kB\n../public/vue-assets/assets/AvatarsStack-C0Nvfh1V.css 0.28 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/LogoLong100-ByQTvUY3.css 0.32 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/connect--xBDzLbc.css 0.34 kB │ gzip: 0.21 kB\n../public/vue-assets/assets/KioskBanner-xU3FRd8g.css 0.41 kB │ gzip: 0.27 kB\n../public/vue-assets/assets/extension-installed-BPBhG13P.css 0.41 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/FollowModal-A0rJmIOK.css 0.45 kB │ gzip: 0.23 kB\n../public/vue-assets/assets/GenericMessage-DutAlObK.css 0.49 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/textarea-caret-DCiNUER_.css 0.51 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/AppLinks-BznfGcBR.css 0.58 kB │ gzip: 0.33 kB\n../public/vue-assets/assets/SeekBtn-_VMPIk6v.css 0.63 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/emoji-input-kJWRbWf8.css 0.68 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/activity-preview-BDxP2S0-.css 0.70 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/ListLoader-DQqAw-8s.css 0.73 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/filters-Bgb6cQD6.css 0.75 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/AiContext-Bg-zjjbA.css 0.99 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/vue3-daterange-picker-izPOBj3P.css 1.02 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/PrimaryButton-CJj2FzKS.css 1.08 kB │ gzip: 0.34 kB\n../public/vue-assets/assets/other-BTuPXtu_.css 1.15 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/jenesius-vue-modal-DDcfejHO.css 1.17 kB │ gzip: 0.48 kB\n../public/vue-assets/assets/TabEmptyState-DOlIO68k.css 1.21 kB │ gzip: 0.53 kB\n../public/vue-assets/assets/AppAlert-BlSJKiqj.css 1.29 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/useActivityCustomerName-CBjfIUfJ.css 1.42 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/UserAvatar-co8l9mMk.css 1.51 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/basic-modal-Dx7ZBbcA.css 1.55 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/login-DYbcBesM.css 1.58 kB │ gzip: 0.61 kB\n../public/vue-assets/assets/DrawerWidget-poAmqspk.css 1.67 kB │ gzip: 0.69 kB\n../public/vue-assets/assets/ActionItems-5FkZSo3G.css 1.79 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/join-conference-DQ6Yl7eU.css 1.82 kB │ gzip: 0.75 kB\n../public/vue-assets/assets/InputText-C-DxDNSV.css 1.95 kB │ gzip: 0.71 kB\n../public/vue-assets/assets/GoogleLikeButton-DvQHxbqR.css 2.11 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/usePusherEventListener-CDj8aSNp.css 2.20 kB │ gzip: 0.73 kB\n../public/vue-assets/assets/onboard-IzGiQ-EC.css 2.32 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/ai-reports-manage-DaynTl62.css 2.34 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/StatusBadge-B4N12dzU.css 2.35 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/GridView-CsP1Jk-k.css 2.36 kB │ gzip: 0.90 kB\n../public/vue-assets/assets/WelcomeLayout-6AX86p6F.css 2.38 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/snackbar-CS_iqvrv.css 2.45 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/ai-reports-mtqGqPPq.css 2.47 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/Comment-gmAfiuer.css 2.63 kB │ gzip: 1.00 kB\n../public/vue-assets/assets/meeting-consent-BasRvxE3.css 2.66 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/DealRiskList-CwxmDnSC.css 2.67 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/add-to-playlist-modal-C_ad1hht.css 2.84 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/live-_zT5qBc1.css 3.60 kB │ gzip: 1.16 kB\n../public/vue-assets/assets/RadioField-DDp8Y71G.css 3.64 kB │ gzip: 1.20 kB\n../public/vue-assets/assets/vue-multiselect-BkQNV7oL.css 3.80 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/activity-preview-result-Db1Da81T.css 3.89 kB │ gzip: 1.15 kB\n../public/vue-assets/assets/liquor-tree-CB5oh8v1.css 4.08 kB │ gzip: 1.24 kB\n../public/vue-assets/assets/sentry-CSjHcvmn.css 4.47 kB │ gzip: 1.08 kB\n../public/vue-assets/assets/AiAutomation-Cj1t8v5y.css 4.61 kB │ gzip: 0.84 kB\n../public/vue-assets/assets/export-portal-CyIUVTEe.css 5.34 kB │ gzip: 1.65 kB\n../public/vue-assets/assets/InputField-C4hoKJ7Z.css 6.13 kB │ gzip: 1.27 kB\n../public/vue-assets/assets/vue-mq-bh4L87Tr.css 6.64 kB │ gzip: 1.95 kB\n../public/vue-assets/assets/invitation-EPR1t-bH.css 6.74 kB │ gzip: 2.22 kB\n../public/vue-assets/assets/ondemand-C2m0YVGA.css 6.84 kB │ gzip: 2.02 kB\n../public/vue-assets/assets/AskAnything-CyvEhHS1.css 8.60 kB │ gzip: 2.60 kB\n../public/vue-assets/assets/AppButton-BIeoar_U.css 8.88 kB │ gzip: 2.04 kB\n../public/vue-assets/assets/kiosk-BcheyWWL.css 9.56 kB │ gzip: 2.53 kB\n../public/vue-assets/assets/AiCrmNotes-nNTe_mOY.css 10.62 kB │ gzip: 1.93 kB\n../public/vue-assets/assets/deal-view-DJzG7PLp.css 11.70 kB │ gzip: 3.29 kB\n../public/vue-assets/assets/tokens-DZgWrfKd.css 12.44 kB │ gzip: 2.82 kB\n../public/vue-assets/assets/tokens-DL2BPbai.css 12.89 kB │ gzip: 2.87 kB\n../public/vue-assets/assets/playlists-Cy3t3kYU.css 13.54 kB │ gzip: 3.16 kB\n../public/vue-assets/assets/PhoneField-D_kGOah0.css 13.75 kB │ gzip: 3.44 kB\n../public/vue-assets/assets/ListView-Dt0qkyuY.css 17.07 kB │ gzip: 4.11 kB\n../public/vue-assets/assets/intl-tel-input-DgmgTINs.css 17.11 kB │ gzip: 5.37 kB\n../public/vue-assets/assets/AppFormField-DvpfRzi8.css 18.98 kB │ gzip: 4.42 kB\n../public/vue-assets/assets/OrgSettingsLayout-BogdXtgY.css 21.98 kB │ gzip: 5.51 kB\n../public/vue-assets/assets/dashboard-DQw3TKyk.css 22.74 kB │ gzip: 4.35 kB\n../public/vue-assets/assets/deal-insights-CfX3UNzh.css 29.66 kB │ gzip: 6.44 kB\n../public/vue-assets/assets/team-insights-CjVhm0JN.css 42.31 kB │ gzip: 8.46 kB\n../public/vue-assets/assets/playback-CvJP5tX1.css 44.55 kB │ gzip: 10.36 kB\n../public/vue-assets/assets/video-js-skin-DYZluGb-.css 47.73 kB │ gzip: 12.63 kB\n../public/vue-assets/assets/assets-CAbfI4CY.css 83.38 kB │ gzip: 51.71 kB\n../public/vue-assets/assets/logged-in-layout-1mAqkmnS.css 153.67 kB │ gzip: 27.18 kB\n../public/vue-assets/assets/assets-xH6dx_9q.css 259.71 kB │ gzip: 181.71 kB\n../public/vue-assets/assets/directives-DJJeJJOP.js 0.53 kB │ gzip: 0.36 kB │ map: 0.32 kB\n../public/vue-assets/assets/jenesius-vue-modal-BuBhyl83.js 0.54 kB │ gzip: 0.37 kB │ map: 0.47 kB\n../public/vue-assets/assets/spark-D_-Wgfar.js 0.54 kB │ gzip: 0.35 kB │ map: 0.40 kB\n../public/vue-assets/assets/url-messenger-_CGQa-lH.js 0.56 kB │ gzip: 0.37 kB │ map: 0.46 kB\n../public/vue-assets/assets/wavy-bg-Cec-_GDj.js 0.57 kB │ gzip: 0.36 kB │ map: 0.35 kB\n../public/vue-assets/assets/component-css-class-CtO0AVgW.js 0.61 kB │ gzip: 0.40 kB │ map: 0.91 kB\n../public/vue-assets/assets/theme-Cy-WIInU.js 0.63 kB │ gzip: 0.42 kB │ map: 0.49 kB\n../public/vue-assets/assets/pick-BGRRe-Qj.js 0.71 kB │ gzip: 0.45 kB │ map: 1.59 kB\n../public/vue-assets/assets/utils-BjsEoLDB.js 0.77 kB │ gzip: 0.50 kB │ map: 1.68 kB\n../public/vue-assets/assets/throttle-DmtOZ8h1.js 0.78 kB │ gzip: 0.49 kB │ map: 3.24 kB\n../public/vue-assets/assets/lastFilters-CzSBGyMt.js 0.80 kB │ gzip: 0.51 kB │ map: 1.12 kB\n../public/vue-assets/assets/useAuthState-Bpx8WpBc.js 0.81 kB │ gzip: 0.50 kB │ map: 1.64 kB\n../public/vue-assets/assets/v-focus-618yf9WO.js 0.85 kB │ gzip: 0.52 kB │ map: 1.42 kB\n../public/vue-assets/assets/ListLoader-bfQmkVGu.js 0.87 kB │ gzip: 0.54 kB │ map: 2.90 kB\n../public/vue-assets/assets/utils-CAMifZzY.js 0.88 kB │ gzip: 0.53 kB │ map: 2.45 kB\n../public/vue-assets/assets/mobileApp-QFLS3kId.js 0.89 kB │ gzip: 0.54 kB │ map: 1.12 kB\n../public/vue-assets/assets/pickBy-BtwCuAYd.js 0.95 kB │ gzip: 0.59 kB │ map: 2.57 kB\n../public/vue-assets/assets/BuildInfo-CIGre86L.js 1.00 kB │ gzip: 0.65 kB │ map: 1.48 kB\n../public/vue-assets/assets/LogoShort100-D_SQdBYB.js 1.01 kB │ gzip: 0.64 kB │ map: 1.23 kB\n../public/vue-assets/assets/useDrawerModal-CmhXI-pZ.js 1.04 kB │ gzip: 0.65 kB │ map: 1.94 kB\n../public/vue-assets/assets/_getAllKeysIn-CkMQAnJa.js 1.12 kB │ gzip: 0.67 kB │ map: 4.83 kB\n../public/vue-assets/assets/useAutosizeTextarea-DyMwh_20.js 1.13 kB │ gzip: 0.70 kB │ map: 3.16 kB\n../public/vue-assets/assets/planhat-DPA36Hcn.js 1.21 kB │ gzip: 0.71 kB │ map: 2.18 kB\n../public/vue-assets/assets/useStoreModule-Cx6UoGVJ.js 1.35 kB │ gzip: 0.72 kB │ map: 6.03 kB\n../public/vue-assets/assets/GenericMessage-BxGsrfvw.js 1.39 kB │ gzip: 0.75 kB │ map: 1.90 kB\n../public/vue-assets/assets/BrowserExtensionInstaller-DimyBPIi.js 1.45 kB │ gzip: 0.78 kB │ map: 2.05 kB\n../public/vue-assets/assets/debounce-DG3xxJjJ.js 1.53 kB │ gzip: 0.83 kB │ map: 8.61 kB\n../public/vue-assets/assets/extension-installed-mV_4t574.js 1.60 kB │ gzip: 0.91 kB │ map: 3.20 kB\n../public/vue-assets/assets/RecipientsCell-BS9gDGey.js 1.61 kB │ gzip: 0.93 kB │ map: 4.04 kB\n../public/vue-assets/assets/PrimaryButton-DaKR3UAG.js 1.61 kB │ gzip: 0.87 kB │ map: 3.73 kB\n../public/vue-assets/assets/KioskBanner-g60shJd4.js 1.66 kB │ gzip: 1.00 kB │ map: 2.88 kB\n../public/vue-assets/assets/LogoLong100-Hid0kvjR.js 1.78 kB │ gzip: 1.08 kB │ map: 5.91 kB\n../public/vue-assets/assets/GoogleLikeButton-CGFy3nbz.js 1.80 kB │ gzip: 0.84 kB │ map: 4.71 kB\n../public/vue-assets/assets/AppAlert-DFp0nftl.js 1.86 kB │ gzip: 1.01 kB │ map: 6.51 kB\n../public/vue-assets/assets/usePusherEventListener-Z27Z5klt.js 1.93 kB │ gzip: 1.08 kB │ map: 9.59 kB\n../public/vue-assets/assets/AppForm-BgtH5xsM.js 2.01 kB │ gzip: 1.10 kB │ map: 6.89 kB\n../public/vue-assets/assets/vee-validate-rules-IFeGZHb3.js 2.02 kB │ gzip: 0.89 kB │ map: 22.31 kB\n../public/vue-assets/assets/literals-CXSwYC8y.js 2.05 kB │ gzip: 0.97 kB │ map: 3.08 kB\n../public/vue-assets/assets/TabEmptyState-pk8vRxJt.js 2.16 kB │ gzip: 1.12 kB │ map: 8.26 kB\n../public/vue-assets/assets/Replies-Cn_qGwMd.js 2.23 kB │ gzip: 1.14 kB │ map: 2.40 kB\n../public/vue-assets/assets/settings-BSSIkavG.js 2.34 kB │ gzip: 1.24 kB │ map: 1.73 kB\n../public/vue-assets/assets/SeekBtn-CMp8sSUA.js 2.47 kB │ gzip: 1.18 kB │ map: 9.27 kB\n../public/vue-assets/assets/AiAutomation-C5_UlocO.js 2.59 kB │ gzip: 1.37 kB │ map: 4.45 kB\n../public/vue-assets/assets/locked-Bad3U1k5.js 2.59 kB │ gzip: 1.43 kB │ map: 4.00 kB\n../public/vue-assets/assets/DrawerWidget-BPC-tCgo.js 2.68 kB │ gzip: 1.33 kB │ map: 6.95 kB\n../public/vue-assets/assets/vue-infinite-scroll-D0cI4gH6.js 2.72 kB │ gzip: 1.28 kB │ map: 9.99 kB\n../public/vue-assets/assets/useActivityCustomerName-CYuaZj-p.js 2.87 kB │ gzip: 1.41 kB │ map: 6.27 kB\n../public/vue-assets/assets/other-D11UHzKP.js 2.97 kB │ gzip: 1.51 kB │ map: 3.38 kB\n../public/vue-assets/assets/InputDropdown-CBM8xiPT.js 3.33 kB │ gzip: 1.61 kB │ map: 6.02 kB\n../public/vue-assets/assets/AvatarsStack-hxex9Ic8.js 3.40 kB │ gzip: 1.51 kB │ map: 9.54 kB\n../public/vue-assets/assets/activity-preview-CoaCdIkx.js 3.45 kB │ gzip: 1.68 kB │ map: 10.11 kB\n../public/vue-assets/assets/FollowModal-SMYvVwqG.js 3.70 kB │ gzip: 1.81 kB │ map: 8.86 kB\n../public/vue-assets/assets/softphone-coach-C452yEnj.js 3.74 kB │ gzip: 1.96 kB │ map: 5.03 kB\n../public/vue-assets/assets/store-CloJVGCY.js 4.10 kB │ gzip: 2.00 kB │ map: 18.73 kB\n../public/vue-assets/assets/AiContext-DJ4M3rJ1.js 4.34 kB │ gzip: 2.08 kB │ map: 13.11 kB\n../public/vue-assets/assets/meeting-consent-B24mqvl1.js 4.43 kB │ gzip: 2.05 kB │ map: 9.72 kB\n../public/vue-assets/assets/vue-multiselect-VB2Agtp1.js 4.49 kB │ gzip: 1.93 kB │ map: 14.85 kB\n../public/vue-assets/assets/snackbarNotifications-DjDIGkWd.js 4.83 kB │ gzip: 1.58 kB │ map: 11.62 kB\n../public/vue-assets/assets/invitation-B5F0aLwl.js 4.88 kB │ gzip: 2.21 kB │ map: 19.38 kB\n../public/vue-assets/assets/textarea-caret-B2HdIdpZ.js 5.25 kB │ gzip: 2.41 kB │ map: 14.48 kB\n../public/vue-assets/assets/connect-DvCp38Q2.js 5.31 kB │ gzip: 2.31 kB │ map: 10.62 kB\n../public/vue-assets/assets/snackbar-Bq-YQ7pz.js 5.48 kB │ gzip: 2.37 kB │ map: 20.43 kB\n../public/vue-assets/assets/RadioField-2SeSZB7D.js 5.65 kB │ gzip: 2.22 kB │ map: 15.82 kB\n../public/vue-assets/assets/filters-D_eXZdIL.js 5.76 kB │ gzip: 2.17 kB │ map: 16.36 kB\n../public/vue-assets/assets/useActivityHelper-BvWVN0nz.js 5.84 kB │ gzip: 2.51 kB │ map: 14.26 kB\n../public/vue-assets/assets/vue-scrollto-ul3pFdrb.js 5.97 kB │ gzip: 2.72 kB │ map: 23.98 kB\n../public/vue-assets/assets/join-conference-CLdXMCwD.js 6.25 kB │ gzip: 2.83 kB │ map: 24.66 kB\n../public/vue-assets/assets/pluralize-BeKVJaE0.js 6.34 kB │ gzip: 2.70 kB │ map: 18.28 kB\n../public/vue-assets/assets/login-PrWVOMsr.js 7.02 kB │ gzip: 3.17 kB │ map: 17.51 kB\n../public/vue-assets/assets/AppLinks-Bg20Hslb.js 7.14 kB │ gzip: 2.80 kB │ map: 14.87 kB\n../public/vue-assets/assets/InputField-CkdrqKBp.js 7.20 kB │ gzip: 2.58 kB │ map: 19.39 kB\n../public/vue-assets/assets/UserAvatar-UKkSAS4R.js 7.93 kB │ gzip: 3.32 kB │ map: 34.91 kB\n../public/vue-assets/assets/InputText-BH9WFuby.js 7.99 kB │ gzip: 3.05 kB │ map: 20.64 kB\n../public/vue-assets/assets/AiCrmNotes-DmPJHAz_.js 8.27 kB │ gzip: 3.32 kB │ map: 34.03 kB\n../public/vue-assets/assets/vue-mq-CmpUzQtD.js 8.84 kB │ gzip: 3.54 kB │ map: 23.69 kB\n../public/vue-assets/assets/pro-duotone-svg-icons-BSBZV3-t.js 9.51 kB │ gzip: 3.43 kB │ map: 3,146.68 kB\n../public/vue-assets/assets/activity-preview-result-CQou30yT.js 10.65 kB │ gzip: 3.98 kB │ map: 38.51 kB\n../public/vue-assets/assets/pro-regular-svg-icons-CWpSBR20.js 13.10 kB │ gzip: 4.64 kB │ map: 3,178.15 kB\n../public/vue-assets/assets/add-to-playlist-modal-BS8w3ovp.js 14.99 kB │ gzip: 5.98 kB │ map: 51.34 kB\n../public/vue-assets/assets/ai-reports-Cy_xmRg8.js 15.17 kB │ gzip: 6.04 kB │ map: 51.85 kB\n../public/vue-assets/assets/export-portal-CZ2b-FlS.js 15.68 kB │ gzip: 6.22 kB │ map: 52.78 kB\n../public/vue-assets/assets/Comment-CdvjTszI.js 15.92 kB │ gzip: 5.72 kB │ map: 39.96 kB\n../public/vue-assets/assets/useragent-DQ4F_O30.js 18.02 kB │ gzip: 8.06 kB │ map: 68.31 kB\n../public/vue-assets/assets/linkify-B_q6MgwI.js 18.58 kB │ gzip: 10.37 kB │ map: 88.73 kB\n../public/vue-assets/assets/ai-reports-manage-B1v5HdqS.js 18.92 kB │ gzip: 6.76 kB │ map: 66.14 kB\n../public/vue-assets/assets/dist-C2hisF0L.js 19.32 kB │ gzip: 7.87 kB │ map: 390.77 kB\n../public/vue-assets/assets/pro-solid-svg-icons-DBOkpln3.js 19.46 kB │ gzip: 6.72 kB │ map: 2,692.14 kB\n../public/vue-assets/assets/purify.es-BxOw6oE6.js 21.49 kB │ gzip: 8.71 kB │ map: 83.43 kB\n../public/vue-assets/assets/ActionItems-uKCSfiLo.js 21.92 kB │ gzip: 8.55 kB │ map: 76.05 kB\n../public/vue-assets/assets/mobile-zCtNIOGN.js 23.34 kB │ gzip: 9.71 kB │ map: 50.90 kB\n../public/vue-assets/assets/basic-modal-BdXsnFwC.js 23.87 kB │ gzip: 9.06 kB │ map: 64.15 kB\n../public/vue-assets/assets/GridView-D5_z7YdA.js 26.60 kB │ gzip: 10.05 kB │ map: 92.74 kB\n../public/vue-assets/assets/ondemand-mxFxJiHI.js 26.88 kB │ gzip: 9.39 kB │ map: 73.94 kB\n../public/vue-assets/assets/CrmLink-DKYsnHnx.js 27.91 kB │ gzip: 10.18 kB │ map: 93.18 kB\n../public/vue-assets/assets/liquor-tree-COUefof4.js 30.75 kB │ gzip: 9.58 kB │ map: 78.74 kB\n../public/vue-assets/assets/DealRiskList-D7gUVql5.js 34.39 kB │ gzip: 10.62 kB │ map: 115.18 kB\n../public/vue-assets/assets/AskAnything-DgI0NfRA.js 39.50 kB │ gzip: 14.97 kB │ map: 173.20 kB\n../public/vue-assets/assets/lib-CwM9toD2.js 39.69 kB │ gzip: 12.70 kB │ map: 138.34 kB\n../public/vue-assets/assets/AppFormField-BqQiLGFF.js 41.91 kB │ gzip: 12.69 kB │ map: 150.73 kB\n../public/vue-assets/assets/deal-view-D4YlPwr_.js 43.22 kB │ gzip: 14.35 kB │ map: 150.62 kB\n../public/vue-assets/assets/exports-D1lmea4O.js 47.84 kB │ gzip: 16.46 kB │ map: 294.48 kB\n../public/vue-assets/assets/playlists-BWAdERcJ.js 48.28 kB │ gzip: 15.07 kB │ map: 153.25 kB\n../public/vue-assets/assets/callScoringTemplates-zeRn40uL.js 55.13 kB │ gzip: 13.28 kB │ map: 65.85 kB\n../public/vue-assets/assets/_copyObject-USkOnlaQ.js 61.28 kB │ gzip: 20.09 kB │ map: 239.59 kB\n../public/vue-assets/assets/pusher-znYCfz7U.js 62.98 kB │ gzip: 18.88 kB │ map: 219.27 kB\n../public/vue-assets/assets/onboard-CyAPGoFk.js 63.11 kB │ gzip: 21.85 kB │ map: 201.38 kB\n../public/vue-assets/assets/StatusBadge-DNHiCr2i.js 64.66 kB │ gzip: 22.96 kB │ map: 244.72 kB\n../public/vue-assets/assets/kiosk-dfcpodo5.js 79.60 kB │ gzip: 22.65 kB │ map: 300.68 kB\n../public/vue-assets/assets/preload-helper-DCvhahzG.js 82.59 kB │ gzip: 27.46 kB │ map: 3,452.35 kB\n../public/vue-assets/assets/deal-insights-BVnPilVP.js 94.84 kB │ gzip: 28.17 kB │ map: 292.79 kB\n../public/vue-assets/assets/ListView-DJD6SV4A.js 115.71 kB │ gzip: 33.79 kB │ map: 308.10 kB\n../public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js 117.59 kB │ gzip: 38.71 kB │ map: 500.60 kB\n../public/vue-assets/assets/WelcomeLayout-B6wd32HG.js 120.67 kB │ gzip: 34.16 kB │ map: 258.56 kB\n../public/vue-assets/assets/dashboard-CsDOiLAi.js 128.71 kB │ gzip: 40.05 kB │ map: 410.48 kB\n../public/vue-assets/assets/emoji-input-CSq87OVy.js 129.28 kB │ gzip: 36.72 kB │ map: 266.15 kB\n../public/vue-assets/assets/AppButton-D3qMdODr.js 133.44 kB │ gzip: 43.03 kB │ map: 516.67 kB\n../public/vue-assets/assets/sentry-B3B1ZM6O.js 164.28 kB │ gzip: 52.24 kB │ map: 831.82 kB\n../public/vue-assets/assets/OrgSettingsLayout-DatDldIe.js 176.33 kB │ gzip: 56.15 kB │ map: 623.43 kB\n../public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js 180.40 kB │ gzip: 67.85 kB │ map: 836.88 kB\n../public/vue-assets/assets/playback-D1gm80qQ.js 198.79 kB │ gzip: 61.85 kB │ map: 684.87 kB\n../public/vue-assets/assets/index.module-Bjlhgfd1.js 218.14 kB │ gzip: 64.16 kB │ map: 1,108.20 kB\n../public/vue-assets/assets/intl-tel-input-BW4mv40Q.js 264.94 kB │ gzip: 60.31 kB │ map: 475.61 kB\n../public/vue-assets/assets/team-insights-DRugjYCA.js 298.57 kB │ gzip: 77.22 kB │ map: 959.96 kB\n../public/vue-assets/assets/popper-CQwVcrX4.js 307.13 kB │ gzip: 103.86 kB │ map: 1,245.28 kB\n../public/vue-assets/assets/PhoneField-CwCIoAYm.js 343.99 kB │ gzip: 84.90 kB │ map: 849.05 kB\n../public/vue-assets/assets/live-CxSmZv7h.js 367.43 kB │ gzip: 97.05 kB │ map: 792.41 kB\n../public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BNO485xV.js 689.63 kB │ gzip: 202.81 kB │ map: 3,016.64 kB\n../public/vue-assets/assets/index-Cp0YOK4U.js 825.23 kB │ gzip: 72.54 kB │ map: 436.62 kB\n../public/vue-assets/assets/logged-in-layout-CE9ox17M.js 1,402.70 kB │ gzip: 438.07 kB │ map: 6,283.55 kB\n\n✓ built in 26.01s\n[plugin builtin:vite-reporter] \n(!) Some chunks are larger than 500 kB after minification. Consider:\n- Using dynamic import() to code-split the application\n- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting\n- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $","is_focused":true},{"role":"AXRadioButton","text":"DOCKER","depth":2,"bounds":{"left":0.00069444446,"top":0.06,"width":0.12291667,"height":0.026666667},"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.0048611113,"top":0.064444445,"width":0.011111111,"height":0.017777778},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"DEV (docker)","depth":2,"bounds":{"left":0.12361111,"top":0.06,"width":0.12291667,"height":0.026666667},"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.12777779,"top":0.064444445,"width":0.011111111,"height":0.017777778},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"APP (-zsh)","depth":2,"bounds":{"left":0.24652778,"top":0.06,"width":0.12291667,"height":0.026666667},"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.25069445,"top":0.064444445,"width":0.011111111,"height":0.017777778},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.36944443,"top":0.06,"width":0.12291667,"height":0.026666667},"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.37361112,"top":0.064444445,"width":0.011111111,"height":0.017777778},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.4923611,"top":0.06,"width":0.12291667,"height":0.026666667},"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.4965278,"top":0.064444445,"width":0.011111111,"height":0.017777778},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"✳ Review screenpipe usage and Boosteroid integration (claude)","depth":2,"bounds":{"left":0.61527777,"top":0.06,"width":0.12291667,"height":0.026666667},"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.61944443,"top":0.064444445,"width":0.011111111,"height":0.017777778},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"ec2-user@ip-10-30-159-186:~ (nc)","depth":2,"bounds":{"left":0.73819447,"top":0.06,"width":0.12291667,"height":0.026666667},"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.7423611,"top":0.064444445,"width":0.011111111,"height":0.017777778},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"ec2-user@ip-10-20-6-111:~ (nc)","depth":2,"bounds":{"left":0.8611111,"top":0.06,"width":0.12291667,"height":0.026666667},"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.86527777,"top":0.064444445,"width":0.011111111,"height":0.017777778},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"⌥⌘1","depth":1,"bounds":{"left":0.95555556,"top":0.033333335,"width":0.03888889,"height":0.018888889},"automation_id":"_NS:8","role_description":"text"},{"role":"AXStaticText","text":"APP (-zsh)","depth":1,"bounds":{"left":0.475,"top":0.034444444,"width":0.05138889,"height":0.017777778},"role_description":"text"}]...
|
-2407191479157132203
|
3429803486563842285
|
idle
|
accessibility
|
NULL
|
info Visit [URL_WITH_CREDENTIALS] ~/jiminny/app/fr info Visit [URL_WITH_CREDENTIALS] ~/jiminny/app/front-end (JY-20692-fix-integration-app-[API_KEY]) $ yanrn build
zsh: command not found: yanrn
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-[API_KEY]) $ yarn build
[dotenv@17.3.1] injecting env (0) from .env -- tip: ⚡️ secrets for agents: [URL_WITH_CREDENTIALS] ~/jiminny/app/front-end (JY-20692-fix-integration-app-[API_KEY]) $ yarn install
➤ YN0000: · Yarn 4.12.0
➤ YN0000: ┌ Resolution step
➤ YN0000: └ Completed
➤ YN0000: ┌ Post-resolution validation
➤ YN0002: │ root-workspace-0b6124@workspace:. doesn't provide @testing-library/dom (pea6ced), requested by @testing-library/user-event.
➤ YN0002: │ root-workspace-0b6124@workspace:. doesn't provide noty (p5bdb0d), requested by vue-components.
➤ YN0086: │ Some peer dependencies are incorrectly met by your project; run yarn explain peer-requirements <hash> for details, where <hash> is the six-letter p-prefixed code.
➤ YN0086: │ Some peer dependencies are incorrectly met by dependencies; run yarn explain peer-requirements for details.
➤ YN0000: └ Completed
➤ YN0000: ┌ Fetch step
➤ YN0000: └ Completed in 1s 655ms
➤ YN0000: ┌ Link step
➤ YN0008: │ root-workspace-0b6124@workspace:. must be rebuilt because its dependency tree changed
➤ YN0000: └ Completed in 0s 969ms
➤ YN0000: · Done with warnings in 3s 14ms
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-[API_KEY]) $ yarn build
[dotenv@17.3.1] injecting env (0) from .env -- tip: ⚙️ override existing env vars with { override: true }
vite v8.0.0 building client environment for production...
✓ 4656 modules transformed.
[sentry-vite-plugin] Warning: No auth token provided. Will not create release. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/
[sentry-vite-plugin] Warning: No auth token provided. Will not upload source maps. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/
computing gzip size...
../public/vue-assets/index.html 4.58 kB │ gzip: 1.15 kB
../public/vue-assets/assets/job-adder-B2LcgC4o.png 5.41 kB
../public/vue-assets/assets/dixa-DzT89tKh.png 7.87 kB
../public/vue-assets/assets/planhat-CQycOTMW.svg 8.61 kB │ gzip: 3.48 kB
../public/vue-assets/assets/funding-circle-9iGnDGsz.png 9.43 kB
../public/vue-assets/assets/cision-uOdx_YiN.svg 14.41 kB │ gzip: 6.32 kB
../public/vue-assets/assets/les-mills-DWabpmYc.png 15.73 kB
../public/vue-assets/assets/superside-btT37L9L.png 26.69 kB
../public/vue-assets/assets/flags-a2kmUSbF.webp 28.18 kB
../public/vue-assets/assets/quinyx-RGQNHx6o.png 63.17 kB
../public/vue-assets/assets/[EMAIL] 66.44 kB
../public/vue-assets/assets/bg-marketing-CAIHIJMj.png 70.66 kB
../public/vue-assets/.vite/manifest.json 85.73 kB │ gzip: 8.32 kB
../public/vue-assets/assets/jiminny-score-DtTbKZaD.svg 131.93 kB │ gzip: 86.07 kB
../public/vue-assets/assets/wavy-bg-vnCaCm9m.webm 670.97 kB
../public/vue-assets/assets/wavy-bg-Op6dRIjR.mp4 14,484.63 kB
../public/vue-assets/assets/AppForm-EEGPL5K8.css 0.04 kB │ gzip: 0.06 kB
../public/vue-assets/assets/RecipientsCell-DNOSS8k1.css 0.12 kB │ gzip: 0.12 kB
../public/vue-assets/assets/softphone-coach-Cy_9nuQZ.css 0.13 kB │ gzip: 0.10 kB
../public/vue-assets/assets/locked-CHS8VPur.css 0.15 kB │ gzip: 0.12 kB
../public/vue-assets/assets/mobile-KPnGhnTp.css 0.16 kB │ gzip: 0.14 kB
../public/vue-assets/assets/BuildInfo-CxNQv_OV.css 0.20 kB │ gzip: 0.17 kB
../public/vue-assets/assets/AvatarsStack-C0Nvfh1V.css 0.28 kB │ gzip: 0.20 kB
../public/vue-assets/assets/LogoLong100-ByQTvUY3.css 0.32 kB │ gzip: 0.20 kB
../public/vue-assets/assets/connect--xBDzLbc.css 0.34 kB │ gzip: 0.21 kB
../public/vue-assets/assets/KioskBanner-xU3FRd8g.css 0.41 kB │ gzip: 0.27 kB
../public/vue-assets/assets/extension-installed-BPBhG13P.css 0.41 kB │ gzip: 0.26 kB
../public/vue-assets/assets/FollowModal-A0rJmIOK.css 0.45 kB │ gzip: 0.23 kB
../public/vue-assets/assets/GenericMessage-DutAlObK.css 0.49 kB │ gzip: 0.26 kB
../public/vue-assets/assets/textarea-caret-DCiNUER_.css 0.51 kB │ gzip: 0.32 kB
../public/vue-assets/assets/AppLinks-BznfGcBR.css 0.58 kB │ gzip: 0.33 kB
../public/vue-assets/assets/SeekBtn-_VMPIk6v.css 0.63 kB │ gzip: 0.32 kB
../public/vue-assets/assets/emoji-input-kJWRbWf8.css 0.68 kB │ gzip: 0.38 kB
../public/vue-assets/assets/activity-preview-BDxP2S0-.css 0.70 kB │ gzip: 0.41 kB
../public/vue-assets/assets/ListLoader-DQqAw-8s.css 0.73 kB │ gzip: 0.38 kB
../public/vue-assets/assets/filters-Bgb6cQD6.css 0.75 kB │ gzip: 0.39 kB
../public/vue-assets/assets/AiContext-Bg-zjjbA.css 0.99 kB │ gzip: 0.45 kB
../public/vue-assets/assets/vue3-daterange-picker-izPOBj3P.css 1.02 kB │ gzip: 0.39 kB
../public/vue-assets/assets/PrimaryButton-CJj2FzKS.css 1.08 kB │ gzip: 0.34 kB
../public/vue-assets/assets/other-BTuPXtu_.css 1.15 kB │ gzip: 0.49 kB
../public/vue-assets/assets/jenesius-vue-modal-DDcfejHO.css 1.17 kB │ gzip: 0.48 kB
../public/vue-assets/assets/TabEmptyState-DOlIO68k.css 1.21 kB │ gzip: 0.53 kB
../public/vue-assets/assets/AppAlert-BlSJKiqj.css 1.29 kB │ gzip: 0.41 kB
../public/vue-assets/assets/useActivityCustomerName-CBjfIUfJ.css 1.42 kB │ gzip: 0.45 kB
../public/vue-assets/assets/UserAvatar-co8l9mMk.css 1.51 kB │ gzip: 0.49 kB
../public/vue-assets/assets/basic-modal-Dx7ZBbcA.css 1.55 kB │ gzip: 0.66 kB
../public/vue-assets/assets/login-DYbcBesM.css 1.58 kB │ gzip: 0.61 kB
../public/vue-assets/assets/DrawerWidget-poAmqspk.css 1.67 kB │ gzip: 0.69 kB
../public/vue-assets/assets/ActionItems-5FkZSo3G.css 1.79 kB │ gzip: 0.80 kB
../public/vue-assets/assets/join-conference-DQ6Yl7eU.css 1.82 kB │ gzip: 0.75 kB
../public/vue-assets/assets/InputText-C-DxDNSV.css 1.95 kB │ gzip: 0.71 kB
../public/vue-assets/assets/GoogleLikeButton-DvQHxbqR.css 2.11 kB │ gzip: 0.66 kB
../public/vue-assets/assets/usePusherEventListener-CDj8aSNp.css 2.20 kB │ gzip: 0.73 kB
../public/vue-assets/assets/onboard-IzGiQ-EC.css 2.32 kB │ gzip: 0.95 kB
../public/vue-assets/assets/ai-reports-manage-DaynTl62.css 2.34 kB │ gzip: 0.86 kB
../public/vue-assets/assets/StatusBadge-B4N12dzU.css 2.35 kB │ gzip: 0.86 kB
../public/vue-assets/assets/GridView-CsP1Jk-k.css 2.36 kB │ gzip: 0.90 kB
../public/vue-assets/assets/WelcomeLayout-6AX86p6F.css 2.38 kB │ gzip: 0.91 kB
../public/vue-assets/assets/snackbar-CS_iqvrv.css 2.45 kB │ gzip: 0.80 kB
../public/vue-assets/assets/ai-reports-mtqGqPPq.css 2.47 kB │ gzip: 0.91 kB
../public/vue-assets/assets/Comment-gmAfiuer.css 2.63 kB │ gzip: 1.00 kB
../public/vue-assets/assets/meeting-consent-BasRvxE3.css 2.66 kB │ gzip: 0.96 kB
../public/vue-assets/assets/DealRiskList-CwxmDnSC.css 2.67 kB │ gzip: 0.96 kB
../public/vue-assets/assets/add-to-playlist-modal-C_ad1hht.css 2.84 kB │ gzip: 0.95 kB
../public/vue-assets/assets/live-_zT5qBc1.css 3.60 kB │ gzip: 1.16 kB
../public/vue-assets/assets/RadioField-DDp8Y71G.css 3.64 kB │ gzip: 1.20 kB
../public/vue-assets/assets/vue-multiselect-BkQNV7oL.css 3.80 kB │ gzip: 0.86 kB
../public/vue-assets/assets/activity-preview-result-Db1Da81T.css 3.89 kB │ gzip: 1.15 kB
../public/vue-assets/assets/liquor-tree-CB5oh8v1.css 4.08 kB │ gzip: 1.24 kB
../public/vue-assets/assets/sentry-CSjHcvmn.css 4.47 kB │ gzip: 1.08 kB
../public/vue-assets/assets/AiAutomation-Cj1t8v5y.css 4.61 kB │ gzip: 0.84 kB
../public/vue-assets/assets/export-portal-CyIUVTEe.css 5.34 kB │ gzip: 1.65 kB
../public/vue-assets/assets/InputField-C4hoKJ7Z.css 6.13 kB │ gzip: 1.27 kB
../public/vue-assets/assets/vue-mq-bh4L87Tr.css 6.64 kB │ gzip: 1.95 kB
../public/vue-assets/assets/invitation-EPR1t-bH.css 6.74 kB │ gzip: 2.22 kB
../public/vue-assets/assets/ondemand-C2m0YVGA.css 6.84 kB │ gzip: 2.02 kB
../public/vue-assets/assets/AskAnything-CyvEhHS1.css 8.60 kB │ gzip: 2.60 kB
../public/vue-assets/assets/AppButton-BIeoar_U.css 8.88 kB │ gzip: 2.04 kB
../public/vue-assets/assets/kiosk-BcheyWWL.css 9.56 kB │ gzip: 2.53 kB
../public/vue-assets/assets/AiCrmNotes-nNTe_mOY.css 10.62 kB │ gzip: 1.93 kB
../public/vue-assets/assets/deal-view-DJzG7PLp.css 11.70 kB │ gzip: 3.29 kB
../public/vue-assets/assets/tokens-DZgWrfKd.css 12.44 kB │ gzip: 2.82 kB
../public/vue-assets/assets/tokens-DL2BPbai.css 12.89 kB │ gzip: 2.87 kB
../public/vue-assets/assets/playlists-Cy3t3kYU.css 13.54 kB │ gzip: 3.16 kB
../public/vue-assets/assets/PhoneField-D_kGOah0.css 13.75 kB │ gzip: 3.44 kB
../public/vue-assets/assets/ListView-Dt0qkyuY.css 17.07 kB │ gzip: 4.11 kB
../public/vue-assets/assets/intl-tel-input-DgmgTINs.css 17.11 kB │ gzip: 5.37 kB
../public/vue-assets/assets/AppFormField-DvpfRzi8.css 18.98 kB │ gzip: 4.42 kB
../public/vue-assets/assets/OrgSettingsLayout-BogdXtgY.css 21.98 kB │ gzip: 5.51 kB
../public/vue-assets/assets/dashboard-DQw3TKyk.css 22.74 kB │ gzip: 4.35 kB
../public/vue-assets/assets/deal-insights-CfX3UNzh.css 29.66 kB │ gzip: 6.44 kB
../public/vue-assets/assets/team-insights-CjVhm0JN.css 42.31 kB │ gzip: 8.46 kB
../public/vue-assets/assets/playback-CvJP5tX1.css 44.55 kB │ gzip: 10.36 kB
../public/vue-assets/assets/video-js-skin-DYZluGb-.css 47.73 kB │ gzip: 12.63 kB
../public/vue-assets/assets/assets-CAbfI4CY.css 83.38 kB │ gzip: 51.71 kB
../public/vue-assets/assets/logged-in-layout-1mAqkmnS.css 153.67 kB │ gzip: 27.18 kB
../public/vue-assets/assets/assets-xH6dx_9q.css 259.71 kB │ gzip: 181.71 kB
../public/vue-assets/assets/directives-DJJeJJOP.js 0.53 kB │ gzip: 0.36 kB │ map: 0.32 kB
../public/vue-assets/assets/jenesius-vue-modal-BuBhyl83.js 0.54 kB │ gzip: 0.37 kB │ map: 0.47 kB
../public/vue-assets/assets/spark-D_-Wgfar.js 0.54 kB │ gzip: 0.35 kB │ map: 0.40 kB
../public/vue-assets/assets/url-messenger-_CGQa-lH.js 0.56 kB │ gzip: 0.37 kB │ map: 0.46 kB
../public/vue-assets/assets/wavy-bg-Cec-_GDj.js 0.57 kB │ gzip: 0.36 kB │ map: 0.35 kB
../public/vue-assets/assets/component-css-class-CtO0AVgW.js 0.61 kB │ gzip: 0.40 kB │ map: 0.91 kB
../public/vue-assets/assets/theme-B5VdyMN_.js 0.63 kB │ gzip: 0.41 kB │ map: 0.49 kB
../public/vue-assets/assets/pick-C8Pw7kHx.js 0.71 kB │ gzip: 0.45 kB │ map: 1.59 kB
../public/vue-assets/assets/utils-BjsEoLDB.js 0.77 kB │ gzip: 0.50 kB │ map: 1.68 kB
../public/vue-assets/assets/throttle-siTOKEMt.js 0.78 kB │ gzip: 0.49 kB │ map: 3.24 kB
../public/vue-assets/assets/lastFilters-B_B_O5LY.js 0.80 kB │ gzip: 0.51 kB │ map: 1.12 kB
../public/vue-assets/assets/useAuthState-Bpx8WpBc.js 0.81 kB │ gz...
|
46615
|
|
46616
|
NULL
|
0
|
2026-04-17T10:42:10.835654+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776422530835_m2.jpg...
|
iTerm2
|
APP (-zsh)
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
info Visit [URL_WITH_CREDENTIALS] ~/jiminny/app/fr info Visit [URL_WITH_CREDENTIALS] ~/jiminny/app/front-end (JY-20692-fix-integration-app-[API_KEY]) $ yanrn build
zsh: command not found: yanrn
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-[API_KEY]) $ yarn build
[dotenv@17.3.1] injecting env (0) from .env -- tip: ⚡️ secrets for agents: [URL_WITH_CREDENTIALS] ~/jiminny/app/front-end (JY-20692-fix-integration-app-[API_KEY]) $ yarn install
➤ YN0000: · Yarn 4.12.0
➤ YN0000: ┌ Resolution step
➤ YN0000: └ Completed
➤ YN0000: ┌ Post-resolution validation
➤ YN0002: │ root-workspace-0b6124@workspace:. doesn't provide @testing-library/dom (pea6ced), requested by @testing-library/user-event.
➤ YN0002: │ root-workspace-0b6124@workspace:. doesn't provide noty (p5bdb0d), requested by vue-components.
➤ YN0086: │ Some peer dependencies are incorrectly met by your project; run yarn explain peer-requirements <hash> for details, where <hash> is the six-letter p-prefixed code.
➤ YN0086: │ Some peer dependencies are incorrectly met by dependencies; run yarn explain peer-requirements for details.
➤ YN0000: └ Completed
➤ YN0000: ┌ Fetch step
➤ YN0000: └ Completed in 1s 655ms
➤ YN0000: ┌ Link step
➤ YN0008: │ root-workspace-0b6124@workspace:. must be rebuilt because its dependency tree changed
➤ YN0000: └ Completed in 0s 969ms
➤ YN0000: · Done with warnings in 3s 14ms
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-[API_KEY]) $ yarn build
[dotenv@17.3.1] injecting env (0) from .env -- tip: ⚙️ override existing env vars with { override: true }
vite v8.0.0 building client environment for production...
✓ 4656 modules transformed.
[sentry-vite-plugin] Warning: No auth token provided. Will not create release. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/
[sentry-vite-plugin] Warning: No auth token provided. Will not upload source maps. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/
computing gzip size...
../public/vue-assets/index.html 4.58 kB │ gzip: 1.15 kB
../public/vue-assets/assets/job-adder-B2LcgC4o.png 5.41 kB
../public/vue-assets/assets/dixa-DzT89tKh.png 7.87 kB
../public/vue-assets/assets/planhat-CQycOTMW.svg 8.61 kB │ gzip: 3.48 kB
../public/vue-assets/assets/funding-circle-9iGnDGsz.png 9.43 kB
../public/vue-assets/assets/cision-uOdx_YiN.svg 14.41 kB │ gzip: 6.32 kB
../public/vue-assets/assets/les-mills-DWabpmYc.png 15.73 kB
../public/vue-assets/assets/superside-btT37L9L.png 26.69 kB
../public/vue-assets/assets/flags-a2kmUSbF.webp 28.18 kB
../public/vue-assets/assets/quinyx-RGQNHx6o.png 63.17 kB
../public/vue-assets/assets/[EMAIL] 66.44 kB
../public/vue-assets/assets/bg-marketing-CAIHIJMj.png 70.66 kB
../public/vue-assets/.vite/manifest.json 85.73 kB │ gzip: 8.32 kB
../public/vue-assets/assets/jiminny-score-DtTbKZaD.svg 131.93 kB │ gzip: 86.07 kB
../public/vue-assets/assets/wavy-bg-vnCaCm9m.webm 670.97 kB
../public/vue-assets/assets/wavy-bg-Op6dRIjR.mp4 14,484.63 kB
../public/vue-assets/assets/AppForm-EEGPL5K8.css 0.04 kB │ gzip: 0.06 kB
../public/vue-assets/assets/RecipientsCell-DNOSS8k1.css 0.12 kB │ gzip: 0.12 kB
../public/vue-assets/assets/softphone-coach-Cy_9nuQZ.css 0.13 kB │ gzip: 0.10 kB
../public/vue-assets/assets/locked-CHS8VPur.css 0.15 kB │ gzip: 0.12 kB
../public/vue-assets/assets/mobile-KPnGhnTp.css 0.16 kB │ gzip: 0.14 kB
../public/vue-assets/assets/BuildInfo-CxNQv_OV.css 0.20 kB │ gzip: 0.17 kB
../public/vue-assets/assets/AvatarsStack-C0Nvfh1V.css 0.28 kB │ gzip: 0.20 kB
../public/vue-assets/assets/LogoLong100-ByQTvUY3.css 0.32 kB │ gzip: 0.20 kB
../public/vue-assets/assets/connect--xBDzLbc.css 0.34 kB │ gzip: 0.21 kB
../public/vue-assets/assets/KioskBanner-xU3FRd8g.css 0.41 kB │ gzip: 0.27 kB
../public/vue-assets/assets/extension-installed-BPBhG13P.css 0.41 kB │ gzip: 0.26 kB
../public/vue-assets/assets/FollowModal-A0rJmIOK.css 0.45 kB │ gzip: 0.23 kB
../public/vue-assets/assets/GenericMessage-DutAlObK.css 0.49 kB │ gzip: 0.26 kB
../public/vue-assets/assets/textarea-caret-DCiNUER_.css 0.51 kB │ gzip: 0.32 kB
../public/vue-assets/assets/AppLinks-BznfGcBR.css 0.58 kB │ gzip: 0.33 kB
../public/vue-assets/assets/SeekBtn-_VMPIk6v.css 0.63 kB │ gzip: 0.32 kB
../public/vue-assets/assets/emoji-input-kJWRbWf8.css 0.68 kB │ gzip: 0.38 kB
../public/vue-assets/assets/activity-preview-BDxP2S0-.css 0.70 kB │ gzip: 0.41 kB
../public/vue-assets/assets/ListLoader-DQqAw-8s.css 0.73 kB │ gzip: 0.38 kB
../public/vue-assets/assets/filters-Bgb6cQD6.css 0.75 kB │ gzip: 0.39 kB
../public/vue-assets/assets/AiContext-Bg-zjjbA.css 0.99 kB │ gzip: 0.45 kB
../public/vue-assets/assets/vue3-daterange-picker-izPOBj3P.css 1.02 kB │ gzip: 0.39 kB
../public/vue-assets/assets/PrimaryButton-CJj2FzKS.css 1.08 kB │ gzip: 0.34 kB
../public/vue-assets/assets/other-BTuPXtu_.css 1.15 kB │ gzip: 0.49 kB
../public/vue-assets/assets/jenesius-vue-modal-DDcfejHO.css 1.17 kB │ gzip: 0.48 kB
../public/vue-assets/assets/TabEmptyState-DOlIO68k.css 1.21 kB │ gzip: 0.53 kB
../public/vue-assets/assets/AppAlert-BlSJKiqj.css 1.29 kB │ gzip: 0.41 kB
../public/vue-assets/assets/useActivityCustomerName-CBjfIUfJ.css 1.42 kB │ gzip: 0.45 kB
../public/vue-assets/assets/UserAvatar-co8l9mMk.css 1.51 kB │ gzip: 0.49 kB
../public/vue-assets/assets/basic-modal-Dx7ZBbcA.css 1.55 kB │ gzip: 0.66 kB
../public/vue-assets/assets/login-DYbcBesM.css 1.58 kB │ gzip: 0.61 kB
../public/vue-assets/assets/DrawerWidget-poAmqspk.css 1.67 kB │ gzip: 0.69 kB
../public/vue-assets/assets/ActionItems-5FkZSo3G.css 1.79 kB │ gzip: 0.80 kB
../public/vue-assets/assets/join-conference-DQ6Yl7eU.css 1.82 kB │ gzip: 0.75 kB
../public/vue-assets/assets/InputText-C-DxDNSV.css 1.95 kB │ gzip: 0.71 kB
../public/vue-assets/assets/GoogleLikeButton-DvQHxbqR.css 2.11 kB │ gzip: 0.66 kB
../public/vue-assets/assets/usePusherEventListener-CDj8aSNp.css 2.20 kB │ gzip: 0.73 kB
../public/vue-assets/assets/onboard-IzGiQ-EC.css 2.32 kB │ gzip: 0.95 kB
../public/vue-assets/assets/ai-reports-manage-DaynTl62.css 2.34 kB │ gzip: 0.86 kB
../public/vue-assets/assets/StatusBadge-B4N12dzU.css 2.35 kB │ gzip: 0.86 kB
../public/vue-assets/assets/GridView-CsP1Jk-k.css 2.36 kB │ gzip: 0.90 kB
../public/vue-assets/assets/WelcomeLayout-6AX86p6F.css 2.38 kB │ gzip: 0.91 kB
../public/vue-assets/assets/snackbar-CS_iqvrv.css 2.45 kB │ gzip: 0.80 kB
../public/vue-assets/assets/ai-reports-mtqGqPPq.css 2.47 kB │ gzip: 0.91 kB
../public/vue-assets/assets/Comment-gmAfiuer.css 2.63 kB │ gzip: 1.00 kB
../public/vue-assets/assets/meeting-consent-BasRvxE3.css 2.66 kB │ gzip: 0.96 kB
../public/vue-assets/assets/DealRiskList-CwxmDnSC.css 2.67 kB │ gzip: 0.96 kB
../public/vue-assets/assets/add-to-playlist-modal-C_ad1hht.css 2.84 kB │ gzip: 0.95 kB
../public/vue-assets/assets/live-_zT5qBc1.css 3.60 kB │ gzip: 1.16 kB
../public/vue-assets/assets/RadioField-DDp8Y71G.css 3.64 kB │ gzip: 1.20 kB
../public/vue-assets/assets/vue-multiselect-BkQNV7oL.css 3.80 kB │ gzip: 0.86 kB
../public/vue-assets/assets/activity-preview-result-Db1Da81T.css 3.89 kB │ gzip: 1.15 kB
../public/vue-assets/assets/liquor-tree-CB5oh8v1.css 4.08 kB │ gzip: 1.24 kB
../public/vue-assets/assets/sentry-CSjHcvmn.css 4.47 kB │ gzip: 1.08 kB
../public/vue-assets/assets/AiAutomation-Cj1t8v5y.css 4.61 kB │ gzip: 0.84 kB
../public/vue-assets/assets/export-portal-CyIUVTEe.css 5.34 kB │ gzip: 1.65 kB
../public/vue-assets/assets/InputField-C4hoKJ7Z.css 6.13 kB │ gzip: 1.27 kB
../public/vue-assets/assets/vue-mq-bh4L87Tr.css 6.64 kB │ gzip: 1.95 kB
../public/vue-assets/assets/invitation-EPR1t-bH.css 6.74 kB │ gzip: 2.22 kB
../public/vue-assets/assets/ondemand-C2m0YVGA.css 6.84 kB │ gzip: 2.02 kB
../public/vue-assets/assets/AskAnything-CyvEhHS1.css 8.60 kB │ gzip: 2.60 kB
../public/vue-assets/assets/AppButton-BIeoar_U.css 8.88 kB │ gzip: 2.04 kB
../public/vue-assets/assets/kiosk-BcheyWWL.css 9.56 kB │ gzip: 2.53 kB
../public/vue-assets/assets/AiCrmNotes-nNTe_mOY.css 10.62 kB │ gzip: 1.93 kB
../public/vue-assets/assets/deal-view-DJzG7PLp.css 11.70 kB │ gzip: 3.29 kB
../public/vue-assets/assets/tokens-DZgWrfKd.css 12.44 kB │ gzip: 2.82 kB
../public/vue-assets/assets/tokens-DL2BPbai.css 12.89 kB │ gzip: 2.87 kB
../public/vue-assets/assets/playlists-Cy3t3kYU.css 13.54 kB │ gzip: 3.16 kB
../public/vue-assets/assets/PhoneField-D_kGOah0.css 13.75 kB │ gzip: 3.44 kB
../public/vue-assets/assets/ListView-Dt0qkyuY.css 17.07 kB │ gzip: 4.11 kB
../public/vue-assets/assets/intl-tel-input-DgmgTINs.css 17.11 kB │ gzip: 5.37 kB
../public/vue-assets/assets/AppFormField-DvpfRzi8.css 18.98 kB │ gzip: 4.42 kB
../public/vue-assets/assets/OrgSettingsLayout-BogdXtgY.css 21.98 kB │ gzip: 5.51 kB
../public/vue-assets/assets/dashboard-DQw3TKyk.css 22.74 kB │ gzip: 4.35 kB
../public/vue-assets/assets/deal-insights-CfX3UNzh.css 29.66 kB │ gzip: 6.44 kB
../public/vue-assets/assets/team-insights-CjVhm0JN.css 42.31 kB │ gzip: 8.46 kB
../public/vue-assets/assets/playback-CvJP5tX1.css 44.55 kB │ gzip: 10.36 kB
../public/vue-assets/assets/video-js-skin-DYZluGb-.css 47.73 kB │ gzip: 12.63 kB
../public/vue-assets/assets/assets-CAbfI4CY.css 83.38 kB │ gzip: 51.71 kB
../public/vue-assets/assets/logged-in-layout-1mAqkmnS.css 153.67 kB │ gzip: 27.18 kB
../public/vue-assets/assets/assets-xH6dx_9q.css 259.71 kB │ gzip: 181.71 kB
../public/vue-assets/assets/directives-DJJeJJOP.js 0.53 kB │ gzip: 0.36 kB │ map: 0.32 kB
../public/vue-assets/assets/jenesius-vue-modal-BuBhyl83.js 0.54 kB │ gzip: 0.37 kB │ map: 0.47 kB
../public/vue-assets/assets/spark-D_-Wgfar.js 0.54 kB │ gzip: 0.35 kB │ map: 0.40 kB
../public/vue-assets/assets/url-messenger-_CGQa-lH.js 0.56 kB │ gzip: 0.37 kB │ map: 0.46 kB
../public/vue-assets/assets/wavy-bg-Cec-_GDj.js 0.57 kB │ gzip: 0.36 kB │ map: 0.35 kB
../public/vue-assets/assets/component-css-class-CtO0AVgW.js 0.61 kB │ gzip: 0.40 kB │ map: 0.91 kB
../public/vue-assets/assets/theme-B5VdyMN_.js 0.63 kB │ gzip: 0.41 kB │ map: 0.49 kB
../public/vue-assets/assets/pick-C8Pw7kHx.js 0.71 kB │ gzip: 0.45 kB │ map: 1.59 kB
../public/vue-assets/assets/utils-BjsEoLDB.js 0.77 kB │ gzip: 0.50 kB │ map: 1.68 kB
../public/vue-assets/assets/throttle-siTOKEMt.js 0.78 kB │ gzip: 0.49 kB │ map: 3.24 kB
../public/vue-assets/assets/lastFilters-B_B_O5LY.js 0.80 kB │ gzip: 0.51 kB │ map: 1.12 kB
../public/vue-assets/assets/useAuthState-Bpx8WpBc.js 0.81 kB │ gz...
|
[{"role":"AXTextArea","text [{"role":"AXTextArea","text":"info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ nvm use 24\nNow using node v24.11.1 (npm v11.6.2)\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yanr build\nzsh: command not found: yanr\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yanrn build\nzsh: command not found: yanrn\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yarn build \n[dotenv@17.3.1] injecting env (0) from .env -- tip: ⚡️\u0000 secrets for agents: https://dotenvx.com/as2\nvite v8.0.0 building client environment for production...\n✓ 4656 modules transformed.\n[sentry-vite-plugin] Warning: No auth token provided. Will not create release. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\n[sentry-vite-plugin] Warning: No auth token provided. Will not upload source maps. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\ncomputing gzip size...\n../public/vue-assets/index.html 4.58 kB │ gzip: 1.15 kB\n../public/vue-assets/assets/job-adder-B2LcgC4o.png 5.41 kB\n../public/vue-assets/assets/dixa-DzT89tKh.png 7.87 kB\n../public/vue-assets/assets/planhat-CQycOTMW.svg 8.61 kB │ gzip: 3.48 kB\n../public/vue-assets/assets/funding-circle-9iGnDGsz.png 9.43 kB\n../public/vue-assets/assets/cision-uOdx_YiN.svg 14.41 kB │ gzip: 6.32 kB\n../public/vue-assets/assets/les-mills-DWabpmYc.png 15.73 kB\n../public/vue-assets/assets/superside-btT37L9L.png 26.69 kB\n../public/vue-assets/assets/flags-a2kmUSbF.webp 28.18 kB\n../public/vue-assets/assets/quinyx-RGQNHx6o.png 63.17 kB\n../public/vue-assets/assets/flags@2x-gR6KPp3x.webp 66.44 kB\n../public/vue-assets/assets/bg-marketing-CAIHIJMj.png 70.66 kB\n../public/vue-assets/.vite/manifest.json 85.73 kB │ gzip: 8.33 kB\n../public/vue-assets/assets/jiminny-score-DtTbKZaD.svg 131.93 kB │ gzip: 86.07 kB\n../public/vue-assets/assets/wavy-bg-vnCaCm9m.webm 670.97 kB\n../public/vue-assets/assets/wavy-bg-Op6dRIjR.mp4 14,484.63 kB\n../public/vue-assets/assets/AppForm-EEGPL5K8.css 0.04 kB │ gzip: 0.06 kB\n../public/vue-assets/assets/RecipientsCell-DNOSS8k1.css 0.12 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/softphone-coach-Cy_9nuQZ.css 0.13 kB │ gzip: 0.10 kB\n../public/vue-assets/assets/locked-CHS8VPur.css 0.15 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/mobile-KPnGhnTp.css 0.16 kB │ gzip: 0.14 kB\n../public/vue-assets/assets/BuildInfo-CxNQv_OV.css 0.20 kB │ gzip: 0.17 kB\n../public/vue-assets/assets/AvatarsStack-C0Nvfh1V.css 0.28 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/LogoLong100-ByQTvUY3.css 0.32 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/connect--xBDzLbc.css 0.34 kB │ gzip: 0.21 kB\n../public/vue-assets/assets/KioskBanner-xU3FRd8g.css 0.41 kB │ gzip: 0.27 kB\n../public/vue-assets/assets/extension-installed-BPBhG13P.css 0.41 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/FollowModal-A0rJmIOK.css 0.45 kB │ gzip: 0.23 kB\n../public/vue-assets/assets/GenericMessage-DutAlObK.css 0.49 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/textarea-caret-DCiNUER_.css 0.51 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/AppLinks-BznfGcBR.css 0.58 kB │ gzip: 0.33 kB\n../public/vue-assets/assets/SeekBtn-_VMPIk6v.css 0.63 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/emoji-input-kJWRbWf8.css 0.68 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/activity-preview-BDxP2S0-.css 0.70 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/ListLoader-DQqAw-8s.css 0.73 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/filters-Bgb6cQD6.css 0.75 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/AiContext-Bg-zjjbA.css 0.99 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/vue3-daterange-picker-izPOBj3P.css 1.02 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/PrimaryButton-CJj2FzKS.css 1.08 kB │ gzip: 0.34 kB\n../public/vue-assets/assets/other-BTuPXtu_.css 1.15 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/jenesius-vue-modal-DDcfejHO.css 1.17 kB │ gzip: 0.48 kB\n../public/vue-assets/assets/TabEmptyState-DOlIO68k.css 1.21 kB │ gzip: 0.53 kB\n../public/vue-assets/assets/AppAlert-BlSJKiqj.css 1.29 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/useActivityCustomerName-CBjfIUfJ.css 1.42 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/UserAvatar-co8l9mMk.css 1.51 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/basic-modal-Dx7ZBbcA.css 1.55 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/login-DYbcBesM.css 1.58 kB │ gzip: 0.61 kB\n../public/vue-assets/assets/DrawerWidget-poAmqspk.css 1.67 kB │ gzip: 0.69 kB\n../public/vue-assets/assets/ActionItems-5FkZSo3G.css 1.79 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/join-conference-DQ6Yl7eU.css 1.82 kB │ gzip: 0.75 kB\n../public/vue-assets/assets/InputText-C-DxDNSV.css 1.95 kB │ gzip: 0.71 kB\n../public/vue-assets/assets/GoogleLikeButton-DvQHxbqR.css 2.11 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/usePusherEventListener-CDj8aSNp.css 2.20 kB │ gzip: 0.73 kB\n../public/vue-assets/assets/onboard-IzGiQ-EC.css 2.32 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/ai-reports-manage-DaynTl62.css 2.34 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/StatusBadge-B4N12dzU.css 2.35 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/GridView-CsP1Jk-k.css 2.36 kB │ gzip: 0.90 kB\n../public/vue-assets/assets/WelcomeLayout-6AX86p6F.css 2.38 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/snackbar-CS_iqvrv.css 2.45 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/ai-reports-mtqGqPPq.css 2.47 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/Comment-gmAfiuer.css 2.63 kB │ gzip: 1.00 kB\n../public/vue-assets/assets/meeting-consent-BasRvxE3.css 2.66 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/DealRiskList-CwxmDnSC.css 2.67 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/add-to-playlist-modal-C_ad1hht.css 2.84 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/live-_zT5qBc1.css 3.60 kB │ gzip: 1.16 kB\n../public/vue-assets/assets/RadioField-DDp8Y71G.css 3.64 kB │ gzip: 1.20 kB\n../public/vue-assets/assets/vue-multiselect-BkQNV7oL.css 3.80 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/activity-preview-result-Db1Da81T.css 3.89 kB │ gzip: 1.15 kB\n../public/vue-assets/assets/liquor-tree-CB5oh8v1.css 4.08 kB │ gzip: 1.24 kB\n../public/vue-assets/assets/sentry-CSjHcvmn.css 4.47 kB │ gzip: 1.08 kB\n../public/vue-assets/assets/AiAutomation-Cj1t8v5y.css 4.61 kB │ gzip: 0.84 kB\n../public/vue-assets/assets/export-portal-CyIUVTEe.css 5.34 kB │ gzip: 1.65 kB\n../public/vue-assets/assets/InputField-C4hoKJ7Z.css 6.13 kB │ gzip: 1.27 kB\n../public/vue-assets/assets/vue-mq-bh4L87Tr.css 6.64 kB │ gzip: 1.95 kB\n../public/vue-assets/assets/invitation-EPR1t-bH.css 6.74 kB │ gzip: 2.22 kB\n../public/vue-assets/assets/ondemand-C2m0YVGA.css 6.84 kB │ gzip: 2.02 kB\n../public/vue-assets/assets/AskAnything-CyvEhHS1.css 8.60 kB │ gzip: 2.60 kB\n../public/vue-assets/assets/AppButton-BIeoar_U.css 8.88 kB │ gzip: 2.04 kB\n../public/vue-assets/assets/kiosk-BcheyWWL.css 9.56 kB │ gzip: 2.53 kB\n../public/vue-assets/assets/AiCrmNotes-nNTe_mOY.css 10.62 kB │ gzip: 1.93 kB\n../public/vue-assets/assets/deal-view-DJzG7PLp.css 11.70 kB │ gzip: 3.29 kB\n../public/vue-assets/assets/tokens-B6_hRKul.css 12.95 kB │ gzip: 2.92 kB\n../public/vue-assets/assets/tokens-CoAAv8do.css 13.41 kB │ gzip: 2.97 kB\n../public/vue-assets/assets/playlists-Cy3t3kYU.css 13.54 kB │ gzip: 3.16 kB\n../public/vue-assets/assets/PhoneField-D_kGOah0.css 13.75 kB │ gzip: 3.44 kB\n../public/vue-assets/assets/ListView-Dt0qkyuY.css 17.07 kB │ gzip: 4.11 kB\n../public/vue-assets/assets/intl-tel-input-DgmgTINs.css 17.11 kB │ gzip: 5.37 kB\n../public/vue-assets/assets/AppFormField-DvpfRzi8.css 18.98 kB │ gzip: 4.42 kB\n../public/vue-assets/assets/OrgSettingsLayout-BogdXtgY.css 21.98 kB │ gzip: 5.51 kB\n../public/vue-assets/assets/dashboard-DQw3TKyk.css 22.74 kB │ gzip: 4.35 kB\n../public/vue-assets/assets/deal-insights-CfX3UNzh.css 29.66 kB │ gzip: 6.44 kB\n../public/vue-assets/assets/team-insights-CjVhm0JN.css 42.31 kB │ gzip: 8.46 kB\n../public/vue-assets/assets/playback-CvJP5tX1.css 44.55 kB │ gzip: 10.36 kB\n../public/vue-assets/assets/video-js-skin-DYZluGb-.css 47.73 kB │ gzip: 12.63 kB\n../public/vue-assets/assets/assets-CAbfI4CY.css 83.38 kB │ gzip: 51.71 kB\n../public/vue-assets/assets/logged-in-layout-1mAqkmnS.css 153.67 kB │ gzip: 27.18 kB\n../public/vue-assets/assets/assets-xH6dx_9q.css 259.71 kB │ gzip: 181.71 kB\n../public/vue-assets/assets/directives-DJJeJJOP.js 0.53 kB │ gzip: 0.36 kB │ map: 0.32 kB\n../public/vue-assets/assets/jenesius-vue-modal-BuBhyl83.js 0.54 kB │ gzip: 0.37 kB │ map: 0.47 kB\n../public/vue-assets/assets/spark-D_-Wgfar.js 0.54 kB │ gzip: 0.35 kB │ map: 0.40 kB\n../public/vue-assets/assets/url-messenger-_CGQa-lH.js 0.56 kB │ gzip: 0.37 kB │ map: 0.46 kB\n../public/vue-assets/assets/wavy-bg-Cec-_GDj.js 0.57 kB │ gzip: 0.36 kB │ map: 0.35 kB\n../public/vue-assets/assets/component-css-class-CtO0AVgW.js 0.61 kB │ gzip: 0.40 kB │ map: 0.91 kB\n../public/vue-assets/assets/theme-CrLnsUSQ.js 0.63 kB │ gzip: 0.41 kB │ map: 0.49 kB\n../public/vue-assets/assets/pick-jGxSsmQW.js 0.71 kB │ gzip: 0.45 kB │ map: 1.59 kB\n../public/vue-assets/assets/utils-BjsEoLDB.js 0.77 kB │ gzip: 0.50 kB │ map: 1.68 kB\n../public/vue-assets/assets/throttle-DE_etUX9.js 0.78 kB │ gzip: 0.49 kB │ map: 3.24 kB\n../public/vue-assets/assets/lastFilters-CjyI5phg.js 0.80 kB │ gzip: 0.51 kB │ map: 1.12 kB\n../public/vue-assets/assets/useAuthState-Bpx8WpBc.js 0.81 kB │ gzip: 0.50 kB │ map: 1.64 kB\n../public/vue-assets/assets/v-focus-618yf9WO.js 0.85 kB │ gzip: 0.52 kB │ map: 1.42 kB\n../public/vue-assets/assets/ListLoader-bfQmkVGu.js 0.87 kB │ gzip: 0.54 kB │ map: 2.90 kB\n../public/vue-assets/assets/utils-CAMifZzY.js 0.88 kB │ gzip: 0.53 kB │ map: 2.45 kB\n../public/vue-assets/assets/mobileApp-QFLS3kId.js 0.89 kB │ gzip: 0.54 kB │ map: 1.12 kB\n../public/vue-assets/assets/pickBy-DV-Svw8z.js 0.95 kB │ gzip: 0.59 kB │ map: 2.57 kB\n../public/vue-assets/assets/BuildInfo-CIGre86L.js 1.00 kB │ gzip: 0.65 kB │ map: 1.48 kB\n../public/vue-assets/assets/LogoShort100-D_SQdBYB.js 1.01 kB │ gzip: 0.64 kB │ map: 1.23 kB\n../public/vue-assets/assets/useDrawerModal-CmhXI-pZ.js 1.04 kB │ gzip: 0.65 kB │ map: 1.94 kB\n../public/vue-assets/assets/_getAllKeysIn-CkMQAnJa.js 1.12 kB │ gzip: 0.67 kB │ map: 4.83 kB\n../public/vue-assets/assets/useAutosizeTextarea-DyMwh_20.js 1.13 kB │ gzip: 0.70 kB │ map: 3.16 kB\n../public/vue-assets/assets/planhat-DPA36Hcn.js 1.21 kB │ gzip: 0.71 kB │ map: 2.18 kB\n../public/vue-assets/assets/useStoreModule-Cx6UoGVJ.js 1.35 kB │ gzip: 0.72 kB │ map: 6.03 kB\n../public/vue-assets/assets/GenericMessage-BxGsrfvw.js 1.39 kB │ gzip: 0.75 kB │ map: 1.90 kB\n../public/vue-assets/assets/BrowserExtensionInstaller-DimyBPIi.js 1.45 kB │ gzip: 0.78 kB │ map: 2.05 kB\n../public/vue-assets/assets/debounce-C6V4-Yml.js 1.53 kB │ gzip: 0.83 kB │ map: 8.61 kB\n../public/vue-assets/assets/extension-installed-C_yEm2e-.js 1.60 kB │ gzip: 0.91 kB │ map: 3.20 kB\n../public/vue-assets/assets/RecipientsCell-BS9gDGey.js 1.61 kB │ gzip: 0.93 kB │ map: 4.04 kB\n../public/vue-assets/assets/PrimaryButton-DaKR3UAG.js 1.61 kB │ gzip: 0.87 kB │ map: 3.73 kB\n../public/vue-assets/assets/KioskBanner-DyDQY_lm.js 1.66 kB │ gzip: 1.00 kB │ map: 2.88 kB\n../public/vue-assets/assets/LogoLong100-Hid0kvjR.js 1.78 kB │ gzip: 1.08 kB │ map: 5.91 kB\n../public/vue-assets/assets/GoogleLikeButton-CGFy3nbz.js 1.80 kB │ gzip: 0.84 kB │ map: 4.71 kB\n../public/vue-assets/assets/AppAlert-DFp0nftl.js 1.86 kB │ gzip: 1.01 kB │ map: 6.51 kB\n../public/vue-assets/assets/usePusherEventListener-DRJ7JhID.js 1.93 kB │ gzip: 1.08 kB │ map: 9.59 kB\n../public/vue-assets/assets/AppForm-BgtH5xsM.js 2.01 kB │ gzip: 1.10 kB │ map: 6.89 kB\n../public/vue-assets/assets/vee-validate-rules-IFeGZHb3.js 2.02 kB │ gzip: 0.89 kB │ map: 22.31 kB\n../public/vue-assets/assets/literals-CXSwYC8y.js 2.05 kB │ gzip: 0.97 kB │ map: 3.08 kB\n../public/vue-assets/assets/TabEmptyState-pk8vRxJt.js 2.16 kB │ gzip: 1.12 kB │ map: 8.26 kB\n../public/vue-assets/assets/Replies-BrhP0P7D.js 2.23 kB │ gzip: 1.14 kB │ map: 2.40 kB\n../public/vue-assets/assets/settings-DWZF-G8N.js 2.34 kB │ gzip: 1.23 kB │ map: 1.73 kB\n../public/vue-assets/assets/SeekBtn-CMp8sSUA.js 2.47 kB │ gzip: 1.18 kB │ map: 9.27 kB\n../public/vue-assets/assets/AiAutomation-CnVa_KZl.js 2.59 kB │ gzip: 1.37 kB │ map: 4.45 kB\n../public/vue-assets/assets/locked-B2ThxTAd.js 2.59 kB │ gzip: 1.42 kB │ map: 4.00 kB\n../public/vue-assets/assets/DrawerWidget-BPC-tCgo.js 2.68 kB │ gzip: 1.33 kB │ map: 6.95 kB\n../public/vue-assets/assets/vue-infinite-scroll-D0cI4gH6.js 2.72 kB │ gzip: 1.28 kB │ map: 9.99 kB\n../public/vue-assets/assets/useActivityCustomerName-CYuaZj-p.js 2.87 kB │ gzip: 1.41 kB │ map: 6.27 kB\n../public/vue-assets/assets/other-k3In5q1l.js 2.97 kB │ gzip: 1.50 kB │ map: 3.38 kB\n../public/vue-assets/assets/InputDropdown-CBM8xiPT.js 3.33 kB │ gzip: 1.61 kB │ map: 6.02 kB\n../public/vue-assets/assets/AvatarsStack-hxex9Ic8.js 3.40 kB │ gzip: 1.51 kB │ map: 9.54 kB\n../public/vue-assets/assets/activity-preview-BZrDAfqV.js 3.45 kB │ gzip: 1.69 kB │ map: 10.11 kB\n../public/vue-assets/assets/FollowModal-SMYvVwqG.js 3.70 kB │ gzip: 1.81 kB │ map: 8.86 kB\n../public/vue-assets/assets/softphone-coach-DJLlDZUr.js 3.74 kB │ gzip: 1.96 kB │ map: 5.03 kB\n../public/vue-assets/assets/store-DdBy-CTd.js 4.10 kB │ gzip: 2.00 kB │ map: 18.73 kB\n../public/vue-assets/assets/AiContext-BYAvUsG9.js 4.34 kB │ gzip: 2.08 kB │ map: 13.11 kB\n../public/vue-assets/assets/meeting-consent-EdnBkMBr.js 4.43 kB │ gzip: 2.04 kB │ map: 9.72 kB\n../public/vue-assets/assets/vue-multiselect-VB2Agtp1.js 4.49 kB │ gzip: 1.93 kB │ map: 14.85 kB\n../public/vue-assets/assets/snackbarNotifications-DjDIGkWd.js 4.83 kB │ gzip: 1.58 kB │ map: 11.62 kB\n../public/vue-assets/assets/invitation-B1SNfWGY.js 4.88 kB │ gzip: 2.21 kB │ map: 19.38 kB\n../public/vue-assets/assets/connect-DzHqvqqR.js 5.17 kB │ gzip: 2.28 kB │ map: 10.35 kB\n../public/vue-assets/assets/textarea-caret-B2HdIdpZ.js 5.25 kB │ gzip: 2.41 kB │ map: 14.48 kB\n../public/vue-assets/assets/snackbar-Bq-YQ7pz.js 5.48 kB │ gzip: 2.37 kB │ map: 20.43 kB\n../public/vue-assets/assets/RadioField-2SeSZB7D.js 5.65 kB │ gzip: 2.22 kB │ map: 15.82 kB\n../public/vue-assets/assets/filters-D_eXZdIL.js 5.76 kB │ gzip: 2.17 kB │ map: 16.36 kB\n../public/vue-assets/assets/useActivityHelper-DbifQcp6.js 5.84 kB │ gzip: 2.51 kB │ map: 14.26 kB\n../public/vue-assets/assets/vue-scrollto-ul3pFdrb.js 5.97 kB │ gzip: 2.72 kB │ map: 23.98 kB\n../public/vue-assets/assets/join-conference-CYJkDTD8.js 6.25 kB │ gzip: 2.82 kB │ map: 24.66 kB\n../public/vue-assets/assets/pluralize-BeKVJaE0.js 6.34 kB │ gzip: 2.70 kB │ map: 18.28 kB\n../public/vue-assets/assets/login-DkwzcZhV.js 7.02 kB │ gzip: 3.16 kB │ map: 17.51 kB\n../public/vue-assets/assets/AppLinks-Bg20Hslb.js 7.14 kB │ gzip: 2.80 kB │ map: 14.87 kB\n../public/vue-assets/assets/InputField-CkdrqKBp.js 7.20 kB │ gzip: 2.58 kB │ map: 19.39 kB\n../public/vue-assets/assets/UserAvatar-UKkSAS4R.js 7.93 kB │ gzip: 3.32 kB │ map: 34.91 kB\n../public/vue-assets/assets/InputText-CiBJ3oHz.js 7.99 kB │ gzip: 3.05 kB │ map: 20.64 kB\n../public/vue-assets/assets/AiCrmNotes-BZWjw9pN.js 8.27 kB │ gzip: 3.32 kB │ map: 34.03 kB\n../public/vue-assets/assets/vue-mq-CmpUzQtD.js 8.84 kB │ gzip: 3.54 kB │ map: 23.69 kB\n../public/vue-assets/assets/pro-duotone-svg-icons-BSBZV3-t.js 9.51 kB │ gzip: 3.43 kB │ map: 3,146.68 kB\n../public/vue-assets/assets/activity-preview-result-DLO6BXq1.js 10.65 kB │ gzip: 3.98 kB │ map: 38.51 kB\n../public/vue-assets/assets/pro-regular-svg-icons-CWpSBR20.js 13.10 kB │ gzip: 4.64 kB │ map: 3,178.15 kB\n../public/vue-assets/assets/add-to-playlist-modal-prxYCVfk.js 14.99 kB │ gzip: 5.98 kB │ map: 51.34 kB\n../public/vue-assets/assets/ai-reports-B3i0pft-.js 15.17 kB │ gzip: 6.03 kB │ map: 51.85 kB\n../public/vue-assets/assets/export-portal-54ZutGpG.js 15.68 kB │ gzip: 6.22 kB │ map: 52.78 kB\n../public/vue-assets/assets/Comment-BpW-5w7g.js 15.92 kB │ gzip: 5.72 kB │ map: 39.96 kB\n../public/vue-assets/assets/useragent-DQ4F_O30.js 18.02 kB │ gzip: 8.06 kB │ map: 68.31 kB\n../public/vue-assets/assets/linkify-B_q6MgwI.js 18.58 kB │ gzip: 10.37 kB │ map: 88.73 kB\n../public/vue-assets/assets/ai-reports-manage-Ber3EVyb.js 18.92 kB │ gzip: 6.75 kB │ map: 66.14 kB\n../public/vue-assets/assets/dist-C2hisF0L.js 19.32 kB │ gzip: 7.87 kB │ map: 390.77 kB\n../public/vue-assets/assets/pro-solid-svg-icons-DBOkpln3.js 19.46 kB │ gzip: 6.72 kB │ map: 2,692.14 kB\n../public/vue-assets/assets/purify.es-BxOw6oE6.js 21.49 kB │ gzip: 8.71 kB │ map: 83.43 kB\n../public/vue-assets/assets/ActionItems-BuNWx0BA.js 21.92 kB │ gzip: 8.54 kB │ map: 76.05 kB\n../public/vue-assets/assets/mobile-BMtylYi6.js 23.34 kB │ gzip: 9.71 kB │ map: 50.90 kB\n../public/vue-assets/assets/basic-modal-BdXsnFwC.js 23.87 kB │ gzip: 9.06 kB │ map: 64.15 kB\n../public/vue-assets/assets/GridView-CP7J7-qo.js 26.60 kB │ gzip: 10.05 kB │ map: 92.74 kB\n../public/vue-assets/assets/ondemand-kg1j3nIA.js 26.88 kB │ gzip: 9.39 kB │ map: 73.94 kB\n../public/vue-assets/assets/CrmLink-DKYsnHnx.js 27.91 kB │ gzip: 10.18 kB │ map: 93.18 kB\n../public/vue-assets/assets/liquor-tree-COUefof4.js 30.75 kB │ gzip: 9.58 kB │ map: 78.74 kB\n../public/vue-assets/assets/DealRiskList-C8q7faXl.js 34.39 kB │ gzip: 10.62 kB │ map: 115.18 kB\n../public/vue-assets/assets/AskAnything-BgSY9SKi.js 39.50 kB │ gzip: 14.97 kB │ map: 173.20 kB\n../public/vue-assets/assets/lib-CwM9toD2.js 39.69 kB │ gzip: 12.70 kB │ map: 138.34 kB\n../public/vue-assets/assets/AppFormField-COtjyq5c.js 41.91 kB │ gzip: 12.69 kB │ map: 150.73 kB\n../public/vue-assets/assets/deal-view-bksUVN5E.js 43.22 kB │ gzip: 14.34 kB │ map: 150.62 kB\n../public/vue-assets/assets/exports-D1lmea4O.js 47.84 kB │ gzip: 16.46 kB │ map: 294.48 kB\n../public/vue-assets/assets/playlists-Yn3gKP2z.js 48.28 kB │ gzip: 15.07 kB │ map: 153.25 kB\n../public/vue-assets/assets/callScoringTemplates-zeRn40uL.js 55.13 kB │ gzip: 13.28 kB │ map: 65.85 kB\n../public/vue-assets/assets/_copyObject-USkOnlaQ.js 61.28 kB │ gzip: 20.09 kB │ map: 239.59 kB\n../public/vue-assets/assets/pusher-znYCfz7U.js 62.98 kB │ gzip: 18.88 kB │ map: 219.27 kB\n../public/vue-assets/assets/onboard-BqxRx4zw.js 63.11 kB │ gzip: 21.85 kB │ map: 201.38 kB\n../public/vue-assets/assets/StatusBadge-C2H2xj9g.js 64.66 kB │ gzip: 22.96 kB │ map: 244.72 kB\n../public/vue-assets/assets/kiosk-BDlz4mpq.js 79.60 kB │ gzip: 22.65 kB │ map: 300.68 kB\n../public/vue-assets/assets/preload-helper-DCvhahzG.js 82.59 kB │ gzip: 27.46 kB │ map: 3,452.35 kB\n../public/vue-assets/assets/deal-insights-BnVEi9yc.js 94.84 kB │ gzip: 28.16 kB │ map: 292.79 kB\n../public/vue-assets/assets/ListView-CN2ZtlC7.js 115.71 kB │ gzip: 33.78 kB │ map: 308.10 kB\n../public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js 117.59 kB │ gzip: 38.71 kB │ map: 500.60 kB\n../public/vue-assets/assets/WelcomeLayout-B6wd32HG.js 120.67 kB │ gzip: 34.16 kB │ map: 258.56 kB\n../public/vue-assets/assets/dashboard-XCNXJG27.js 128.71 kB │ gzip: 40.05 kB │ map: 410.48 kB\n../public/vue-assets/assets/emoji-input-CSq87OVy.js 129.28 kB │ gzip: 36.72 kB │ map: 266.15 kB\n../public/vue-assets/assets/AppButton-D3qMdODr.js 133.44 kB │ gzip: 43.03 kB │ map: 516.67 kB\n../public/vue-assets/assets/sentry-DMzthP2u.js 164.28 kB │ gzip: 52.24 kB │ map: 831.82 kB\n../public/vue-assets/assets/OrgSettingsLayout-DYzOhe6q.js 176.33 kB │ gzip: 56.15 kB │ map: 623.43 kB\n../public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js 180.40 kB │ gzip: 67.85 kB │ map: 836.88 kB\n../public/vue-assets/assets/playback-DRpCBZO9.js 198.79 kB │ gzip: 61.85 kB │ map: 684.87 kB\n../public/vue-assets/assets/index.module-Bjlhgfd1.js 218.14 kB │ gzip: 64.16 kB │ map: 1,108.20 kB\n../public/vue-assets/assets/intl-tel-input-BW4mv40Q.js 264.94 kB │ gzip: 60.31 kB │ map: 475.61 kB\n../public/vue-assets/assets/team-insights-CPpPTAKV.js 298.57 kB │ gzip: 77.22 kB │ map: 959.96 kB\n../public/vue-assets/assets/popper-CQwVcrX4.js 307.13 kB │ gzip: 103.86 kB │ map: 1,245.28 kB\n../public/vue-assets/assets/PhoneField-CwCIoAYm.js 343.99 kB │ gzip: 84.90 kB │ map: 849.05 kB\n../public/vue-assets/assets/live-Cfb59XkF.js 367.43 kB │ gzip: 97.05 kB │ map: 792.41 kB\n../public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BNO485xV.js 689.63 kB │ gzip: 202.81 kB │ map: 3,016.64 kB\n../public/vue-assets/assets/index-1Grk9P2e.js 825.23 kB │ gzip: 72.55 kB │ map: 436.62 kB\n../public/vue-assets/assets/logged-in-layout-DArs1VTa.js 1,402.70 kB │ gzip: 438.08 kB │ map: 6,283.55 kB\n\n[PLUGIN_TIMINGS] Warning: Your build spent significant time in plugins. Here is a breakdown:\n - vite:css (61%)\n - vite-plugin-externals (12%)\n - vite:vue (9%)\n - sentry-vite-plugin (8%)\n - vite:worker (6%)\nSee https://rolldown.rs/options/checks#plugintimings for more details.\n\n[plugin builtin:vite-reporter] \n(!) Some chunks are larger than 500 kB after minification. Consider:\n- Using dynamic import() to code-split the application\n- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting\n- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.\n✓ built in 22.57s\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yarn install\n➤ YN0000: · Yarn 4.12.0\n➤ YN0000: ┌ Resolution step\n➤ YN0000: └ Completed\n➤ YN0000: ┌ Post-resolution validation\n➤ YN0002: │ root-workspace-0b6124@workspace:. doesn't provide @testing-library/dom (pea6ced), requested by @testing-library/user-event.\n➤ YN0002: │ root-workspace-0b6124@workspace:. doesn't provide noty (p5bdb0d), requested by vue-components.\n➤ YN0086: │ Some peer dependencies are incorrectly met by your project; run yarn explain peer-requirements <hash> for details, where <hash> is the six-letter p-prefixed code.\n➤ YN0086: │ Some peer dependencies are incorrectly met by dependencies; run yarn explain peer-requirements for details.\n➤ YN0000: └ Completed\n➤ YN0000: ┌ Fetch step\n➤ YN0000: └ Completed in 1s 655ms\n➤ YN0000: ┌ Link step\n➤ YN0008: │ root-workspace-0b6124@workspace:. must be rebuilt because its dependency tree changed\n➤ YN0000: └ Completed in 0s 969ms\n➤ YN0000: · Done with warnings in 3s 14ms\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yarn build\n[dotenv@17.3.1] injecting env (0) from .env -- tip: ⚙️\u0000 override existing env vars with { override: true }\nvite v8.0.0 building client environment for production...\n✓ 4656 modules transformed.\n[sentry-vite-plugin] Warning: No auth token provided. Will not create release. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\n[sentry-vite-plugin] Warning: No auth token provided. Will not upload source maps. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\ncomputing gzip size...\n../public/vue-assets/index.html 4.58 kB │ gzip: 1.15 kB\n../public/vue-assets/assets/job-adder-B2LcgC4o.png 5.41 kB\n../public/vue-assets/assets/dixa-DzT89tKh.png 7.87 kB\n../public/vue-assets/assets/planhat-CQycOTMW.svg 8.61 kB │ gzip: 3.48 kB\n../public/vue-assets/assets/funding-circle-9iGnDGsz.png 9.43 kB\n../public/vue-assets/assets/cision-uOdx_YiN.svg 14.41 kB │ gzip: 6.32 kB\n../public/vue-assets/assets/les-mills-DWabpmYc.png 15.73 kB\n../public/vue-assets/assets/superside-btT37L9L.png 26.69 kB\n../public/vue-assets/assets/flags-a2kmUSbF.webp 28.18 kB\n../public/vue-assets/assets/quinyx-RGQNHx6o.png 63.17 kB\n../public/vue-assets/assets/flags@2x-gR6KPp3x.webp 66.44 kB\n../public/vue-assets/assets/bg-marketing-CAIHIJMj.png 70.66 kB\n../public/vue-assets/.vite/manifest.json 85.73 kB │ gzip: 8.32 kB\n../public/vue-assets/assets/jiminny-score-DtTbKZaD.svg 131.93 kB │ gzip: 86.07 kB\n../public/vue-assets/assets/wavy-bg-vnCaCm9m.webm 670.97 kB\n../public/vue-assets/assets/wavy-bg-Op6dRIjR.mp4 14,484.63 kB\n../public/vue-assets/assets/AppForm-EEGPL5K8.css 0.04 kB │ gzip: 0.06 kB\n../public/vue-assets/assets/RecipientsCell-DNOSS8k1.css 0.12 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/softphone-coach-Cy_9nuQZ.css 0.13 kB │ gzip: 0.10 kB\n../public/vue-assets/assets/locked-CHS8VPur.css 0.15 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/mobile-KPnGhnTp.css 0.16 kB │ gzip: 0.14 kB\n../public/vue-assets/assets/BuildInfo-CxNQv_OV.css 0.20 kB │ gzip: 0.17 kB\n../public/vue-assets/assets/AvatarsStack-C0Nvfh1V.css 0.28 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/LogoLong100-ByQTvUY3.css 0.32 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/connect--xBDzLbc.css 0.34 kB │ gzip: 0.21 kB\n../public/vue-assets/assets/KioskBanner-xU3FRd8g.css 0.41 kB │ gzip: 0.27 kB\n../public/vue-assets/assets/extension-installed-BPBhG13P.css 0.41 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/FollowModal-A0rJmIOK.css 0.45 kB │ gzip: 0.23 kB\n../public/vue-assets/assets/GenericMessage-DutAlObK.css 0.49 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/textarea-caret-DCiNUER_.css 0.51 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/AppLinks-BznfGcBR.css 0.58 kB │ gzip: 0.33 kB\n../public/vue-assets/assets/SeekBtn-_VMPIk6v.css 0.63 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/emoji-input-kJWRbWf8.css 0.68 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/activity-preview-BDxP2S0-.css 0.70 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/ListLoader-DQqAw-8s.css 0.73 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/filters-Bgb6cQD6.css 0.75 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/AiContext-Bg-zjjbA.css 0.99 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/vue3-daterange-picker-izPOBj3P.css 1.02 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/PrimaryButton-CJj2FzKS.css 1.08 kB │ gzip: 0.34 kB\n../public/vue-assets/assets/other-BTuPXtu_.css 1.15 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/jenesius-vue-modal-DDcfejHO.css 1.17 kB │ gzip: 0.48 kB\n../public/vue-assets/assets/TabEmptyState-DOlIO68k.css 1.21 kB │ gzip: 0.53 kB\n../public/vue-assets/assets/AppAlert-BlSJKiqj.css 1.29 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/useActivityCustomerName-CBjfIUfJ.css 1.42 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/UserAvatar-co8l9mMk.css 1.51 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/basic-modal-Dx7ZBbcA.css 1.55 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/login-DYbcBesM.css 1.58 kB │ gzip: 0.61 kB\n../public/vue-assets/assets/DrawerWidget-poAmqspk.css 1.67 kB │ gzip: 0.69 kB\n../public/vue-assets/assets/ActionItems-5FkZSo3G.css 1.79 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/join-conference-DQ6Yl7eU.css 1.82 kB │ gzip: 0.75 kB\n../public/vue-assets/assets/InputText-C-DxDNSV.css 1.95 kB │ gzip: 0.71 kB\n../public/vue-assets/assets/GoogleLikeButton-DvQHxbqR.css 2.11 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/usePusherEventListener-CDj8aSNp.css 2.20 kB │ gzip: 0.73 kB\n../public/vue-assets/assets/onboard-IzGiQ-EC.css 2.32 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/ai-reports-manage-DaynTl62.css 2.34 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/StatusBadge-B4N12dzU.css 2.35 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/GridView-CsP1Jk-k.css 2.36 kB │ gzip: 0.90 kB\n../public/vue-assets/assets/WelcomeLayout-6AX86p6F.css 2.38 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/snackbar-CS_iqvrv.css 2.45 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/ai-reports-mtqGqPPq.css 2.47 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/Comment-gmAfiuer.css 2.63 kB │ gzip: 1.00 kB\n../public/vue-assets/assets/meeting-consent-BasRvxE3.css 2.66 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/DealRiskList-CwxmDnSC.css 2.67 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/add-to-playlist-modal-C_ad1hht.css 2.84 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/live-_zT5qBc1.css 3.60 kB │ gzip: 1.16 kB\n../public/vue-assets/assets/RadioField-DDp8Y71G.css 3.64 kB │ gzip: 1.20 kB\n../public/vue-assets/assets/vue-multiselect-BkQNV7oL.css 3.80 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/activity-preview-result-Db1Da81T.css 3.89 kB │ gzip: 1.15 kB\n../public/vue-assets/assets/liquor-tree-CB5oh8v1.css 4.08 kB │ gzip: 1.24 kB\n../public/vue-assets/assets/sentry-CSjHcvmn.css 4.47 kB │ gzip: 1.08 kB\n../public/vue-assets/assets/AiAutomation-Cj1t8v5y.css 4.61 kB │ gzip: 0.84 kB\n../public/vue-assets/assets/export-portal-CyIUVTEe.css 5.34 kB │ gzip: 1.65 kB\n../public/vue-assets/assets/InputField-C4hoKJ7Z.css 6.13 kB │ gzip: 1.27 kB\n../public/vue-assets/assets/vue-mq-bh4L87Tr.css 6.64 kB │ gzip: 1.95 kB\n../public/vue-assets/assets/invitation-EPR1t-bH.css 6.74 kB │ gzip: 2.22 kB\n../public/vue-assets/assets/ondemand-C2m0YVGA.css 6.84 kB │ gzip: 2.02 kB\n../public/vue-assets/assets/AskAnything-CyvEhHS1.css 8.60 kB │ gzip: 2.60 kB\n../public/vue-assets/assets/AppButton-BIeoar_U.css 8.88 kB │ gzip: 2.04 kB\n../public/vue-assets/assets/kiosk-BcheyWWL.css 9.56 kB │ gzip: 2.53 kB\n../public/vue-assets/assets/AiCrmNotes-nNTe_mOY.css 10.62 kB │ gzip: 1.93 kB\n../public/vue-assets/assets/deal-view-DJzG7PLp.css 11.70 kB │ gzip: 3.29 kB\n../public/vue-assets/assets/tokens-DZgWrfKd.css 12.44 kB │ gzip: 2.82 kB\n../public/vue-assets/assets/tokens-DL2BPbai.css 12.89 kB │ gzip: 2.87 kB\n../public/vue-assets/assets/playlists-Cy3t3kYU.css 13.54 kB │ gzip: 3.16 kB\n../public/vue-assets/assets/PhoneField-D_kGOah0.css 13.75 kB │ gzip: 3.44 kB\n../public/vue-assets/assets/ListView-Dt0qkyuY.css 17.07 kB │ gzip: 4.11 kB\n../public/vue-assets/assets/intl-tel-input-DgmgTINs.css 17.11 kB │ gzip: 5.37 kB\n../public/vue-assets/assets/AppFormField-DvpfRzi8.css 18.98 kB │ gzip: 4.42 kB\n../public/vue-assets/assets/OrgSettingsLayout-BogdXtgY.css 21.98 kB │ gzip: 5.51 kB\n../public/vue-assets/assets/dashboard-DQw3TKyk.css 22.74 kB │ gzip: 4.35 kB\n../public/vue-assets/assets/deal-insights-CfX3UNzh.css 29.66 kB │ gzip: 6.44 kB\n../public/vue-assets/assets/team-insights-CjVhm0JN.css 42.31 kB │ gzip: 8.46 kB\n../public/vue-assets/assets/playback-CvJP5tX1.css 44.55 kB │ gzip: 10.36 kB\n../public/vue-assets/assets/video-js-skin-DYZluGb-.css 47.73 kB │ gzip: 12.63 kB\n../public/vue-assets/assets/assets-CAbfI4CY.css 83.38 kB │ gzip: 51.71 kB\n../public/vue-assets/assets/logged-in-layout-1mAqkmnS.css 153.67 kB │ gzip: 27.18 kB\n../public/vue-assets/assets/assets-xH6dx_9q.css 259.71 kB │ gzip: 181.71 kB\n../public/vue-assets/assets/directives-DJJeJJOP.js 0.53 kB │ gzip: 0.36 kB │ map: 0.32 kB\n../public/vue-assets/assets/jenesius-vue-modal-BuBhyl83.js 0.54 kB │ gzip: 0.37 kB │ map: 0.47 kB\n../public/vue-assets/assets/spark-D_-Wgfar.js 0.54 kB │ gzip: 0.35 kB │ map: 0.40 kB\n../public/vue-assets/assets/url-messenger-_CGQa-lH.js 0.56 kB │ gzip: 0.37 kB │ map: 0.46 kB\n../public/vue-assets/assets/wavy-bg-Cec-_GDj.js 0.57 kB │ gzip: 0.36 kB │ map: 0.35 kB\n../public/vue-assets/assets/component-css-class-CtO0AVgW.js 0.61 kB │ gzip: 0.40 kB │ map: 0.91 kB\n../public/vue-assets/assets/theme-B5VdyMN_.js 0.63 kB │ gzip: 0.41 kB │ map: 0.49 kB\n../public/vue-assets/assets/pick-C8Pw7kHx.js 0.71 kB │ gzip: 0.45 kB │ map: 1.59 kB\n../public/vue-assets/assets/utils-BjsEoLDB.js 0.77 kB │ gzip: 0.50 kB │ map: 1.68 kB\n../public/vue-assets/assets/throttle-siTOKEMt.js 0.78 kB │ gzip: 0.49 kB │ map: 3.24 kB\n../public/vue-assets/assets/lastFilters-B_B_O5LY.js 0.80 kB │ gzip: 0.51 kB │ map: 1.12 kB\n../public/vue-assets/assets/useAuthState-Bpx8WpBc.js 0.81 kB │ gzip: 0.50 kB │ map: 1.64 kB\n../public/vue-assets/assets/v-focus-618yf9WO.js 0.85 kB │ gzip: 0.52 kB │ map: 1.42 kB\n../public/vue-assets/assets/ListLoader-bfQmkVGu.js 0.87 kB │ gzip: 0.54 kB │ map: 2.90 kB\n../public/vue-assets/assets/utils-CAMifZzY.js 0.88 kB │ gzip: 0.53 kB │ map: 2.45 kB\n../public/vue-assets/assets/mobileApp-QFLS3kId.js 0.89 kB │ gzip: 0.54 kB │ map: 1.12 kB\n../public/vue-assets/assets/pickBy-CsQ1aO-4.js 0.95 kB │ gzip: 0.59 kB │ map: 2.57 kB\n../public/vue-assets/assets/BuildInfo-CIGre86L.js 1.00 kB │ gzip: 0.65 kB │ map: 1.48 kB\n../public/vue-assets/assets/LogoShort100-D_SQdBYB.js 1.01 kB │ gzip: 0.64 kB │ map: 1.23 kB\n../public/vue-assets/assets/useDrawerModal-CmhXI-pZ.js 1.04 kB │ gzip: 0.65 kB │ map: 1.94 kB\n../public/vue-assets/assets/_getAllKeysIn-CkMQAnJa.js 1.12 kB │ gzip: 0.67 kB │ map: 4.83 kB\n../public/vue-assets/assets/useAutosizeTextarea-DyMwh_20.js 1.13 kB │ gzip: 0.70 kB │ map: 3.16 kB\n../public/vue-assets/assets/planhat-DPA36Hcn.js 1.21 kB │ gzip: 0.71 kB │ map: 2.18 kB\n../public/vue-assets/assets/useStoreModule-Cx6UoGVJ.js 1.35 kB │ gzip: 0.72 kB │ map: 6.03 kB\n../public/vue-assets/assets/GenericMessage-BxGsrfvw.js 1.39 kB │ gzip: 0.75 kB │ map: 1.90 kB\n../public/vue-assets/assets/BrowserExtensionInstaller-DimyBPIi.js 1.45 kB │ gzip: 0.78 kB │ map: 2.05 kB\n../public/vue-assets/assets/debounce-7ycH7TWx.js 1.53 kB │ gzip: 0.83 kB │ map: 8.61 kB\n../public/vue-assets/assets/extension-installed-DUcV_uoi.js 1.60 kB │ gzip: 0.91 kB │ map: 3.20 kB\n../public/vue-assets/assets/RecipientsCell-BS9gDGey.js 1.61 kB │ gzip: 0.93 kB │ map: 4.04 kB\n../public/vue-assets/assets/PrimaryButton-DaKR3UAG.js 1.61 kB │ gzip: 0.87 kB │ map: 3.73 kB\n../public/vue-assets/assets/KioskBanner-CYQu1FG7.js 1.66 kB │ gzip: 1.00 kB │ map: 2.88 kB\n../public/vue-assets/assets/LogoLong100-Hid0kvjR.js 1.78 kB │ gzip: 1.08 kB │ map: 5.91 kB\n../public/vue-assets/assets/GoogleLikeButton-CGFy3nbz.js 1.80 kB │ gzip: 0.84 kB │ map: 4.71 kB\n../public/vue-assets/assets/AppAlert-DFp0nftl.js 1.86 kB │ gzip: 1.01 kB │ map: 6.51 kB\n../public/vue-assets/assets/usePusherEventListener-BjhdeObt.js 1.93 kB │ gzip: 1.08 kB │ map: 9.59 kB\n../public/vue-assets/assets/AppForm-BgtH5xsM.js 2.01 kB │ gzip: 1.10 kB │ map: 6.89 kB\n../public/vue-assets/assets/vee-validate-rules-IFeGZHb3.js 2.02 kB │ gzip: 0.89 kB │ map: 22.31 kB\n../public/vue-assets/assets/literals-CXSwYC8y.js 2.05 kB │ gzip: 0.97 kB │ map: 3.08 kB\n../public/vue-assets/assets/TabEmptyState-pk8vRxJt.js 2.16 kB │ gzip: 1.12 kB │ map: 8.26 kB\n../public/vue-assets/assets/Replies-B2cExxmx.js 2.23 kB │ gzip: 1.14 kB │ map: 2.40 kB\n../public/vue-assets/assets/settings--S7_RYRE.js 2.34 kB │ gzip: 1.24 kB │ map: 1.73 kB\n../public/vue-assets/assets/SeekBtn-CMp8sSUA.js 2.47 kB │ gzip: 1.18 kB │ map: 9.27 kB\n../public/vue-assets/assets/AiAutomation-cuK1068l.js 2.59 kB │ gzip: 1.37 kB │ map: 4.45 kB\n../public/vue-assets/assets/locked-DxPLOs1S.js 2.59 kB │ gzip: 1.42 kB │ map: 4.00 kB\n../public/vue-assets/assets/DrawerWidget-BPC-tCgo.js 2.68 kB │ gzip: 1.33 kB │ map: 6.95 kB\n../public/vue-assets/assets/vue-infinite-scroll-D0cI4gH6.js 2.72 kB │ gzip: 1.28 kB │ map: 9.99 kB\n../public/vue-assets/assets/useActivityCustomerName-CYuaZj-p.js 2.87 kB │ gzip: 1.41 kB │ map: 6.27 kB\n../public/vue-assets/assets/other-CVxaD0_B.js 2.97 kB │ gzip: 1.50 kB │ map: 3.38 kB\n../public/vue-assets/assets/InputDropdown-CBM8xiPT.js 3.33 kB │ gzip: 1.61 kB │ map: 6.02 kB\n../public/vue-assets/assets/AvatarsStack-hxex9Ic8.js 3.40 kB │ gzip: 1.51 kB │ map: 9.54 kB\n../public/vue-assets/assets/activity-preview--ydYatRe.js 3.45 kB │ gzip: 1.68 kB │ map: 10.11 kB\n../public/vue-assets/assets/FollowModal-SMYvVwqG.js 3.70 kB │ gzip: 1.81 kB │ map: 8.86 kB\n../public/vue-assets/assets/softphone-coach-CkLTUCy6.js 3.74 kB │ gzip: 1.96 kB │ map: 5.03 kB\n../public/vue-assets/assets/store-DPy-uEfS.js 4.10 kB │ gzip: 2.00 kB │ map: 18.73 kB\n../public/vue-assets/assets/AiContext-D36CpnKB.js 4.34 kB │ gzip: 2.08 kB │ map: 13.11 kB\n../public/vue-assets/assets/meeting-consent-C0YRJ_xR.js 4.43 kB │ gzip: 2.05 kB │ map: 9.72 kB\n../public/vue-assets/assets/vue-multiselect-VB2Agtp1.js 4.49 kB │ gzip: 1.93 kB │ map: 14.85 kB\n../public/vue-assets/assets/snackbarNotifications-DjDIGkWd.js 4.83 kB │ gzip: 1.58 kB │ map: 11.62 kB\n../public/vue-assets/assets/invitation--7sddGcP.js 4.88 kB │ gzip: 2.21 kB │ map: 19.38 kB\n../public/vue-assets/assets/connect-Bfq7LjjQ.js 5.17 kB │ gzip: 2.28 kB │ map: 10.35 kB\n../public/vue-assets/assets/textarea-caret-B2HdIdpZ.js 5.25 kB │ gzip: 2.41 kB │ map: 14.48 kB\n../public/vue-assets/assets/snackbar-Bq-YQ7pz.js 5.48 kB │ gzip: 2.37 kB │ map: 20.43 kB\n../public/vue-assets/assets/RadioField-2SeSZB7D.js 5.65 kB │ gzip: 2.22 kB │ map: 15.82 kB\n../public/vue-assets/assets/filters-D_eXZdIL.js 5.76 kB │ gzip: 2.17 kB │ map: 16.36 kB\n../public/vue-assets/assets/useActivityHelper-Dj5DULYF.js 5.84 kB │ gzip: 2.51 kB │ map: 14.26 kB\n../public/vue-assets/assets/vue-scrollto-ul3pFdrb.js 5.97 kB │ gzip: 2.72 kB │ map: 23.98 kB\n../public/vue-assets/assets/join-conference-gY3HLgqP.js 6.25 kB │ gzip: 2.83 kB │ map: 24.66 kB\n../public/vue-assets/assets/pluralize-BeKVJaE0.js 6.34 kB │ gzip: 2.70 kB │ map: 18.28 kB\n../public/vue-assets/assets/login-Bz_Gi9sn.js 7.02 kB │ gzip: 3.16 kB │ map: 17.51 kB\n../public/vue-assets/assets/AppLinks-Bg20Hslb.js 7.14 kB │ gzip: 2.80 kB │ map: 14.87 kB\n../public/vue-assets/assets/InputField-CkdrqKBp.js 7.20 kB │ gzip: 2.58 kB │ map: 19.39 kB\n../public/vue-assets/assets/UserAvatar-UKkSAS4R.js 7.93 kB │ gzip: 3.32 kB │ map: 34.91 kB\n../public/vue-assets/assets/InputText-Dgwsn6KA.js 7.99 kB │ gzip: 3.05 kB │ map: 20.64 kB\n../public/vue-assets/assets/AiCrmNotes-DNS51mtN.js 8.27 kB │ gzip: 3.32 kB │ map: 34.03 kB\n../public/vue-assets/assets/vue-mq-CmpUzQtD.js 8.84 kB │ gzip: 3.54 kB │ map: 23.69 kB\n../public/vue-assets/assets/pro-duotone-svg-icons-BSBZV3-t.js 9.51 kB │ gzip: 3.43 kB │ map: 3,146.68 kB\n../public/vue-assets/assets/activity-preview-result-CIGPrkbw.js 10.65 kB │ gzip: 3.98 kB │ map: 38.51 kB\n../public/vue-assets/assets/pro-regular-svg-icons-CWpSBR20.js 13.10 kB │ gzip: 4.64 kB │ map: 3,178.15 kB\n../public/vue-assets/assets/add-to-playlist-modal-CPwNAOXA.js 14.99 kB │ gzip: 5.98 kB │ map: 51.34 kB\n../public/vue-assets/assets/ai-reports-DAEn44FQ.js 15.17 kB │ gzip: 6.04 kB │ map: 51.85 kB\n../public/vue-assets/assets/export-portal-DB0CksQW.js 15.68 kB │ gzip: 6.22 kB │ map: 52.78 kB\n../public/vue-assets/assets/Comment-ClBvXCXq.js 15.92 kB │ gzip: 5.72 kB │ map: 39.96 kB\n../public/vue-assets/assets/useragent-DQ4F_O30.js 18.02 kB │ gzip: 8.06 kB │ map: 68.31 kB\n../public/vue-assets/assets/linkify-B_q6MgwI.js 18.58 kB │ gzip: 10.37 kB │ map: 88.73 kB\n../public/vue-assets/assets/ai-reports-manage-BTQmbI4-.js 18.92 kB │ gzip: 6.76 kB │ map: 66.14 kB\n../public/vue-assets/assets/dist-C2hisF0L.js 19.32 kB │ gzip: 7.87 kB │ map: 390.77 kB\n../public/vue-assets/assets/pro-solid-svg-icons-DBOkpln3.js 19.46 kB │ gzip: 6.72 kB │ map: 2,692.14 kB\n../public/vue-assets/assets/purify.es-BxOw6oE6.js 21.49 kB │ gzip: 8.71 kB │ map: 83.43 kB\n../public/vue-assets/assets/ActionItems-CqgLpVFE.js 21.92 kB │ gzip: 8.54 kB │ map: 76.05 kB\n../public/vue-assets/assets/mobile-4e6kQ9l_.js 23.34 kB │ gzip: 9.71 kB │ map: 50.90 kB\n../public/vue-assets/assets/basic-modal-BdXsnFwC.js 23.87 kB │ gzip: 9.06 kB │ map: 64.15 kB\n../public/vue-assets/assets/GridView-CocIYUaz.js 26.60 kB │ gzip: 10.05 kB │ map: 92.74 kB\n../public/vue-assets/assets/ondemand-CE8XLH98.js 26.88 kB │ gzip: 9.38 kB │ map: 73.94 kB\n../public/vue-assets/assets/CrmLink-DKYsnHnx.js 27.91 kB │ gzip: 10.18 kB │ map: 93.18 kB\n../public/vue-assets/assets/liquor-tree-COUefof4.js 30.75 kB │ gzip: 9.58 kB │ map: 78.74 kB\n../public/vue-assets/assets/DealRiskList-3DSxnTNQ.js 34.39 kB │ gzip: 10.62 kB │ map: 115.18 kB\n../public/vue-assets/assets/AskAnything-BQ36EoGS.js 39.50 kB │ gzip: 14.97 kB │ map: 173.20 kB\n../public/vue-assets/assets/lib-CwM9toD2.js 39.69 kB │ gzip: 12.70 kB │ map: 138.34 kB\n../public/vue-assets/assets/AppFormField-CqCXZwrk.js 41.91 kB │ gzip: 12.69 kB │ map: 150.73 kB\n../public/vue-assets/assets/deal-view-B92qaj32.js 43.22 kB │ gzip: 14.34 kB │ map: 150.62 kB\n../public/vue-assets/assets/exports-D1lmea4O.js 47.84 kB │ gzip: 16.46 kB │ map: 294.48 kB\n../public/vue-assets/assets/playlists-u3UeUUz5.js 48.28 kB │ gzip: 15.07 kB │ map: 153.25 kB\n../public/vue-assets/assets/callScoringTemplates-zeRn40uL.js 55.13 kB │ gzip: 13.28 kB │ map: 65.85 kB\n../public/vue-assets/assets/_copyObject-USkOnlaQ.js 61.28 kB │ gzip: 20.09 kB │ map: 239.59 kB\n../public/vue-assets/assets/pusher-znYCfz7U.js 62.98 kB │ gzip: 18.88 kB │ map: 219.27 kB\n../public/vue-assets/assets/onboard-CCnYJmCP.js 63.11 kB │ gzip: 21.85 kB │ map: 201.38 kB\n../public/vue-assets/assets/StatusBadge-CNGFZm8P.js 64.66 kB │ gzip: 22.96 kB │ map: 244.72 kB\n../public/vue-assets/assets/kiosk-D6N5-ivP.js 79.60 kB │ gzip: 22.65 kB │ map: 300.68 kB\n../public/vue-assets/assets/preload-helper-DCvhahzG.js 82.59 kB │ gzip: 27.46 kB │ map: 3,452.35 kB\n../public/vue-assets/assets/deal-insights-jlkz65MZ.js 94.84 kB │ gzip: 28.16 kB │ map: 292.79 kB\n../public/vue-assets/assets/ListView-DmKqYVi1.js 115.71 kB │ gzip: 33.78 kB │ map: 308.10 kB\n../public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js 117.59 kB │ gzip: 38.71 kB │ map: 500.60 kB\n../public/vue-assets/assets/WelcomeLayout-B6wd32HG.js 120.67 kB │ gzip: 34.16 kB │ map: 258.56 kB\n../public/vue-assets/assets/dashboard-BWoBIPKA.js 128.71 kB │ gzip: 40.05 kB │ map: 410.48 kB\n../public/vue-assets/assets/emoji-input-CSq87OVy.js 129.28 kB │ gzip: 36.72 kB │ map: 266.15 kB\n../public/vue-assets/assets/AppButton-D3qMdODr.js 133.44 kB │ gzip: 43.03 kB │ map: 516.67 kB\n../public/vue-assets/assets/sentry-icPKypZa.js 164.28 kB │ gzip: 52.24 kB │ map: 831.82 kB\n../public/vue-assets/assets/OrgSettingsLayout-BZ-J0giH.js 176.33 kB │ gzip: 56.15 kB │ map: 623.43 kB\n../public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js 180.40 kB │ gzip: 67.85 kB │ map: 836.88 kB\n../public/vue-assets/assets/playback-DKsqp_aL.js 198.79 kB │ gzip: 61.85 kB │ map: 684.87 kB\n../public/vue-assets/assets/index.module-Bjlhgfd1.js 218.14 kB │ gzip: 64.16 kB │ map: 1,108.20 kB\n../public/vue-assets/assets/intl-tel-input-BW4mv40Q.js 264.94 kB │ gzip: 60.31 kB │ map: 475.61 kB\n../public/vue-assets/assets/team-insights-D9P6ojjD.js 298.57 kB │ gzip: 77.22 kB │ map: 959.96 kB\n../public/vue-assets/assets/popper-CQwVcrX4.js 307.13 kB │ gzip: 103.86 kB │ map: 1,245.28 kB\n../public/vue-assets/assets/PhoneField-CwCIoAYm.js 343.99 kB │ gzip: 84.90 kB │ map: 849.05 kB\n../public/vue-assets/assets/live-DWumv-QD.js 367.43 kB │ gzip: 97.05 kB │ map: 792.41 kB\n../public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BNO485xV.js 689.63 kB │ gzip: 202.81 kB │ map: 3,016.64 kB\n../public/vue-assets/assets/index-DiEs6xIb.js 825.23 kB │ gzip: 72.54 kB │ map: 436.62 kB\n../public/vue-assets/assets/logged-in-layout-Cq1ztH5O.js 1,402.70 kB │ gzip: 438.08 kB │ map: 6,283.55 kB\n\n[plugin builtin:vite-reporter] \n(!) Some chunks are larger than 500 kB after minification. Consider:\n- Using dynamic import() to code-split the application\n- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting\n- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.\n✓ built in 13.59s\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yarn build\n[dotenv@17.3.1] injecting env (0) from .env -- tip: ⚙️\u0000 load multiple .env files with { path: ['.env.local', '.env'] }\nvite v8.0.0 building client environment for production...\n✓ 4656 modules transformed.\n[sentry-vite-plugin] Warning: No auth token provided. Will not create release. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\n[sentry-vite-plugin] Warning: No auth token provided. Will not upload source maps. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\ncomputing gzip size...\n../public/vue-assets/index.html 4.58 kB │ gzip: 1.16 kB\n../public/vue-assets/assets/job-adder-B2LcgC4o.png 5.41 kB\n../public/vue-assets/assets/dixa-DzT89tKh.png 7.87 kB\n../public/vue-assets/assets/planhat-CQycOTMW.svg 8.61 kB │ gzip: 3.48 kB\n../public/vue-assets/assets/funding-circle-9iGnDGsz.png 9.43 kB\n../public/vue-assets/assets/cision-uOdx_YiN.svg 14.41 kB │ gzip: 6.32 kB\n../public/vue-assets/assets/les-mills-DWabpmYc.png 15.73 kB\n../public/vue-assets/assets/superside-btT37L9L.png 26.69 kB\n../public/vue-assets/assets/flags-a2kmUSbF.webp 28.18 kB\n../public/vue-assets/assets/quinyx-RGQNHx6o.png 63.17 kB\n../public/vue-assets/assets/flags@2x-gR6KPp3x.webp 66.44 kB\n../public/vue-assets/assets/bg-marketing-CAIHIJMj.png 70.66 kB\n../public/vue-assets/.vite/manifest.json 85.73 kB │ gzip: 8.33 kB\n../public/vue-assets/assets/jiminny-score-DtTbKZaD.svg 131.93 kB │ gzip: 86.07 kB\n../public/vue-assets/assets/wavy-bg-vnCaCm9m.webm 670.97 kB\n../public/vue-assets/assets/wavy-bg-Op6dRIjR.mp4 14,484.63 kB\n../public/vue-assets/assets/AppForm-EEGPL5K8.css 0.04 kB │ gzip: 0.06 kB\n../public/vue-assets/assets/RecipientsCell-DNOSS8k1.css 0.12 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/softphone-coach-Cy_9nuQZ.css 0.13 kB │ gzip: 0.10 kB\n../public/vue-assets/assets/locked-CHS8VPur.css 0.15 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/mobile-KPnGhnTp.css 0.16 kB │ gzip: 0.14 kB\n../public/vue-assets/assets/BuildInfo-CxNQv_OV.css 0.20 kB │ gzip: 0.17 kB\n../public/vue-assets/assets/AvatarsStack-C0Nvfh1V.css 0.28 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/LogoLong100-ByQTvUY3.css 0.32 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/connect--xBDzLbc.css 0.34 kB │ gzip: 0.21 kB\n../public/vue-assets/assets/KioskBanner-xU3FRd8g.css 0.41 kB │ gzip: 0.27 kB\n../public/vue-assets/assets/extension-installed-BPBhG13P.css 0.41 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/FollowModal-A0rJmIOK.css 0.45 kB │ gzip: 0.23 kB\n../public/vue-assets/assets/GenericMessage-DutAlObK.css 0.49 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/textarea-caret-DCiNUER_.css 0.51 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/AppLinks-BznfGcBR.css 0.58 kB │ gzip: 0.33 kB\n../public/vue-assets/assets/SeekBtn-_VMPIk6v.css 0.63 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/emoji-input-kJWRbWf8.css 0.68 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/activity-preview-BDxP2S0-.css 0.70 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/ListLoader-DQqAw-8s.css 0.73 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/filters-Bgb6cQD6.css 0.75 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/AiContext-Bg-zjjbA.css 0.99 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/vue3-daterange-picker-izPOBj3P.css 1.02 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/PrimaryButton-CJj2FzKS.css 1.08 kB │ gzip: 0.34 kB\n../public/vue-assets/assets/other-BTuPXtu_.css 1.15 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/jenesius-vue-modal-DDcfejHO.css 1.17 kB │ gzip: 0.48 kB\n../public/vue-assets/assets/TabEmptyState-DOlIO68k.css 1.21 kB │ gzip: 0.53 kB\n../public/vue-assets/assets/AppAlert-BlSJKiqj.css 1.29 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/useActivityCustomerName-CBjfIUfJ.css 1.42 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/UserAvatar-co8l9mMk.css 1.51 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/basic-modal-Dx7ZBbcA.css 1.55 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/login-DYbcBesM.css 1.58 kB │ gzip: 0.61 kB\n../public/vue-assets/assets/DrawerWidget-poAmqspk.css 1.67 kB │ gzip: 0.69 kB\n../public/vue-assets/assets/ActionItems-5FkZSo3G.css 1.79 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/join-conference-DQ6Yl7eU.css 1.82 kB │ gzip: 0.75 kB\n../public/vue-assets/assets/InputText-C-DxDNSV.css 1.95 kB │ gzip: 0.71 kB\n../public/vue-assets/assets/GoogleLikeButton-DvQHxbqR.css 2.11 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/usePusherEventListener-CDj8aSNp.css 2.20 kB │ gzip: 0.73 kB\n../public/vue-assets/assets/onboard-IzGiQ-EC.css 2.32 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/ai-reports-manage-DaynTl62.css 2.34 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/StatusBadge-B4N12dzU.css 2.35 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/GridView-CsP1Jk-k.css 2.36 kB │ gzip: 0.90 kB\n../public/vue-assets/assets/WelcomeLayout-6AX86p6F.css 2.38 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/snackbar-CS_iqvrv.css 2.45 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/ai-reports-mtqGqPPq.css 2.47 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/Comment-gmAfiuer.css 2.63 kB │ gzip: 1.00 kB\n../public/vue-assets/assets/meeting-consent-BasRvxE3.css 2.66 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/DealRiskList-CwxmDnSC.css 2.67 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/add-to-playlist-modal-C_ad1hht.css 2.84 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/live-_zT5qBc1.css 3.60 kB │ gzip: 1.16 kB\n../public/vue-assets/assets/RadioField-DDp8Y71G.css 3.64 kB │ gzip: 1.20 kB\n../public/vue-assets/assets/vue-multiselect-BkQNV7oL.css 3.80 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/activity-preview-result-Db1Da81T.css 3.89 kB │ gzip: 1.15 kB\n../public/vue-assets/assets/liquor-tree-CB5oh8v1.css 4.08 kB │ gzip: 1.24 kB\n../public/vue-assets/assets/sentry-CSjHcvmn.css 4.47 kB │ gzip: 1.08 kB\n../public/vue-assets/assets/AiAutomation-Cj1t8v5y.css 4.61 kB │ gzip: 0.84 kB\n../public/vue-assets/assets/export-portal-CyIUVTEe.css 5.34 kB │ gzip: 1.65 kB\n../public/vue-assets/assets/InputField-C4hoKJ7Z.css 6.13 kB │ gzip: 1.27 kB\n../public/vue-assets/assets/vue-mq-bh4L87Tr.css 6.64 kB │ gzip: 1.95 kB\n../public/vue-assets/assets/invitation-EPR1t-bH.css 6.74 kB │ gzip: 2.22 kB\n../public/vue-assets/assets/ondemand-C2m0YVGA.css 6.84 kB │ gzip: 2.02 kB\n../public/vue-assets/assets/AskAnything-CyvEhHS1.css 8.60 kB │ gzip: 2.60 kB\n../public/vue-assets/assets/AppButton-BIeoar_U.css 8.88 kB │ gzip: 2.04 kB\n../public/vue-assets/assets/kiosk-BcheyWWL.css 9.56 kB │ gzip: 2.53 kB\n../public/vue-assets/assets/AiCrmNotes-nNTe_mOY.css 10.62 kB │ gzip: 1.93 kB\n../public/vue-assets/assets/deal-view-DJzG7PLp.css 11.70 kB │ gzip: 3.29 kB\n../public/vue-assets/assets/tokens-DZgWrfKd.css 12.44 kB │ gzip: 2.82 kB\n../public/vue-assets/assets/tokens-DL2BPbai.css 12.89 kB │ gzip: 2.87 kB\n../public/vue-assets/assets/playlists-Cy3t3kYU.css 13.54 kB │ gzip: 3.16 kB\n../public/vue-assets/assets/PhoneField-D_kGOah0.css 13.75 kB │ gzip: 3.44 kB\n../public/vue-assets/assets/ListView-Dt0qkyuY.css 17.07 kB │ gzip: 4.11 kB\n../public/vue-assets/assets/intl-tel-input-DgmgTINs.css 17.11 kB │ gzip: 5.37 kB\n../public/vue-assets/assets/AppFormField-DvpfRzi8.css 18.98 kB │ gzip: 4.42 kB\n../public/vue-assets/assets/OrgSettingsLayout-BogdXtgY.css 21.98 kB │ gzip: 5.51 kB\n../public/vue-assets/assets/dashboard-DQw3TKyk.css 22.74 kB │ gzip: 4.35 kB\n../public/vue-assets/assets/deal-insights-CfX3UNzh.css 29.66 kB │ gzip: 6.44 kB\n../public/vue-assets/assets/team-insights-CjVhm0JN.css 42.31 kB │ gzip: 8.46 kB\n../public/vue-assets/assets/playback-CvJP5tX1.css 44.55 kB │ gzip: 10.36 kB\n../public/vue-assets/assets/video-js-skin-DYZluGb-.css 47.73 kB │ gzip: 12.63 kB\n../public/vue-assets/assets/assets-CAbfI4CY.css 83.38 kB │ gzip: 51.71 kB\n../public/vue-assets/assets/logged-in-layout-1mAqkmnS.css 153.67 kB │ gzip: 27.18 kB\n../public/vue-assets/assets/assets-xH6dx_9q.css 259.71 kB │ gzip: 181.71 kB\n../public/vue-assets/assets/directives-DJJeJJOP.js 0.53 kB │ gzip: 0.36 kB │ map: 0.32 kB\n../public/vue-assets/assets/jenesius-vue-modal-BuBhyl83.js 0.54 kB │ gzip: 0.37 kB │ map: 0.47 kB\n../public/vue-assets/assets/spark-D_-Wgfar.js 0.54 kB │ gzip: 0.35 kB │ map: 0.40 kB\n../public/vue-assets/assets/url-messenger-_CGQa-lH.js 0.56 kB │ gzip: 0.37 kB │ map: 0.46 kB\n../public/vue-assets/assets/wavy-bg-Cec-_GDj.js 0.57 kB │ gzip: 0.36 kB │ map: 0.35 kB\n../public/vue-assets/assets/component-css-class-CtO0AVgW.js 0.61 kB │ gzip: 0.40 kB │ map: 0.91 kB\n../public/vue-assets/assets/theme-CrWye7yo.js 0.63 kB │ gzip: 0.41 kB │ map: 0.49 kB\n../public/vue-assets/assets/pick-D-kTQpNV.js 0.71 kB │ gzip: 0.45 kB │ map: 1.59 kB\n../public/vue-assets/assets/utils-BjsEoLDB.js 0.77 kB │ gzip: 0.50 kB │ map: 1.68 kB\n../public/vue-assets/assets/throttle-CEV1oVAM.js 0.78 kB │ gzip: 0.49 kB │ map: 3.24 kB\n../public/vue-assets/assets/lastFilters-D3xNTqAt.js 0.80 kB │ gzip: 0.51 kB │ map: 1.12 kB\n../public/vue-assets/assets/useAuthState-Bpx8WpBc.js 0.81 kB │ gzip: 0.50 kB │ map: 1.64 kB\n../public/vue-assets/assets/v-focus-618yf9WO.js 0.85 kB │ gzip: 0.52 kB │ map: 1.42 kB\n../public/vue-assets/assets/ListLoader-bfQmkVGu.js 0.87 kB │ gzip: 0.54 kB │ map: 2.90 kB\n../public/vue-assets/assets/utils-CAMifZzY.js 0.88 kB │ gzip: 0.53 kB │ map: 2.45 kB\n../public/vue-assets/assets/mobileApp-QFLS3kId.js 0.89 kB │ gzip: 0.54 kB │ map: 1.12 kB\n../public/vue-assets/assets/pickBy-DftTc1_7.js 0.95 kB │ gzip: 0.59 kB │ map: 2.57 kB\n../public/vue-assets/assets/BuildInfo-CIGre86L.js 1.00 kB │ gzip: 0.65 kB │ map: 1.48 kB\n../public/vue-assets/assets/LogoShort100-D_SQdBYB.js 1.01 kB │ gzip: 0.64 kB │ map: 1.23 kB\n../public/vue-assets/assets/useDrawerModal-CmhXI-pZ.js 1.04 kB │ gzip: 0.65 kB │ map: 1.94 kB\n../public/vue-assets/assets/_getAllKeysIn-CkMQAnJa.js 1.12 kB │ gzip: 0.67 kB │ map: 4.83 kB\n../public/vue-assets/assets/useAutosizeTextarea-DyMwh_20.js 1.13 kB │ gzip: 0.70 kB │ map: 3.16 kB\n../public/vue-assets/assets/planhat-DPA36Hcn.js 1.21 kB │ gzip: 0.71 kB │ map: 2.18 kB\n../public/vue-assets/assets/useStoreModule-Cx6UoGVJ.js 1.35 kB │ gzip: 0.72 kB │ map: 6.03 kB\n../public/vue-assets/assets/GenericMessage-BxGsrfvw.js 1.39 kB │ gzip: 0.75 kB │ map: 1.90 kB\n../public/vue-assets/assets/BrowserExtensionInstaller-DimyBPIi.js 1.45 kB │ gzip: 0.78 kB │ map: 2.05 kB\n../public/vue-assets/assets/debounce-CK0K0ozg.js 1.53 kB │ gzip: 0.83 kB │ map: 8.61 kB\n../public/vue-assets/assets/extension-installed-DLwqdmK8.js 1.60 kB │ gzip: 0.91 kB │ map: 3.20 kB\n../public/vue-assets/assets/RecipientsCell-BS9gDGey.js 1.61 kB │ gzip: 0.93 kB │ map: 4.04 kB\n../public/vue-assets/assets/PrimaryButton-DaKR3UAG.js 1.61 kB │ gzip: 0.87 kB │ map: 3.73 kB\n../public/vue-assets/assets/KioskBanner-D8zd6tGl.js 1.66 kB │ gzip: 1.00 kB │ map: 2.88 kB\n../public/vue-assets/assets/LogoLong100-Hid0kvjR.js 1.78 kB │ gzip: 1.08 kB │ map: 5.91 kB\n../public/vue-assets/assets/GoogleLikeButton-CGFy3nbz.js 1.80 kB │ gzip: 0.84 kB │ map: 4.71 kB\n../public/vue-assets/assets/AppAlert-DFp0nftl.js 1.86 kB │ gzip: 1.01 kB │ map: 6.51 kB\n../public/vue-assets/assets/usePusherEventListener-D8AEMpQD.js 1.93 kB │ gzip: 1.08 kB │ map: 9.59 kB\n../public/vue-assets/assets/AppForm-BgtH5xsM.js 2.01 kB │ gzip: 1.10 kB │ map: 6.89 kB\n../public/vue-assets/assets/vee-validate-rules-IFeGZHb3.js 2.02 kB │ gzip: 0.89 kB │ map: 22.31 kB\n../public/vue-assets/assets/literals-CXSwYC8y.js 2.05 kB │ gzip: 0.97 kB │ map: 3.08 kB\n../public/vue-assets/assets/TabEmptyState-pk8vRxJt.js 2.16 kB │ gzip: 1.12 kB │ map: 8.26 kB\n../public/vue-assets/assets/Replies-DZPkx2YQ.js 2.23 kB │ gzip: 1.14 kB │ map: 2.40 kB\n../public/vue-assets/assets/settings-DHjh0S4O.js 2.34 kB │ gzip: 1.24 kB │ map: 1.73 kB\n../public/vue-assets/assets/SeekBtn-CMp8sSUA.js 2.47 kB │ gzip: 1.18 kB │ map: 9.27 kB\n../public/vue-assets/assets/AiAutomation-CTc8r0pe.js 2.59 kB │ gzip: 1.37 kB │ map: 4.45 kB\n../public/vue-assets/assets/locked-CHodD5F8.js 2.59 kB │ gzip: 1.43 kB │ map: 4.00 kB\n../public/vue-assets/assets/DrawerWidget-BPC-tCgo.js 2.68 kB │ gzip: 1.33 kB │ map: 6.95 kB\n../public/vue-assets/assets/vue-infinite-scroll-D0cI4gH6.js 2.72 kB │ gzip: 1.28 kB │ map: 9.99 kB\n../public/vue-assets/assets/useActivityCustomerName-CYuaZj-p.js 2.87 kB │ gzip: 1.41 kB │ map: 6.27 kB\n../public/vue-assets/assets/other-CS9P7JeF.js 2.97 kB │ gzip: 1.50 kB │ map: 3.38 kB\n../public/vue-assets/assets/InputDropdown-CBM8xiPT.js 3.33 kB │ gzip: 1.61 kB │ map: 6.02 kB\n../public/vue-assets/assets/AvatarsStack-hxex9Ic8.js 3.40 kB │ gzip: 1.51 kB │ map: 9.54 kB\n../public/vue-assets/assets/activity-preview-Dkxw0lkq.js 3.45 kB │ gzip: 1.68 kB │ map: 10.11 kB\n../public/vue-assets/assets/FollowModal-SMYvVwqG.js 3.70 kB │ gzip: 1.81 kB │ map: 8.86 kB\n../public/vue-assets/assets/softphone-coach-BdRuUFYY.js 3.74 kB │ gzip: 1.96 kB │ map: 5.03 kB\n../public/vue-assets/assets/store-Cploop1A.js 4.10 kB │ gzip: 2.00 kB │ map: 18.73 kB\n../public/vue-assets/assets/AiContext-Cz-Y-wOJ.js 4.34 kB │ gzip: 2.08 kB │ map: 13.11 kB\n../public/vue-assets/assets/meeting-consent-DZKBS9eW.js 4.43 kB │ gzip: 2.05 kB │ map: 9.72 kB\n../public/vue-assets/assets/vue-multiselect-VB2Agtp1.js 4.49 kB │ gzip: 1.93 kB │ map: 14.85 kB\n../public/vue-assets/assets/snackbarNotifications-DjDIGkWd.js 4.83 kB │ gzip: 1.58 kB │ map: 11.62 kB\n../public/vue-assets/assets/invitation-DUWFWQvr.js 4.88 kB │ gzip: 2.21 kB │ map: 19.38 kB\n../public/vue-assets/assets/textarea-caret-B2HdIdpZ.js 5.25 kB │ gzip: 2.41 kB │ map: 14.48 kB\n../public/vue-assets/assets/connect-LR_dB_VC.js 5.25 kB │ gzip: 2.30 kB │ map: 10.52 kB\n../public/vue-assets/assets/snackbar-Bq-YQ7pz.js 5.48 kB │ gzip: 2.37 kB │ map: 20.43 kB\n../public/vue-assets/assets/RadioField-2SeSZB7D.js 5.65 kB │ gzip: 2.22 kB │ map: 15.82 kB\n../public/vue-assets/assets/filters-D_eXZdIL.js 5.76 kB │ gzip: 2.17 kB │ map: 16.36 kB\n../public/vue-assets/assets/useActivityHelper-C6KkT8e1.js 5.84 kB │ gzip: 2.51 kB │ map: 14.26 kB\n../public/vue-assets/assets/vue-scrollto-ul3pFdrb.js 5.97 kB │ gzip: 2.72 kB │ map: 23.98 kB\n../public/vue-assets/assets/join-conference-D5dWNU9i.js 6.25 kB │ gzip: 2.82 kB │ map: 24.66 kB\n../public/vue-assets/assets/pluralize-BeKVJaE0.js 6.34 kB │ gzip: 2.70 kB │ map: 18.28 kB\n../public/vue-assets/assets/login-DGWP8lD_.js 7.02 kB │ gzip: 3.16 kB │ map: 17.51 kB\n../public/vue-assets/assets/AppLinks-Bg20Hslb.js 7.14 kB │ gzip: 2.80 kB │ map: 14.87 kB\n../public/vue-assets/assets/InputField-CkdrqKBp.js 7.20 kB │ gzip: 2.58 kB │ map: 19.39 kB\n../public/vue-assets/assets/UserAvatar-UKkSAS4R.js 7.93 kB │ gzip: 3.32 kB │ map: 34.91 kB\n../public/vue-assets/assets/InputText-zafDvh_H.js 7.99 kB │ gzip: 3.05 kB │ map: 20.64 kB\n../public/vue-assets/assets/AiCrmNotes-Co9u0t34.js 8.27 kB │ gzip: 3.32 kB │ map: 34.03 kB\n../public/vue-assets/assets/vue-mq-CmpUzQtD.js 8.84 kB │ gzip: 3.54 kB │ map: 23.69 kB\n../public/vue-assets/assets/pro-duotone-svg-icons-BSBZV3-t.js 9.51 kB │ gzip: 3.43 kB │ map: 3,146.68 kB\n../public/vue-assets/assets/activity-preview-result-C820ZfzV.js 10.65 kB │ gzip: 3.98 kB │ map: 38.51 kB\n../public/vue-assets/assets/pro-regular-svg-icons-CWpSBR20.js 13.10 kB │ gzip: 4.64 kB │ map: 3,178.15 kB\n../public/vue-assets/assets/add-to-playlist-modal-CfDB_Ohb.js 14.99 kB │ gzip: 5.98 kB │ map: 51.34 kB\n../public/vue-assets/assets/ai-reports-BhbjZb-1.js 15.17 kB │ gzip: 6.04 kB │ map: 51.85 kB\n../public/vue-assets/assets/export-portal-BM3w9d6h.js 15.68 kB │ gzip: 6.22 kB │ map: 52.78 kB\n../public/vue-assets/assets/Comment-Kj7lCx0-.js 15.92 kB │ gzip: 5.72 kB │ map: 39.96 kB\n../public/vue-assets/assets/useragent-DQ4F_O30.js 18.02 kB │ gzip: 8.06 kB │ map: 68.31 kB\n../public/vue-assets/assets/linkify-B_q6MgwI.js 18.58 kB │ gzip: 10.37 kB │ map: 88.73 kB\n../public/vue-assets/assets/ai-reports-manage-ulzm9gk2.js 18.92 kB │ gzip: 6.76 kB │ map: 66.14 kB\n../public/vue-assets/assets/dist-C2hisF0L.js 19.32 kB │ gzip: 7.87 kB │ map: 390.77 kB\n../public/vue-assets/assets/pro-solid-svg-icons-DBOkpln3.js 19.46 kB │ gzip: 6.72 kB │ map: 2,692.14 kB\n../public/vue-assets/assets/purify.es-BxOw6oE6.js 21.49 kB │ gzip: 8.71 kB │ map: 83.43 kB\n../public/vue-assets/assets/ActionItems-C9INej-u.js 21.92 kB │ gzip: 8.54 kB │ map: 76.05 kB\n../public/vue-assets/assets/mobile-C-6FGhqy.js 23.34 kB │ gzip: 9.71 kB │ map: 50.90 kB\n../public/vue-assets/assets/basic-modal-BdXsnFwC.js 23.87 kB │ gzip: 9.06 kB │ map: 64.15 kB\n../public/vue-assets/assets/GridView-mZhlfeUe.js 26.60 kB │ gzip: 10.05 kB │ map: 92.74 kB\n../public/vue-assets/assets/ondemand-mq9zQOVK.js 26.88 kB │ gzip: 9.39 kB │ map: 73.94 kB\n../public/vue-assets/assets/CrmLink-DKYsnHnx.js 27.91 kB │ gzip: 10.18 kB │ map: 93.18 kB\n../public/vue-assets/assets/liquor-tree-COUefof4.js 30.75 kB │ gzip: 9.58 kB │ map: 78.74 kB\n../public/vue-assets/assets/DealRiskList-BUgW-21G.js 34.39 kB │ gzip: 10.62 kB │ map: 115.18 kB\n../public/vue-assets/assets/AskAnything-CVCNDjsB.js 39.50 kB │ gzip: 14.97 kB │ map: 173.20 kB\n../public/vue-assets/assets/lib-CwM9toD2.js 39.69 kB │ gzip: 12.70 kB │ map: 138.34 kB\n../public/vue-assets/assets/AppFormField-Cu4TbCsZ.js 41.91 kB │ gzip: 12.69 kB │ map: 150.73 kB\n../public/vue-assets/assets/deal-view-CoZZkbGt.js 43.22 kB │ gzip: 14.34 kB │ map: 150.62 kB\n../public/vue-assets/assets/exports-D1lmea4O.js 47.84 kB │ gzip: 16.46 kB │ map: 294.48 kB\n../public/vue-assets/assets/playlists-BpFWtNz1.js 48.28 kB │ gzip: 15.08 kB │ map: 153.25 kB\n../public/vue-assets/assets/callScoringTemplates-zeRn40uL.js 55.13 kB │ gzip: 13.28 kB │ map: 65.85 kB\n../public/vue-assets/assets/_copyObject-USkOnlaQ.js 61.28 kB │ gzip: 20.09 kB │ map: 239.59 kB\n../public/vue-assets/assets/pusher-znYCfz7U.js 62.98 kB │ gzip: 18.88 kB │ map: 219.27 kB\n../public/vue-assets/assets/onboard-D27tiAEp.js 63.11 kB │ gzip: 21.85 kB │ map: 201.38 kB\n../public/vue-assets/assets/StatusBadge-Dmn6sJ5_.js 64.66 kB │ gzip: 22.96 kB │ map: 244.72 kB\n../public/vue-assets/assets/kiosk-DO4wa1Ld.js 79.60 kB │ gzip: 22.65 kB │ map: 300.68 kB\n../public/vue-assets/assets/preload-helper-DCvhahzG.js 82.59 kB │ gzip: 27.46 kB │ map: 3,452.35 kB\n../public/vue-assets/assets/deal-insights-BKxR7Ag9.js 94.84 kB │ gzip: 28.17 kB │ map: 292.79 kB\n../public/vue-assets/assets/ListView-V1cngeeb.js 115.71 kB │ gzip: 33.78 kB │ map: 308.10 kB\n../public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js 117.59 kB │ gzip: 38.71 kB │ map: 500.60 kB\n../public/vue-assets/assets/WelcomeLayout-B6wd32HG.js 120.67 kB │ gzip: 34.16 kB │ map: 258.56 kB\n../public/vue-assets/assets/dashboard-D0nLYfJh.js 128.71 kB │ gzip: 40.05 kB │ map: 410.48 kB\n../public/vue-assets/assets/emoji-input-CSq87OVy.js 129.28 kB │ gzip: 36.72 kB │ map: 266.15 kB\n../public/vue-assets/assets/AppButton-D3qMdODr.js 133.44 kB │ gzip: 43.03 kB │ map: 516.67 kB\n../public/vue-assets/assets/sentry-zRF85Jni.js 164.28 kB │ gzip: 52.24 kB │ map: 831.82 kB\n../public/vue-assets/assets/OrgSettingsLayout-DXmpAhyV.js 176.33 kB │ gzip: 56.15 kB │ map: 623.43 kB\n../public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js 180.40 kB │ gzip: 67.85 kB │ map: 836.88 kB\n../public/vue-assets/assets/playback-BerPTevk.js 198.79 kB │ gzip: 61.85 kB │ map: 684.87 kB\n../public/vue-assets/assets/index.module-Bjlhgfd1.js 218.14 kB │ gzip: 64.16 kB │ map: 1,108.20 kB\n../public/vue-assets/assets/intl-tel-input-BW4mv40Q.js 264.94 kB │ gzip: 60.31 kB │ map: 475.61 kB\n../public/vue-assets/assets/team-insights-Rfo09TvE.js 298.57 kB │ gzip: 77.21 kB │ map: 959.96 kB\n../public/vue-assets/assets/popper-CQwVcrX4.js 307.13 kB │ gzip: 103.86 kB │ map: 1,245.28 kB\n../public/vue-assets/assets/PhoneField-CwCIoAYm.js 343.99 kB │ gzip: 84.90 kB │ map: 849.05 kB\n../public/vue-assets/assets/live-BkQge7FT.js 367.43 kB │ gzip: 97.05 kB │ map: 792.41 kB\n../public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BNO485xV.js 689.63 kB │ gzip: 202.81 kB │ map: 3,016.64 kB\n../public/vue-assets/assets/index-RakUX3TF.js 825.23 kB │ gzip: 72.54 kB │ map: 436.62 kB\n../public/vue-assets/assets/logged-in-layout-WZXlxzkM.js 1,402.70 kB │ gzip: 438.07 kB │ map: 6,283.55 kB\n\n[plugin builtin:vite-reporter] \n(!) Some chunks are larger than 500 kB after minification. Consider:\n- Using dynamic import() to code-split the application\n- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting\n- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.\n✓ built in 25.43s\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yarn build\n[dotenv@17.3.1] injecting env (0) from .env -- tip: ⚡️\u0000 secrets for agents: https://dotenvx.com/as2\nvite v8.0.0 building client environment for production...\n✓ 4656 modules transformed.\n[sentry-vite-plugin] Warning: No auth token provided. Will not create release. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\n[sentry-vite-plugin] Warning: No auth token provided. Will not upload source maps. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\ncomputing gzip size...\n../public/vue-assets/index.html 4.58 kB │ gzip: 1.16 kB\n../public/vue-assets/assets/job-adder-B2LcgC4o.png 5.41 kB\n../public/vue-assets/assets/dixa-DzT89tKh.png 7.87 kB\n../public/vue-assets/assets/planhat-CQycOTMW.svg 8.61 kB │ gzip: 3.48 kB\n../public/vue-assets/assets/funding-circle-9iGnDGsz.png 9.43 kB\n../public/vue-assets/assets/cision-uOdx_YiN.svg 14.41 kB │ gzip: 6.32 kB\n../public/vue-assets/assets/les-mills-DWabpmYc.png 15.73 kB\n../public/vue-assets/assets/superside-btT37L9L.png 26.69 kB\n../public/vue-assets/assets/flags-a2kmUSbF.webp 28.18 kB\n../public/vue-assets/assets/quinyx-RGQNHx6o.png 63.17 kB\n../public/vue-assets/assets/flags@2x-gR6KPp3x.webp 66.44 kB\n../public/vue-assets/assets/bg-marketing-CAIHIJMj.png 70.66 kB\n../public/vue-assets/.vite/manifest.json 85.73 kB │ gzip: 8.32 kB\n../public/vue-assets/assets/jiminny-score-DtTbKZaD.svg 131.93 kB │ gzip: 86.07 kB\n../public/vue-assets/assets/wavy-bg-vnCaCm9m.webm 670.97 kB\n../public/vue-assets/assets/wavy-bg-Op6dRIjR.mp4 14,484.63 kB\n../public/vue-assets/assets/AppForm-EEGPL5K8.css 0.04 kB │ gzip: 0.06 kB\n../public/vue-assets/assets/RecipientsCell-DNOSS8k1.css 0.12 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/softphone-coach-Cy_9nuQZ.css 0.13 kB │ gzip: 0.10 kB\n../public/vue-assets/assets/locked-CHS8VPur.css 0.15 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/mobile-KPnGhnTp.css 0.16 kB │ gzip: 0.14 kB\n../public/vue-assets/assets/BuildInfo-CxNQv_OV.css 0.20 kB │ gzip: 0.17 kB\n../public/vue-assets/assets/AvatarsStack-C0Nvfh1V.css 0.28 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/LogoLong100-ByQTvUY3.css 0.32 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/connect--xBDzLbc.css 0.34 kB │ gzip: 0.21 kB\n../public/vue-assets/assets/KioskBanner-xU3FRd8g.css 0.41 kB │ gzip: 0.27 kB\n../public/vue-assets/assets/extension-installed-BPBhG13P.css 0.41 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/FollowModal-A0rJmIOK.css 0.45 kB │ gzip: 0.23 kB\n../public/vue-assets/assets/GenericMessage-DutAlObK.css 0.49 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/textarea-caret-DCiNUER_.css 0.51 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/AppLinks-BznfGcBR.css 0.58 kB │ gzip: 0.33 kB\n../public/vue-assets/assets/SeekBtn-_VMPIk6v.css 0.63 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/emoji-input-kJWRbWf8.css 0.68 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/activity-preview-BDxP2S0-.css 0.70 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/ListLoader-DQqAw-8s.css 0.73 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/filters-Bgb6cQD6.css 0.75 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/AiContext-Bg-zjjbA.css 0.99 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/vue3-daterange-picker-izPOBj3P.css 1.02 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/PrimaryButton-CJj2FzKS.css 1.08 kB │ gzip: 0.34 kB\n../public/vue-assets/assets/other-BTuPXtu_.css 1.15 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/jenesius-vue-modal-DDcfejHO.css 1.17 kB │ gzip: 0.48 kB\n../public/vue-assets/assets/TabEmptyState-DOlIO68k.css 1.21 kB │ gzip: 0.53 kB\n../public/vue-assets/assets/AppAlert-BlSJKiqj.css 1.29 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/useActivityCustomerName-CBjfIUfJ.css 1.42 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/UserAvatar-co8l9mMk.css 1.51 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/basic-modal-Dx7ZBbcA.css 1.55 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/login-DYbcBesM.css 1.58 kB │ gzip: 0.61 kB\n../public/vue-assets/assets/DrawerWidget-poAmqspk.css 1.67 kB │ gzip: 0.69 kB\n../public/vue-assets/assets/ActionItems-5FkZSo3G.css 1.79 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/join-conference-DQ6Yl7eU.css 1.82 kB │ gzip: 0.75 kB\n../public/vue-assets/assets/InputText-C-DxDNSV.css 1.95 kB │ gzip: 0.71 kB\n../public/vue-assets/assets/GoogleLikeButton-DvQHxbqR.css 2.11 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/usePusherEventListener-CDj8aSNp.css 2.20 kB │ gzip: 0.73 kB\n../public/vue-assets/assets/onboard-IzGiQ-EC.css 2.32 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/ai-reports-manage-DaynTl62.css 2.34 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/StatusBadge-B4N12dzU.css 2.35 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/GridView-CsP1Jk-k.css 2.36 kB │ gzip: 0.90 kB\n../public/vue-assets/assets/WelcomeLayout-6AX86p6F.css 2.38 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/snackbar-CS_iqvrv.css 2.45 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/ai-reports-mtqGqPPq.css 2.47 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/Comment-gmAfiuer.css 2.63 kB │ gzip: 1.00 kB\n../public/vue-assets/assets/meeting-consent-BasRvxE3.css 2.66 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/DealRiskList-CwxmDnSC.css 2.67 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/add-to-playlist-modal-C_ad1hht.css 2.84 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/live-_zT5qBc1.css 3.60 kB │ gzip: 1.16 kB\n../public/vue-assets/assets/RadioField-DDp8Y71G.css 3.64 kB │ gzip: 1.20 kB\n../public/vue-assets/assets/vue-multiselect-BkQNV7oL.css 3.80 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/activity-preview-result-Db1Da81T.css 3.89 kB │ gzip: 1.15 kB\n../public/vue-assets/assets/liquor-tree-CB5oh8v1.css 4.08 kB │ gzip: 1.24 kB\n../public/vue-assets/assets/sentry-CSjHcvmn.css 4.47 kB │ gzip: 1.08 kB\n../public/vue-assets/assets/AiAutomation-Cj1t8v5y.css 4.61 kB │ gzip: 0.84 kB\n../public/vue-assets/assets/export-portal-CyIUVTEe.css 5.34 kB │ gzip: 1.65 kB\n../public/vue-assets/assets/InputField-C4hoKJ7Z.css 6.13 kB │ gzip: 1.27 kB\n../public/vue-assets/assets/vue-mq-bh4L87Tr.css 6.64 kB │ gzip: 1.95 kB\n../public/vue-assets/assets/invitation-EPR1t-bH.css 6.74 kB │ gzip: 2.22 kB\n../public/vue-assets/assets/ondemand-C2m0YVGA.css 6.84 kB │ gzip: 2.02 kB\n../public/vue-assets/assets/AskAnything-CyvEhHS1.css 8.60 kB │ gzip: 2.60 kB\n../public/vue-assets/assets/AppButton-BIeoar_U.css 8.88 kB │ gzip: 2.04 kB\n../public/vue-assets/assets/kiosk-BcheyWWL.css 9.56 kB │ gzip: 2.53 kB\n../public/vue-assets/assets/AiCrmNotes-nNTe_mOY.css 10.62 kB │ gzip: 1.93 kB\n../public/vue-assets/assets/deal-view-DJzG7PLp.css 11.70 kB │ gzip: 3.29 kB\n../public/vue-assets/assets/tokens-DZgWrfKd.css 12.44 kB │ gzip: 2.82 kB\n../public/vue-assets/assets/tokens-DL2BPbai.css 12.89 kB │ gzip: 2.87 kB\n../public/vue-assets/assets/playlists-Cy3t3kYU.css 13.54 kB │ gzip: 3.16 kB\n../public/vue-assets/assets/PhoneField-D_kGOah0.css 13.75 kB │ gzip: 3.44 kB\n../public/vue-assets/assets/ListView-Dt0qkyuY.css 17.07 kB │ gzip: 4.11 kB\n../public/vue-assets/assets/intl-tel-input-DgmgTINs.css 17.11 kB │ gzip: 5.37 kB\n../public/vue-assets/assets/AppFormField-DvpfRzi8.css 18.98 kB │ gzip: 4.42 kB\n../public/vue-assets/assets/OrgSettingsLayout-BogdXtgY.css 21.98 kB │ gzip: 5.51 kB\n../public/vue-assets/assets/dashboard-DQw3TKyk.css 22.74 kB │ gzip: 4.35 kB\n../public/vue-assets/assets/deal-insights-CfX3UNzh.css 29.66 kB │ gzip: 6.44 kB\n../public/vue-assets/assets/team-insights-CjVhm0JN.css 42.31 kB │ gzip: 8.46 kB\n../public/vue-assets/assets/playback-CvJP5tX1.css 44.55 kB │ gzip: 10.36 kB\n../public/vue-assets/assets/video-js-skin-DYZluGb-.css 47.73 kB │ gzip: 12.63 kB\n../public/vue-assets/assets/assets-CAbfI4CY.css 83.38 kB │ gzip: 51.71 kB\n../public/vue-assets/assets/logged-in-layout-1mAqkmnS.css 153.67 kB │ gzip: 27.18 kB\n../public/vue-assets/assets/assets-xH6dx_9q.css 259.71 kB │ gzip: 181.71 kB\n../public/vue-assets/assets/directives-DJJeJJOP.js 0.53 kB │ gzip: 0.36 kB │ map: 0.32 kB\n../public/vue-assets/assets/jenesius-vue-modal-BuBhyl83.js 0.54 kB │ gzip: 0.37 kB │ map: 0.47 kB\n../public/vue-assets/assets/spark-D_-Wgfar.js 0.54 kB │ gzip: 0.35 kB │ map: 0.40 kB\n../public/vue-assets/assets/url-messenger-_CGQa-lH.js 0.56 kB │ gzip: 0.37 kB │ map: 0.46 kB\n../public/vue-assets/assets/wavy-bg-Cec-_GDj.js 0.57 kB │ gzip: 0.36 kB │ map: 0.35 kB\n../public/vue-assets/assets/component-css-class-CtO0AVgW.js 0.61 kB │ gzip: 0.40 kB │ map: 0.91 kB\n../public/vue-assets/assets/theme-Cy-WIInU.js 0.63 kB │ gzip: 0.42 kB │ map: 0.49 kB\n../public/vue-assets/assets/pick-BGRRe-Qj.js 0.71 kB │ gzip: 0.45 kB │ map: 1.59 kB\n../public/vue-assets/assets/utils-BjsEoLDB.js 0.77 kB │ gzip: 0.50 kB │ map: 1.68 kB\n../public/vue-assets/assets/throttle-DmtOZ8h1.js 0.78 kB │ gzip: 0.49 kB │ map: 3.24 kB\n../public/vue-assets/assets/lastFilters-CzSBGyMt.js 0.80 kB │ gzip: 0.51 kB │ map: 1.12 kB\n../public/vue-assets/assets/useAuthState-Bpx8WpBc.js 0.81 kB │ gzip: 0.50 kB │ map: 1.64 kB\n../public/vue-assets/assets/v-focus-618yf9WO.js 0.85 kB │ gzip: 0.52 kB │ map: 1.42 kB\n../public/vue-assets/assets/ListLoader-bfQmkVGu.js 0.87 kB │ gzip: 0.54 kB │ map: 2.90 kB\n../public/vue-assets/assets/utils-CAMifZzY.js 0.88 kB │ gzip: 0.53 kB │ map: 2.45 kB\n../public/vue-assets/assets/mobileApp-QFLS3kId.js 0.89 kB │ gzip: 0.54 kB │ map: 1.12 kB\n../public/vue-assets/assets/pickBy-BtwCuAYd.js 0.95 kB │ gzip: 0.59 kB │ map: 2.57 kB\n../public/vue-assets/assets/BuildInfo-CIGre86L.js 1.00 kB │ gzip: 0.65 kB │ map: 1.48 kB\n../public/vue-assets/assets/LogoShort100-D_SQdBYB.js 1.01 kB │ gzip: 0.64 kB │ map: 1.23 kB\n../public/vue-assets/assets/useDrawerModal-CmhXI-pZ.js 1.04 kB │ gzip: 0.65 kB │ map: 1.94 kB\n../public/vue-assets/assets/_getAllKeysIn-CkMQAnJa.js 1.12 kB │ gzip: 0.67 kB │ map: 4.83 kB\n../public/vue-assets/assets/useAutosizeTextarea-DyMwh_20.js 1.13 kB │ gzip: 0.70 kB │ map: 3.16 kB\n../public/vue-assets/assets/planhat-DPA36Hcn.js 1.21 kB │ gzip: 0.71 kB │ map: 2.18 kB\n../public/vue-assets/assets/useStoreModule-Cx6UoGVJ.js 1.35 kB │ gzip: 0.72 kB │ map: 6.03 kB\n../public/vue-assets/assets/GenericMessage-BxGsrfvw.js 1.39 kB │ gzip: 0.75 kB │ map: 1.90 kB\n../public/vue-assets/assets/BrowserExtensionInstaller-DimyBPIi.js 1.45 kB │ gzip: 0.78 kB │ map: 2.05 kB\n../public/vue-assets/assets/debounce-DG3xxJjJ.js 1.53 kB │ gzip: 0.83 kB │ map: 8.61 kB\n../public/vue-assets/assets/extension-installed-mV_4t574.js 1.60 kB │ gzip: 0.91 kB │ map: 3.20 kB\n../public/vue-assets/assets/RecipientsCell-BS9gDGey.js 1.61 kB │ gzip: 0.93 kB │ map: 4.04 kB\n../public/vue-assets/assets/PrimaryButton-DaKR3UAG.js 1.61 kB │ gzip: 0.87 kB │ map: 3.73 kB\n../public/vue-assets/assets/KioskBanner-g60shJd4.js 1.66 kB │ gzip: 1.00 kB │ map: 2.88 kB\n../public/vue-assets/assets/LogoLong100-Hid0kvjR.js 1.78 kB │ gzip: 1.08 kB │ map: 5.91 kB\n../public/vue-assets/assets/GoogleLikeButton-CGFy3nbz.js 1.80 kB │ gzip: 0.84 kB │ map: 4.71 kB\n../public/vue-assets/assets/AppAlert-DFp0nftl.js 1.86 kB │ gzip: 1.01 kB │ map: 6.51 kB\n../public/vue-assets/assets/usePusherEventListener-Z27Z5klt.js 1.93 kB │ gzip: 1.08 kB │ map: 9.59 kB\n../public/vue-assets/assets/AppForm-BgtH5xsM.js 2.01 kB │ gzip: 1.10 kB │ map: 6.89 kB\n../public/vue-assets/assets/vee-validate-rules-IFeGZHb3.js 2.02 kB │ gzip: 0.89 kB │ map: 22.31 kB\n../public/vue-assets/assets/literals-CXSwYC8y.js 2.05 kB │ gzip: 0.97 kB │ map: 3.08 kB\n../public/vue-assets/assets/TabEmptyState-pk8vRxJt.js 2.16 kB │ gzip: 1.12 kB │ map: 8.26 kB\n../public/vue-assets/assets/Replies-Cn_qGwMd.js 2.23 kB │ gzip: 1.14 kB │ map: 2.40 kB\n../public/vue-assets/assets/settings-BSSIkavG.js 2.34 kB │ gzip: 1.24 kB │ map: 1.73 kB\n../public/vue-assets/assets/SeekBtn-CMp8sSUA.js 2.47 kB │ gzip: 1.18 kB │ map: 9.27 kB\n../public/vue-assets/assets/AiAutomation-C5_UlocO.js 2.59 kB │ gzip: 1.37 kB │ map: 4.45 kB\n../public/vue-assets/assets/locked-Bad3U1k5.js 2.59 kB │ gzip: 1.43 kB │ map: 4.00 kB\n../public/vue-assets/assets/DrawerWidget-BPC-tCgo.js 2.68 kB │ gzip: 1.33 kB │ map: 6.95 kB\n../public/vue-assets/assets/vue-infinite-scroll-D0cI4gH6.js 2.72 kB │ gzip: 1.28 kB │ map: 9.99 kB\n../public/vue-assets/assets/useActivityCustomerName-CYuaZj-p.js 2.87 kB │ gzip: 1.41 kB │ map: 6.27 kB\n../public/vue-assets/assets/other-D11UHzKP.js 2.97 kB │ gzip: 1.51 kB │ map: 3.38 kB\n../public/vue-assets/assets/InputDropdown-CBM8xiPT.js 3.33 kB │ gzip: 1.61 kB │ map: 6.02 kB\n../public/vue-assets/assets/AvatarsStack-hxex9Ic8.js 3.40 kB │ gzip: 1.51 kB │ map: 9.54 kB\n../public/vue-assets/assets/activity-preview-CoaCdIkx.js 3.45 kB │ gzip: 1.68 kB │ map: 10.11 kB\n../public/vue-assets/assets/FollowModal-SMYvVwqG.js 3.70 kB │ gzip: 1.81 kB │ map: 8.86 kB\n../public/vue-assets/assets/softphone-coach-C452yEnj.js 3.74 kB │ gzip: 1.96 kB │ map: 5.03 kB\n../public/vue-assets/assets/store-CloJVGCY.js 4.10 kB │ gzip: 2.00 kB │ map: 18.73 kB\n../public/vue-assets/assets/AiContext-DJ4M3rJ1.js 4.34 kB │ gzip: 2.08 kB │ map: 13.11 kB\n../public/vue-assets/assets/meeting-consent-B24mqvl1.js 4.43 kB │ gzip: 2.05 kB │ map: 9.72 kB\n../public/vue-assets/assets/vue-multiselect-VB2Agtp1.js 4.49 kB │ gzip: 1.93 kB │ map: 14.85 kB\n../public/vue-assets/assets/snackbarNotifications-DjDIGkWd.js 4.83 kB │ gzip: 1.58 kB │ map: 11.62 kB\n../public/vue-assets/assets/invitation-B5F0aLwl.js 4.88 kB │ gzip: 2.21 kB │ map: 19.38 kB\n../public/vue-assets/assets/textarea-caret-B2HdIdpZ.js 5.25 kB │ gzip: 2.41 kB │ map: 14.48 kB\n../public/vue-assets/assets/connect-DvCp38Q2.js 5.31 kB │ gzip: 2.31 kB │ map: 10.62 kB\n../public/vue-assets/assets/snackbar-Bq-YQ7pz.js 5.48 kB │ gzip: 2.37 kB │ map: 20.43 kB\n../public/vue-assets/assets/RadioField-2SeSZB7D.js 5.65 kB │ gzip: 2.22 kB │ map: 15.82 kB\n../public/vue-assets/assets/filters-D_eXZdIL.js 5.76 kB │ gzip: 2.17 kB │ map: 16.36 kB\n../public/vue-assets/assets/useActivityHelper-BvWVN0nz.js 5.84 kB │ gzip: 2.51 kB │ map: 14.26 kB\n../public/vue-assets/assets/vue-scrollto-ul3pFdrb.js 5.97 kB │ gzip: 2.72 kB │ map: 23.98 kB\n../public/vue-assets/assets/join-conference-CLdXMCwD.js 6.25 kB │ gzip: 2.83 kB │ map: 24.66 kB\n../public/vue-assets/assets/pluralize-BeKVJaE0.js 6.34 kB │ gzip: 2.70 kB │ map: 18.28 kB\n../public/vue-assets/assets/login-PrWVOMsr.js 7.02 kB │ gzip: 3.17 kB │ map: 17.51 kB\n../public/vue-assets/assets/AppLinks-Bg20Hslb.js 7.14 kB │ gzip: 2.80 kB │ map: 14.87 kB\n../public/vue-assets/assets/InputField-CkdrqKBp.js 7.20 kB │ gzip: 2.58 kB │ map: 19.39 kB\n../public/vue-assets/assets/UserAvatar-UKkSAS4R.js 7.93 kB │ gzip: 3.32 kB │ map: 34.91 kB\n../public/vue-assets/assets/InputText-BH9WFuby.js 7.99 kB │ gzip: 3.05 kB │ map: 20.64 kB\n../public/vue-assets/assets/AiCrmNotes-DmPJHAz_.js 8.27 kB │ gzip: 3.32 kB │ map: 34.03 kB\n../public/vue-assets/assets/vue-mq-CmpUzQtD.js 8.84 kB │ gzip: 3.54 kB │ map: 23.69 kB\n../public/vue-assets/assets/pro-duotone-svg-icons-BSBZV3-t.js 9.51 kB │ gzip: 3.43 kB │ map: 3,146.68 kB\n../public/vue-assets/assets/activity-preview-result-CQou30yT.js 10.65 kB │ gzip: 3.98 kB │ map: 38.51 kB\n../public/vue-assets/assets/pro-regular-svg-icons-CWpSBR20.js 13.10 kB │ gzip: 4.64 kB │ map: 3,178.15 kB\n../public/vue-assets/assets/add-to-playlist-modal-BS8w3ovp.js 14.99 kB │ gzip: 5.98 kB │ map: 51.34 kB\n../public/vue-assets/assets/ai-reports-Cy_xmRg8.js 15.17 kB │ gzip: 6.04 kB │ map: 51.85 kB\n../public/vue-assets/assets/export-portal-CZ2b-FlS.js 15.68 kB │ gzip: 6.22 kB │ map: 52.78 kB\n../public/vue-assets/assets/Comment-CdvjTszI.js 15.92 kB │ gzip: 5.72 kB │ map: 39.96 kB\n../public/vue-assets/assets/useragent-DQ4F_O30.js 18.02 kB │ gzip: 8.06 kB │ map: 68.31 kB\n../public/vue-assets/assets/linkify-B_q6MgwI.js 18.58 kB │ gzip: 10.37 kB │ map: 88.73 kB\n../public/vue-assets/assets/ai-reports-manage-B1v5HdqS.js 18.92 kB │ gzip: 6.76 kB │ map: 66.14 kB\n../public/vue-assets/assets/dist-C2hisF0L.js 19.32 kB │ gzip: 7.87 kB │ map: 390.77 kB\n../public/vue-assets/assets/pro-solid-svg-icons-DBOkpln3.js 19.46 kB │ gzip: 6.72 kB │ map: 2,692.14 kB\n../public/vue-assets/assets/purify.es-BxOw6oE6.js 21.49 kB │ gzip: 8.71 kB │ map: 83.43 kB\n../public/vue-assets/assets/ActionItems-uKCSfiLo.js 21.92 kB │ gzip: 8.55 kB │ map: 76.05 kB\n../public/vue-assets/assets/mobile-zCtNIOGN.js 23.34 kB │ gzip: 9.71 kB │ map: 50.90 kB\n../public/vue-assets/assets/basic-modal-BdXsnFwC.js 23.87 kB │ gzip: 9.06 kB │ map: 64.15 kB\n../public/vue-assets/assets/GridView-D5_z7YdA.js 26.60 kB │ gzip: 10.05 kB │ map: 92.74 kB\n../public/vue-assets/assets/ondemand-mxFxJiHI.js 26.88 kB │ gzip: 9.39 kB │ map: 73.94 kB\n../public/vue-assets/assets/CrmLink-DKYsnHnx.js 27.91 kB │ gzip: 10.18 kB │ map: 93.18 kB\n../public/vue-assets/assets/liquor-tree-COUefof4.js 30.75 kB │ gzip: 9.58 kB │ map: 78.74 kB\n../public/vue-assets/assets/DealRiskList-D7gUVql5.js 34.39 kB │ gzip: 10.62 kB │ map: 115.18 kB\n../public/vue-assets/assets/AskAnything-DgI0NfRA.js 39.50 kB │ gzip: 14.97 kB │ map: 173.20 kB\n../public/vue-assets/assets/lib-CwM9toD2.js 39.69 kB │ gzip: 12.70 kB │ map: 138.34 kB\n../public/vue-assets/assets/AppFormField-BqQiLGFF.js 41.91 kB │ gzip: 12.69 kB │ map: 150.73 kB\n../public/vue-assets/assets/deal-view-D4YlPwr_.js 43.22 kB │ gzip: 14.35 kB │ map: 150.62 kB\n../public/vue-assets/assets/exports-D1lmea4O.js 47.84 kB │ gzip: 16.46 kB │ map: 294.48 kB\n../public/vue-assets/assets/playlists-BWAdERcJ.js 48.28 kB │ gzip: 15.07 kB │ map: 153.25 kB\n../public/vue-assets/assets/callScoringTemplates-zeRn40uL.js 55.13 kB │ gzip: 13.28 kB │ map: 65.85 kB\n../public/vue-assets/assets/_copyObject-USkOnlaQ.js 61.28 kB │ gzip: 20.09 kB │ map: 239.59 kB\n../public/vue-assets/assets/pusher-znYCfz7U.js 62.98 kB │ gzip: 18.88 kB │ map: 219.27 kB\n../public/vue-assets/assets/onboard-CyAPGoFk.js 63.11 kB │ gzip: 21.85 kB │ map: 201.38 kB\n../public/vue-assets/assets/StatusBadge-DNHiCr2i.js 64.66 kB │ gzip: 22.96 kB │ map: 244.72 kB\n../public/vue-assets/assets/kiosk-dfcpodo5.js 79.60 kB │ gzip: 22.65 kB │ map: 300.68 kB\n../public/vue-assets/assets/preload-helper-DCvhahzG.js 82.59 kB │ gzip: 27.46 kB │ map: 3,452.35 kB\n../public/vue-assets/assets/deal-insights-BVnPilVP.js 94.84 kB │ gzip: 28.17 kB │ map: 292.79 kB\n../public/vue-assets/assets/ListView-DJD6SV4A.js 115.71 kB │ gzip: 33.79 kB │ map: 308.10 kB\n../public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js 117.59 kB │ gzip: 38.71 kB │ map: 500.60 kB\n../public/vue-assets/assets/WelcomeLayout-B6wd32HG.js 120.67 kB │ gzip: 34.16 kB │ map: 258.56 kB\n../public/vue-assets/assets/dashboard-CsDOiLAi.js 128.71 kB │ gzip: 40.05 kB │ map: 410.48 kB\n../public/vue-assets/assets/emoji-input-CSq87OVy.js 129.28 kB │ gzip: 36.72 kB │ map: 266.15 kB\n../public/vue-assets/assets/AppButton-D3qMdODr.js 133.44 kB │ gzip: 43.03 kB │ map: 516.67 kB\n../public/vue-assets/assets/sentry-B3B1ZM6O.js 164.28 kB │ gzip: 52.24 kB │ map: 831.82 kB\n../public/vue-assets/assets/OrgSettingsLayout-DatDldIe.js 176.33 kB │ gzip: 56.15 kB │ map: 623.43 kB\n../public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js 180.40 kB │ gzip: 67.85 kB │ map: 836.88 kB\n../public/vue-assets/assets/playback-D1gm80qQ.js 198.79 kB │ gzip: 61.85 kB │ map: 684.87 kB\n../public/vue-assets/assets/index.module-Bjlhgfd1.js 218.14 kB │ gzip: 64.16 kB │ map: 1,108.20 kB\n../public/vue-assets/assets/intl-tel-input-BW4mv40Q.js 264.94 kB │ gzip: 60.31 kB │ map: 475.61 kB\n../public/vue-assets/assets/team-insights-DRugjYCA.js 298.57 kB │ gzip: 77.22 kB │ map: 959.96 kB\n../public/vue-assets/assets/popper-CQwVcrX4.js 307.13 kB │ gzip: 103.86 kB │ map: 1,245.28 kB\n../public/vue-assets/assets/PhoneField-CwCIoAYm.js 343.99 kB │ gzip: 84.90 kB │ map: 849.05 kB\n../public/vue-assets/assets/live-CxSmZv7h.js 367.43 kB │ gzip: 97.05 kB │ map: 792.41 kB\n../public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BNO485xV.js 689.63 kB │ gzip: 202.81 kB │ map: 3,016.64 kB\n../public/vue-assets/assets/index-Cp0YOK4U.js 825.23 kB │ gzip: 72.54 kB │ map: 436.62 kB\n../public/vue-assets/assets/logged-in-layout-CE9ox17M.js 1,402.70 kB │ gzip: 438.07 kB │ map: 6,283.55 kB\n\n✓ built in 26.01s\n[plugin builtin:vite-reporter] \n(!) Some chunks are larger than 500 kB after minification. Consider:\n- Using dynamic import() to code-split the application\n- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting\n- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $","depth":4,"value":"info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ nvm use 24\nNow using node v24.11.1 (npm v11.6.2)\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yanr build\nzsh: command not found: yanr\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yanrn build\nzsh: command not found: yanrn\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yarn build \n[dotenv@17.3.1] injecting env (0) from .env -- tip: ⚡️\u0000 secrets for agents: https://dotenvx.com/as2\nvite v8.0.0 building client environment for production...\n✓ 4656 modules transformed.\n[sentry-vite-plugin] Warning: No auth token provided. Will not create release. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\n[sentry-vite-plugin] Warning: No auth token provided. Will not upload source maps. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\ncomputing gzip size...\n../public/vue-assets/index.html 4.58 kB │ gzip: 1.15 kB\n../public/vue-assets/assets/job-adder-B2LcgC4o.png 5.41 kB\n../public/vue-assets/assets/dixa-DzT89tKh.png 7.87 kB\n../public/vue-assets/assets/planhat-CQycOTMW.svg 8.61 kB │ gzip: 3.48 kB\n../public/vue-assets/assets/funding-circle-9iGnDGsz.png 9.43 kB\n../public/vue-assets/assets/cision-uOdx_YiN.svg 14.41 kB │ gzip: 6.32 kB\n../public/vue-assets/assets/les-mills-DWabpmYc.png 15.73 kB\n../public/vue-assets/assets/superside-btT37L9L.png 26.69 kB\n../public/vue-assets/assets/flags-a2kmUSbF.webp 28.18 kB\n../public/vue-assets/assets/quinyx-RGQNHx6o.png 63.17 kB\n../public/vue-assets/assets/flags@2x-gR6KPp3x.webp 66.44 kB\n../public/vue-assets/assets/bg-marketing-CAIHIJMj.png 70.66 kB\n../public/vue-assets/.vite/manifest.json 85.73 kB │ gzip: 8.33 kB\n../public/vue-assets/assets/jiminny-score-DtTbKZaD.svg 131.93 kB │ gzip: 86.07 kB\n../public/vue-assets/assets/wavy-bg-vnCaCm9m.webm 670.97 kB\n../public/vue-assets/assets/wavy-bg-Op6dRIjR.mp4 14,484.63 kB\n../public/vue-assets/assets/AppForm-EEGPL5K8.css 0.04 kB │ gzip: 0.06 kB\n../public/vue-assets/assets/RecipientsCell-DNOSS8k1.css 0.12 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/softphone-coach-Cy_9nuQZ.css 0.13 kB │ gzip: 0.10 kB\n../public/vue-assets/assets/locked-CHS8VPur.css 0.15 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/mobile-KPnGhnTp.css 0.16 kB │ gzip: 0.14 kB\n../public/vue-assets/assets/BuildInfo-CxNQv_OV.css 0.20 kB │ gzip: 0.17 kB\n../public/vue-assets/assets/AvatarsStack-C0Nvfh1V.css 0.28 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/LogoLong100-ByQTvUY3.css 0.32 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/connect--xBDzLbc.css 0.34 kB │ gzip: 0.21 kB\n../public/vue-assets/assets/KioskBanner-xU3FRd8g.css 0.41 kB │ gzip: 0.27 kB\n../public/vue-assets/assets/extension-installed-BPBhG13P.css 0.41 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/FollowModal-A0rJmIOK.css 0.45 kB │ gzip: 0.23 kB\n../public/vue-assets/assets/GenericMessage-DutAlObK.css 0.49 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/textarea-caret-DCiNUER_.css 0.51 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/AppLinks-BznfGcBR.css 0.58 kB │ gzip: 0.33 kB\n../public/vue-assets/assets/SeekBtn-_VMPIk6v.css 0.63 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/emoji-input-kJWRbWf8.css 0.68 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/activity-preview-BDxP2S0-.css 0.70 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/ListLoader-DQqAw-8s.css 0.73 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/filters-Bgb6cQD6.css 0.75 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/AiContext-Bg-zjjbA.css 0.99 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/vue3-daterange-picker-izPOBj3P.css 1.02 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/PrimaryButton-CJj2FzKS.css 1.08 kB │ gzip: 0.34 kB\n../public/vue-assets/assets/other-BTuPXtu_.css 1.15 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/jenesius-vue-modal-DDcfejHO.css 1.17 kB │ gzip: 0.48 kB\n../public/vue-assets/assets/TabEmptyState-DOlIO68k.css 1.21 kB │ gzip: 0.53 kB\n../public/vue-assets/assets/AppAlert-BlSJKiqj.css 1.29 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/useActivityCustomerName-CBjfIUfJ.css 1.42 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/UserAvatar-co8l9mMk.css 1.51 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/basic-modal-Dx7ZBbcA.css 1.55 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/login-DYbcBesM.css 1.58 kB │ gzip: 0.61 kB\n../public/vue-assets/assets/DrawerWidget-poAmqspk.css 1.67 kB │ gzip: 0.69 kB\n../public/vue-assets/assets/ActionItems-5FkZSo3G.css 1.79 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/join-conference-DQ6Yl7eU.css 1.82 kB │ gzip: 0.75 kB\n../public/vue-assets/assets/InputText-C-DxDNSV.css 1.95 kB │ gzip: 0.71 kB\n../public/vue-assets/assets/GoogleLikeButton-DvQHxbqR.css 2.11 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/usePusherEventListener-CDj8aSNp.css 2.20 kB │ gzip: 0.73 kB\n../public/vue-assets/assets/onboard-IzGiQ-EC.css 2.32 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/ai-reports-manage-DaynTl62.css 2.34 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/StatusBadge-B4N12dzU.css 2.35 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/GridView-CsP1Jk-k.css 2.36 kB │ gzip: 0.90 kB\n../public/vue-assets/assets/WelcomeLayout-6AX86p6F.css 2.38 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/snackbar-CS_iqvrv.css 2.45 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/ai-reports-mtqGqPPq.css 2.47 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/Comment-gmAfiuer.css 2.63 kB │ gzip: 1.00 kB\n../public/vue-assets/assets/meeting-consent-BasRvxE3.css 2.66 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/DealRiskList-CwxmDnSC.css 2.67 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/add-to-playlist-modal-C_ad1hht.css 2.84 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/live-_zT5qBc1.css 3.60 kB │ gzip: 1.16 kB\n../public/vue-assets/assets/RadioField-DDp8Y71G.css 3.64 kB │ gzip: 1.20 kB\n../public/vue-assets/assets/vue-multiselect-BkQNV7oL.css 3.80 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/activity-preview-result-Db1Da81T.css 3.89 kB │ gzip: 1.15 kB\n../public/vue-assets/assets/liquor-tree-CB5oh8v1.css 4.08 kB │ gzip: 1.24 kB\n../public/vue-assets/assets/sentry-CSjHcvmn.css 4.47 kB │ gzip: 1.08 kB\n../public/vue-assets/assets/AiAutomation-Cj1t8v5y.css 4.61 kB │ gzip: 0.84 kB\n../public/vue-assets/assets/export-portal-CyIUVTEe.css 5.34 kB │ gzip: 1.65 kB\n../public/vue-assets/assets/InputField-C4hoKJ7Z.css 6.13 kB │ gzip: 1.27 kB\n../public/vue-assets/assets/vue-mq-bh4L87Tr.css 6.64 kB │ gzip: 1.95 kB\n../public/vue-assets/assets/invitation-EPR1t-bH.css 6.74 kB │ gzip: 2.22 kB\n../public/vue-assets/assets/ondemand-C2m0YVGA.css 6.84 kB │ gzip: 2.02 kB\n../public/vue-assets/assets/AskAnything-CyvEhHS1.css 8.60 kB │ gzip: 2.60 kB\n../public/vue-assets/assets/AppButton-BIeoar_U.css 8.88 kB │ gzip: 2.04 kB\n../public/vue-assets/assets/kiosk-BcheyWWL.css 9.56 kB │ gzip: 2.53 kB\n../public/vue-assets/assets/AiCrmNotes-nNTe_mOY.css 10.62 kB │ gzip: 1.93 kB\n../public/vue-assets/assets/deal-view-DJzG7PLp.css 11.70 kB │ gzip: 3.29 kB\n../public/vue-assets/assets/tokens-B6_hRKul.css 12.95 kB │ gzip: 2.92 kB\n../public/vue-assets/assets/tokens-CoAAv8do.css 13.41 kB │ gzip: 2.97 kB\n../public/vue-assets/assets/playlists-Cy3t3kYU.css 13.54 kB │ gzip: 3.16 kB\n../public/vue-assets/assets/PhoneField-D_kGOah0.css 13.75 kB │ gzip: 3.44 kB\n../public/vue-assets/assets/ListView-Dt0qkyuY.css 17.07 kB │ gzip: 4.11 kB\n../public/vue-assets/assets/intl-tel-input-DgmgTINs.css 17.11 kB │ gzip: 5.37 kB\n../public/vue-assets/assets/AppFormField-DvpfRzi8.css 18.98 kB │ gzip: 4.42 kB\n../public/vue-assets/assets/OrgSettingsLayout-BogdXtgY.css 21.98 kB │ gzip: 5.51 kB\n../public/vue-assets/assets/dashboard-DQw3TKyk.css 22.74 kB │ gzip: 4.35 kB\n../public/vue-assets/assets/deal-insights-CfX3UNzh.css 29.66 kB │ gzip: 6.44 kB\n../public/vue-assets/assets/team-insights-CjVhm0JN.css 42.31 kB │ gzip: 8.46 kB\n../public/vue-assets/assets/playback-CvJP5tX1.css 44.55 kB │ gzip: 10.36 kB\n../public/vue-assets/assets/video-js-skin-DYZluGb-.css 47.73 kB │ gzip: 12.63 kB\n../public/vue-assets/assets/assets-CAbfI4CY.css 83.38 kB │ gzip: 51.71 kB\n../public/vue-assets/assets/logged-in-layout-1mAqkmnS.css 153.67 kB │ gzip: 27.18 kB\n../public/vue-assets/assets/assets-xH6dx_9q.css 259.71 kB │ gzip: 181.71 kB\n../public/vue-assets/assets/directives-DJJeJJOP.js 0.53 kB │ gzip: 0.36 kB │ map: 0.32 kB\n../public/vue-assets/assets/jenesius-vue-modal-BuBhyl83.js 0.54 kB │ gzip: 0.37 kB │ map: 0.47 kB\n../public/vue-assets/assets/spark-D_-Wgfar.js 0.54 kB │ gzip: 0.35 kB │ map: 0.40 kB\n../public/vue-assets/assets/url-messenger-_CGQa-lH.js 0.56 kB │ gzip: 0.37 kB │ map: 0.46 kB\n../public/vue-assets/assets/wavy-bg-Cec-_GDj.js 0.57 kB │ gzip: 0.36 kB │ map: 0.35 kB\n../public/vue-assets/assets/component-css-class-CtO0AVgW.js 0.61 kB │ gzip: 0.40 kB │ map: 0.91 kB\n../public/vue-assets/assets/theme-CrLnsUSQ.js 0.63 kB │ gzip: 0.41 kB │ map: 0.49 kB\n../public/vue-assets/assets/pick-jGxSsmQW.js 0.71 kB │ gzip: 0.45 kB │ map: 1.59 kB\n../public/vue-assets/assets/utils-BjsEoLDB.js 0.77 kB │ gzip: 0.50 kB │ map: 1.68 kB\n../public/vue-assets/assets/throttle-DE_etUX9.js 0.78 kB │ gzip: 0.49 kB │ map: 3.24 kB\n../public/vue-assets/assets/lastFilters-CjyI5phg.js 0.80 kB │ gzip: 0.51 kB │ map: 1.12 kB\n../public/vue-assets/assets/useAuthState-Bpx8WpBc.js 0.81 kB │ gzip: 0.50 kB │ map: 1.64 kB\n../public/vue-assets/assets/v-focus-618yf9WO.js 0.85 kB │ gzip: 0.52 kB │ map: 1.42 kB\n../public/vue-assets/assets/ListLoader-bfQmkVGu.js 0.87 kB │ gzip: 0.54 kB │ map: 2.90 kB\n../public/vue-assets/assets/utils-CAMifZzY.js 0.88 kB │ gzip: 0.53 kB │ map: 2.45 kB\n../public/vue-assets/assets/mobileApp-QFLS3kId.js 0.89 kB │ gzip: 0.54 kB │ map: 1.12 kB\n../public/vue-assets/assets/pickBy-DV-Svw8z.js 0.95 kB │ gzip: 0.59 kB │ map: 2.57 kB\n../public/vue-assets/assets/BuildInfo-CIGre86L.js 1.00 kB │ gzip: 0.65 kB │ map: 1.48 kB\n../public/vue-assets/assets/LogoShort100-D_SQdBYB.js 1.01 kB │ gzip: 0.64 kB │ map: 1.23 kB\n../public/vue-assets/assets/useDrawerModal-CmhXI-pZ.js 1.04 kB │ gzip: 0.65 kB │ map: 1.94 kB\n../public/vue-assets/assets/_getAllKeysIn-CkMQAnJa.js 1.12 kB │ gzip: 0.67 kB │ map: 4.83 kB\n../public/vue-assets/assets/useAutosizeTextarea-DyMwh_20.js 1.13 kB │ gzip: 0.70 kB │ map: 3.16 kB\n../public/vue-assets/assets/planhat-DPA36Hcn.js 1.21 kB │ gzip: 0.71 kB │ map: 2.18 kB\n../public/vue-assets/assets/useStoreModule-Cx6UoGVJ.js 1.35 kB │ gzip: 0.72 kB │ map: 6.03 kB\n../public/vue-assets/assets/GenericMessage-BxGsrfvw.js 1.39 kB │ gzip: 0.75 kB │ map: 1.90 kB\n../public/vue-assets/assets/BrowserExtensionInstaller-DimyBPIi.js 1.45 kB │ gzip: 0.78 kB │ map: 2.05 kB\n../public/vue-assets/assets/debounce-C6V4-Yml.js 1.53 kB │ gzip: 0.83 kB │ map: 8.61 kB\n../public/vue-assets/assets/extension-installed-C_yEm2e-.js 1.60 kB │ gzip: 0.91 kB │ map: 3.20 kB\n../public/vue-assets/assets/RecipientsCell-BS9gDGey.js 1.61 kB │ gzip: 0.93 kB │ map: 4.04 kB\n../public/vue-assets/assets/PrimaryButton-DaKR3UAG.js 1.61 kB │ gzip: 0.87 kB │ map: 3.73 kB\n../public/vue-assets/assets/KioskBanner-DyDQY_lm.js 1.66 kB │ gzip: 1.00 kB │ map: 2.88 kB\n../public/vue-assets/assets/LogoLong100-Hid0kvjR.js 1.78 kB │ gzip: 1.08 kB │ map: 5.91 kB\n../public/vue-assets/assets/GoogleLikeButton-CGFy3nbz.js 1.80 kB │ gzip: 0.84 kB │ map: 4.71 kB\n../public/vue-assets/assets/AppAlert-DFp0nftl.js 1.86 kB │ gzip: 1.01 kB │ map: 6.51 kB\n../public/vue-assets/assets/usePusherEventListener-DRJ7JhID.js 1.93 kB │ gzip: 1.08 kB │ map: 9.59 kB\n../public/vue-assets/assets/AppForm-BgtH5xsM.js 2.01 kB │ gzip: 1.10 kB │ map: 6.89 kB\n../public/vue-assets/assets/vee-validate-rules-IFeGZHb3.js 2.02 kB │ gzip: 0.89 kB │ map: 22.31 kB\n../public/vue-assets/assets/literals-CXSwYC8y.js 2.05 kB │ gzip: 0.97 kB │ map: 3.08 kB\n../public/vue-assets/assets/TabEmptyState-pk8vRxJt.js 2.16 kB │ gzip: 1.12 kB │ map: 8.26 kB\n../public/vue-assets/assets/Replies-BrhP0P7D.js 2.23 kB │ gzip: 1.14 kB │ map: 2.40 kB\n../public/vue-assets/assets/settings-DWZF-G8N.js 2.34 kB │ gzip: 1.23 kB │ map: 1.73 kB\n../public/vue-assets/assets/SeekBtn-CMp8sSUA.js 2.47 kB │ gzip: 1.18 kB │ map: 9.27 kB\n../public/vue-assets/assets/AiAutomation-CnVa_KZl.js 2.59 kB │ gzip: 1.37 kB │ map: 4.45 kB\n../public/vue-assets/assets/locked-B2ThxTAd.js 2.59 kB │ gzip: 1.42 kB │ map: 4.00 kB\n../public/vue-assets/assets/DrawerWidget-BPC-tCgo.js 2.68 kB │ gzip: 1.33 kB │ map: 6.95 kB\n../public/vue-assets/assets/vue-infinite-scroll-D0cI4gH6.js 2.72 kB │ gzip: 1.28 kB │ map: 9.99 kB\n../public/vue-assets/assets/useActivityCustomerName-CYuaZj-p.js 2.87 kB │ gzip: 1.41 kB │ map: 6.27 kB\n../public/vue-assets/assets/other-k3In5q1l.js 2.97 kB │ gzip: 1.50 kB │ map: 3.38 kB\n../public/vue-assets/assets/InputDropdown-CBM8xiPT.js 3.33 kB │ gzip: 1.61 kB │ map: 6.02 kB\n../public/vue-assets/assets/AvatarsStack-hxex9Ic8.js 3.40 kB │ gzip: 1.51 kB │ map: 9.54 kB\n../public/vue-assets/assets/activity-preview-BZrDAfqV.js 3.45 kB │ gzip: 1.69 kB │ map: 10.11 kB\n../public/vue-assets/assets/FollowModal-SMYvVwqG.js 3.70 kB │ gzip: 1.81 kB │ map: 8.86 kB\n../public/vue-assets/assets/softphone-coach-DJLlDZUr.js 3.74 kB │ gzip: 1.96 kB │ map: 5.03 kB\n../public/vue-assets/assets/store-DdBy-CTd.js 4.10 kB │ gzip: 2.00 kB │ map: 18.73 kB\n../public/vue-assets/assets/AiContext-BYAvUsG9.js 4.34 kB │ gzip: 2.08 kB │ map: 13.11 kB\n../public/vue-assets/assets/meeting-consent-EdnBkMBr.js 4.43 kB │ gzip: 2.04 kB │ map: 9.72 kB\n../public/vue-assets/assets/vue-multiselect-VB2Agtp1.js 4.49 kB │ gzip: 1.93 kB │ map: 14.85 kB\n../public/vue-assets/assets/snackbarNotifications-DjDIGkWd.js 4.83 kB │ gzip: 1.58 kB │ map: 11.62 kB\n../public/vue-assets/assets/invitation-B1SNfWGY.js 4.88 kB │ gzip: 2.21 kB │ map: 19.38 kB\n../public/vue-assets/assets/connect-DzHqvqqR.js 5.17 kB │ gzip: 2.28 kB │ map: 10.35 kB\n../public/vue-assets/assets/textarea-caret-B2HdIdpZ.js 5.25 kB │ gzip: 2.41 kB │ map: 14.48 kB\n../public/vue-assets/assets/snackbar-Bq-YQ7pz.js 5.48 kB │ gzip: 2.37 kB │ map: 20.43 kB\n../public/vue-assets/assets/RadioField-2SeSZB7D.js 5.65 kB │ gzip: 2.22 kB │ map: 15.82 kB\n../public/vue-assets/assets/filters-D_eXZdIL.js 5.76 kB │ gzip: 2.17 kB │ map: 16.36 kB\n../public/vue-assets/assets/useActivityHelper-DbifQcp6.js 5.84 kB │ gzip: 2.51 kB │ map: 14.26 kB\n../public/vue-assets/assets/vue-scrollto-ul3pFdrb.js 5.97 kB │ gzip: 2.72 kB │ map: 23.98 kB\n../public/vue-assets/assets/join-conference-CYJkDTD8.js 6.25 kB │ gzip: 2.82 kB │ map: 24.66 kB\n../public/vue-assets/assets/pluralize-BeKVJaE0.js 6.34 kB │ gzip: 2.70 kB │ map: 18.28 kB\n../public/vue-assets/assets/login-DkwzcZhV.js 7.02 kB │ gzip: 3.16 kB │ map: 17.51 kB\n../public/vue-assets/assets/AppLinks-Bg20Hslb.js 7.14 kB │ gzip: 2.80 kB │ map: 14.87 kB\n../public/vue-assets/assets/InputField-CkdrqKBp.js 7.20 kB │ gzip: 2.58 kB │ map: 19.39 kB\n../public/vue-assets/assets/UserAvatar-UKkSAS4R.js 7.93 kB │ gzip: 3.32 kB │ map: 34.91 kB\n../public/vue-assets/assets/InputText-CiBJ3oHz.js 7.99 kB │ gzip: 3.05 kB │ map: 20.64 kB\n../public/vue-assets/assets/AiCrmNotes-BZWjw9pN.js 8.27 kB │ gzip: 3.32 kB │ map: 34.03 kB\n../public/vue-assets/assets/vue-mq-CmpUzQtD.js 8.84 kB │ gzip: 3.54 kB │ map: 23.69 kB\n../public/vue-assets/assets/pro-duotone-svg-icons-BSBZV3-t.js 9.51 kB │ gzip: 3.43 kB │ map: 3,146.68 kB\n../public/vue-assets/assets/activity-preview-result-DLO6BXq1.js 10.65 kB │ gzip: 3.98 kB │ map: 38.51 kB\n../public/vue-assets/assets/pro-regular-svg-icons-CWpSBR20.js 13.10 kB │ gzip: 4.64 kB │ map: 3,178.15 kB\n../public/vue-assets/assets/add-to-playlist-modal-prxYCVfk.js 14.99 kB │ gzip: 5.98 kB │ map: 51.34 kB\n../public/vue-assets/assets/ai-reports-B3i0pft-.js 15.17 kB │ gzip: 6.03 kB │ map: 51.85 kB\n../public/vue-assets/assets/export-portal-54ZutGpG.js 15.68 kB │ gzip: 6.22 kB │ map: 52.78 kB\n../public/vue-assets/assets/Comment-BpW-5w7g.js 15.92 kB │ gzip: 5.72 kB │ map: 39.96 kB\n../public/vue-assets/assets/useragent-DQ4F_O30.js 18.02 kB │ gzip: 8.06 kB │ map: 68.31 kB\n../public/vue-assets/assets/linkify-B_q6MgwI.js 18.58 kB │ gzip: 10.37 kB │ map: 88.73 kB\n../public/vue-assets/assets/ai-reports-manage-Ber3EVyb.js 18.92 kB │ gzip: 6.75 kB │ map: 66.14 kB\n../public/vue-assets/assets/dist-C2hisF0L.js 19.32 kB │ gzip: 7.87 kB │ map: 390.77 kB\n../public/vue-assets/assets/pro-solid-svg-icons-DBOkpln3.js 19.46 kB │ gzip: 6.72 kB │ map: 2,692.14 kB\n../public/vue-assets/assets/purify.es-BxOw6oE6.js 21.49 kB │ gzip: 8.71 kB │ map: 83.43 kB\n../public/vue-assets/assets/ActionItems-BuNWx0BA.js 21.92 kB │ gzip: 8.54 kB │ map: 76.05 kB\n../public/vue-assets/assets/mobile-BMtylYi6.js 23.34 kB │ gzip: 9.71 kB │ map: 50.90 kB\n../public/vue-assets/assets/basic-modal-BdXsnFwC.js 23.87 kB │ gzip: 9.06 kB │ map: 64.15 kB\n../public/vue-assets/assets/GridView-CP7J7-qo.js 26.60 kB │ gzip: 10.05 kB │ map: 92.74 kB\n../public/vue-assets/assets/ondemand-kg1j3nIA.js 26.88 kB │ gzip: 9.39 kB │ map: 73.94 kB\n../public/vue-assets/assets/CrmLink-DKYsnHnx.js 27.91 kB │ gzip: 10.18 kB │ map: 93.18 kB\n../public/vue-assets/assets/liquor-tree-COUefof4.js 30.75 kB │ gzip: 9.58 kB │ map: 78.74 kB\n../public/vue-assets/assets/DealRiskList-C8q7faXl.js 34.39 kB │ gzip: 10.62 kB │ map: 115.18 kB\n../public/vue-assets/assets/AskAnything-BgSY9SKi.js 39.50 kB │ gzip: 14.97 kB │ map: 173.20 kB\n../public/vue-assets/assets/lib-CwM9toD2.js 39.69 kB │ gzip: 12.70 kB │ map: 138.34 kB\n../public/vue-assets/assets/AppFormField-COtjyq5c.js 41.91 kB │ gzip: 12.69 kB │ map: 150.73 kB\n../public/vue-assets/assets/deal-view-bksUVN5E.js 43.22 kB │ gzip: 14.34 kB │ map: 150.62 kB\n../public/vue-assets/assets/exports-D1lmea4O.js 47.84 kB │ gzip: 16.46 kB │ map: 294.48 kB\n../public/vue-assets/assets/playlists-Yn3gKP2z.js 48.28 kB │ gzip: 15.07 kB │ map: 153.25 kB\n../public/vue-assets/assets/callScoringTemplates-zeRn40uL.js 55.13 kB │ gzip: 13.28 kB │ map: 65.85 kB\n../public/vue-assets/assets/_copyObject-USkOnlaQ.js 61.28 kB │ gzip: 20.09 kB │ map: 239.59 kB\n../public/vue-assets/assets/pusher-znYCfz7U.js 62.98 kB │ gzip: 18.88 kB │ map: 219.27 kB\n../public/vue-assets/assets/onboard-BqxRx4zw.js 63.11 kB │ gzip: 21.85 kB │ map: 201.38 kB\n../public/vue-assets/assets/StatusBadge-C2H2xj9g.js 64.66 kB │ gzip: 22.96 kB │ map: 244.72 kB\n../public/vue-assets/assets/kiosk-BDlz4mpq.js 79.60 kB │ gzip: 22.65 kB │ map: 300.68 kB\n../public/vue-assets/assets/preload-helper-DCvhahzG.js 82.59 kB │ gzip: 27.46 kB │ map: 3,452.35 kB\n../public/vue-assets/assets/deal-insights-BnVEi9yc.js 94.84 kB │ gzip: 28.16 kB │ map: 292.79 kB\n../public/vue-assets/assets/ListView-CN2ZtlC7.js 115.71 kB │ gzip: 33.78 kB │ map: 308.10 kB\n../public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js 117.59 kB │ gzip: 38.71 kB │ map: 500.60 kB\n../public/vue-assets/assets/WelcomeLayout-B6wd32HG.js 120.67 kB │ gzip: 34.16 kB │ map: 258.56 kB\n../public/vue-assets/assets/dashboard-XCNXJG27.js 128.71 kB │ gzip: 40.05 kB │ map: 410.48 kB\n../public/vue-assets/assets/emoji-input-CSq87OVy.js 129.28 kB │ gzip: 36.72 kB │ map: 266.15 kB\n../public/vue-assets/assets/AppButton-D3qMdODr.js 133.44 kB │ gzip: 43.03 kB │ map: 516.67 kB\n../public/vue-assets/assets/sentry-DMzthP2u.js 164.28 kB │ gzip: 52.24 kB │ map: 831.82 kB\n../public/vue-assets/assets/OrgSettingsLayout-DYzOhe6q.js 176.33 kB │ gzip: 56.15 kB │ map: 623.43 kB\n../public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js 180.40 kB │ gzip: 67.85 kB │ map: 836.88 kB\n../public/vue-assets/assets/playback-DRpCBZO9.js 198.79 kB │ gzip: 61.85 kB │ map: 684.87 kB\n../public/vue-assets/assets/index.module-Bjlhgfd1.js 218.14 kB │ gzip: 64.16 kB │ map: 1,108.20 kB\n../public/vue-assets/assets/intl-tel-input-BW4mv40Q.js 264.94 kB │ gzip: 60.31 kB │ map: 475.61 kB\n../public/vue-assets/assets/team-insights-CPpPTAKV.js 298.57 kB │ gzip: 77.22 kB │ map: 959.96 kB\n../public/vue-assets/assets/popper-CQwVcrX4.js 307.13 kB │ gzip: 103.86 kB │ map: 1,245.28 kB\n../public/vue-assets/assets/PhoneField-CwCIoAYm.js 343.99 kB │ gzip: 84.90 kB │ map: 849.05 kB\n../public/vue-assets/assets/live-Cfb59XkF.js 367.43 kB │ gzip: 97.05 kB │ map: 792.41 kB\n../public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BNO485xV.js 689.63 kB │ gzip: 202.81 kB │ map: 3,016.64 kB\n../public/vue-assets/assets/index-1Grk9P2e.js 825.23 kB │ gzip: 72.55 kB │ map: 436.62 kB\n../public/vue-assets/assets/logged-in-layout-DArs1VTa.js 1,402.70 kB │ gzip: 438.08 kB │ map: 6,283.55 kB\n\n[PLUGIN_TIMINGS] Warning: Your build spent significant time in plugins. Here is a breakdown:\n - vite:css (61%)\n - vite-plugin-externals (12%)\n - vite:vue (9%)\n - sentry-vite-plugin (8%)\n - vite:worker (6%)\nSee https://rolldown.rs/options/checks#plugintimings for more details.\n\n[plugin builtin:vite-reporter] \n(!) Some chunks are larger than 500 kB after minification. Consider:\n- Using dynamic import() to code-split the application\n- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting\n- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.\n✓ built in 22.57s\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yarn install\n➤ YN0000: · Yarn 4.12.0\n➤ YN0000: ┌ Resolution step\n➤ YN0000: └ Completed\n➤ YN0000: ┌ Post-resolution validation\n➤ YN0002: │ root-workspace-0b6124@workspace:. doesn't provide @testing-library/dom (pea6ced), requested by @testing-library/user-event.\n➤ YN0002: │ root-workspace-0b6124@workspace:. doesn't provide noty (p5bdb0d), requested by vue-components.\n➤ YN0086: │ Some peer dependencies are incorrectly met by your project; run yarn explain peer-requirements <hash> for details, where <hash> is the six-letter p-prefixed code.\n➤ YN0086: │ Some peer dependencies are incorrectly met by dependencies; run yarn explain peer-requirements for details.\n➤ YN0000: └ Completed\n➤ YN0000: ┌ Fetch step\n➤ YN0000: └ Completed in 1s 655ms\n➤ YN0000: ┌ Link step\n➤ YN0008: │ root-workspace-0b6124@workspace:. must be rebuilt because its dependency tree changed\n➤ YN0000: └ Completed in 0s 969ms\n➤ YN0000: · Done with warnings in 3s 14ms\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yarn build\n[dotenv@17.3.1] injecting env (0) from .env -- tip: ⚙️\u0000 override existing env vars with { override: true }\nvite v8.0.0 building client environment for production...\n✓ 4656 modules transformed.\n[sentry-vite-plugin] Warning: No auth token provided. Will not create release. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\n[sentry-vite-plugin] Warning: No auth token provided. Will not upload source maps. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\ncomputing gzip size...\n../public/vue-assets/index.html 4.58 kB │ gzip: 1.15 kB\n../public/vue-assets/assets/job-adder-B2LcgC4o.png 5.41 kB\n../public/vue-assets/assets/dixa-DzT89tKh.png 7.87 kB\n../public/vue-assets/assets/planhat-CQycOTMW.svg 8.61 kB │ gzip: 3.48 kB\n../public/vue-assets/assets/funding-circle-9iGnDGsz.png 9.43 kB\n../public/vue-assets/assets/cision-uOdx_YiN.svg 14.41 kB │ gzip: 6.32 kB\n../public/vue-assets/assets/les-mills-DWabpmYc.png 15.73 kB\n../public/vue-assets/assets/superside-btT37L9L.png 26.69 kB\n../public/vue-assets/assets/flags-a2kmUSbF.webp 28.18 kB\n../public/vue-assets/assets/quinyx-RGQNHx6o.png 63.17 kB\n../public/vue-assets/assets/flags@2x-gR6KPp3x.webp 66.44 kB\n../public/vue-assets/assets/bg-marketing-CAIHIJMj.png 70.66 kB\n../public/vue-assets/.vite/manifest.json 85.73 kB │ gzip: 8.32 kB\n../public/vue-assets/assets/jiminny-score-DtTbKZaD.svg 131.93 kB │ gzip: 86.07 kB\n../public/vue-assets/assets/wavy-bg-vnCaCm9m.webm 670.97 kB\n../public/vue-assets/assets/wavy-bg-Op6dRIjR.mp4 14,484.63 kB\n../public/vue-assets/assets/AppForm-EEGPL5K8.css 0.04 kB │ gzip: 0.06 kB\n../public/vue-assets/assets/RecipientsCell-DNOSS8k1.css 0.12 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/softphone-coach-Cy_9nuQZ.css 0.13 kB │ gzip: 0.10 kB\n../public/vue-assets/assets/locked-CHS8VPur.css 0.15 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/mobile-KPnGhnTp.css 0.16 kB │ gzip: 0.14 kB\n../public/vue-assets/assets/BuildInfo-CxNQv_OV.css 0.20 kB │ gzip: 0.17 kB\n../public/vue-assets/assets/AvatarsStack-C0Nvfh1V.css 0.28 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/LogoLong100-ByQTvUY3.css 0.32 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/connect--xBDzLbc.css 0.34 kB │ gzip: 0.21 kB\n../public/vue-assets/assets/KioskBanner-xU3FRd8g.css 0.41 kB │ gzip: 0.27 kB\n../public/vue-assets/assets/extension-installed-BPBhG13P.css 0.41 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/FollowModal-A0rJmIOK.css 0.45 kB │ gzip: 0.23 kB\n../public/vue-assets/assets/GenericMessage-DutAlObK.css 0.49 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/textarea-caret-DCiNUER_.css 0.51 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/AppLinks-BznfGcBR.css 0.58 kB │ gzip: 0.33 kB\n../public/vue-assets/assets/SeekBtn-_VMPIk6v.css 0.63 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/emoji-input-kJWRbWf8.css 0.68 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/activity-preview-BDxP2S0-.css 0.70 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/ListLoader-DQqAw-8s.css 0.73 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/filters-Bgb6cQD6.css 0.75 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/AiContext-Bg-zjjbA.css 0.99 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/vue3-daterange-picker-izPOBj3P.css 1.02 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/PrimaryButton-CJj2FzKS.css 1.08 kB │ gzip: 0.34 kB\n../public/vue-assets/assets/other-BTuPXtu_.css 1.15 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/jenesius-vue-modal-DDcfejHO.css 1.17 kB │ gzip: 0.48 kB\n../public/vue-assets/assets/TabEmptyState-DOlIO68k.css 1.21 kB │ gzip: 0.53 kB\n../public/vue-assets/assets/AppAlert-BlSJKiqj.css 1.29 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/useActivityCustomerName-CBjfIUfJ.css 1.42 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/UserAvatar-co8l9mMk.css 1.51 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/basic-modal-Dx7ZBbcA.css 1.55 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/login-DYbcBesM.css 1.58 kB │ gzip: 0.61 kB\n../public/vue-assets/assets/DrawerWidget-poAmqspk.css 1.67 kB │ gzip: 0.69 kB\n../public/vue-assets/assets/ActionItems-5FkZSo3G.css 1.79 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/join-conference-DQ6Yl7eU.css 1.82 kB │ gzip: 0.75 kB\n../public/vue-assets/assets/InputText-C-DxDNSV.css 1.95 kB │ gzip: 0.71 kB\n../public/vue-assets/assets/GoogleLikeButton-DvQHxbqR.css 2.11 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/usePusherEventListener-CDj8aSNp.css 2.20 kB │ gzip: 0.73 kB\n../public/vue-assets/assets/onboard-IzGiQ-EC.css 2.32 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/ai-reports-manage-DaynTl62.css 2.34 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/StatusBadge-B4N12dzU.css 2.35 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/GridView-CsP1Jk-k.css 2.36 kB │ gzip: 0.90 kB\n../public/vue-assets/assets/WelcomeLayout-6AX86p6F.css 2.38 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/snackbar-CS_iqvrv.css 2.45 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/ai-reports-mtqGqPPq.css 2.47 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/Comment-gmAfiuer.css 2.63 kB │ gzip: 1.00 kB\n../public/vue-assets/assets/meeting-consent-BasRvxE3.css 2.66 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/DealRiskList-CwxmDnSC.css 2.67 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/add-to-playlist-modal-C_ad1hht.css 2.84 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/live-_zT5qBc1.css 3.60 kB │ gzip: 1.16 kB\n../public/vue-assets/assets/RadioField-DDp8Y71G.css 3.64 kB │ gzip: 1.20 kB\n../public/vue-assets/assets/vue-multiselect-BkQNV7oL.css 3.80 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/activity-preview-result-Db1Da81T.css 3.89 kB │ gzip: 1.15 kB\n../public/vue-assets/assets/liquor-tree-CB5oh8v1.css 4.08 kB │ gzip: 1.24 kB\n../public/vue-assets/assets/sentry-CSjHcvmn.css 4.47 kB │ gzip: 1.08 kB\n../public/vue-assets/assets/AiAutomation-Cj1t8v5y.css 4.61 kB │ gzip: 0.84 kB\n../public/vue-assets/assets/export-portal-CyIUVTEe.css 5.34 kB │ gzip: 1.65 kB\n../public/vue-assets/assets/InputField-C4hoKJ7Z.css 6.13 kB │ gzip: 1.27 kB\n../public/vue-assets/assets/vue-mq-bh4L87Tr.css 6.64 kB │ gzip: 1.95 kB\n../public/vue-assets/assets/invitation-EPR1t-bH.css 6.74 kB │ gzip: 2.22 kB\n../public/vue-assets/assets/ondemand-C2m0YVGA.css 6.84 kB │ gzip: 2.02 kB\n../public/vue-assets/assets/AskAnything-CyvEhHS1.css 8.60 kB │ gzip: 2.60 kB\n../public/vue-assets/assets/AppButton-BIeoar_U.css 8.88 kB │ gzip: 2.04 kB\n../public/vue-assets/assets/kiosk-BcheyWWL.css 9.56 kB │ gzip: 2.53 kB\n../public/vue-assets/assets/AiCrmNotes-nNTe_mOY.css 10.62 kB │ gzip: 1.93 kB\n../public/vue-assets/assets/deal-view-DJzG7PLp.css 11.70 kB │ gzip: 3.29 kB\n../public/vue-assets/assets/tokens-DZgWrfKd.css 12.44 kB │ gzip: 2.82 kB\n../public/vue-assets/assets/tokens-DL2BPbai.css 12.89 kB │ gzip: 2.87 kB\n../public/vue-assets/assets/playlists-Cy3t3kYU.css 13.54 kB │ gzip: 3.16 kB\n../public/vue-assets/assets/PhoneField-D_kGOah0.css 13.75 kB │ gzip: 3.44 kB\n../public/vue-assets/assets/ListView-Dt0qkyuY.css 17.07 kB │ gzip: 4.11 kB\n../public/vue-assets/assets/intl-tel-input-DgmgTINs.css 17.11 kB │ gzip: 5.37 kB\n../public/vue-assets/assets/AppFormField-DvpfRzi8.css 18.98 kB │ gzip: 4.42 kB\n../public/vue-assets/assets/OrgSettingsLayout-BogdXtgY.css 21.98 kB │ gzip: 5.51 kB\n../public/vue-assets/assets/dashboard-DQw3TKyk.css 22.74 kB │ gzip: 4.35 kB\n../public/vue-assets/assets/deal-insights-CfX3UNzh.css 29.66 kB │ gzip: 6.44 kB\n../public/vue-assets/assets/team-insights-CjVhm0JN.css 42.31 kB │ gzip: 8.46 kB\n../public/vue-assets/assets/playback-CvJP5tX1.css 44.55 kB │ gzip: 10.36 kB\n../public/vue-assets/assets/video-js-skin-DYZluGb-.css 47.73 kB │ gzip: 12.63 kB\n../public/vue-assets/assets/assets-CAbfI4CY.css 83.38 kB │ gzip: 51.71 kB\n../public/vue-assets/assets/logged-in-layout-1mAqkmnS.css 153.67 kB │ gzip: 27.18 kB\n../public/vue-assets/assets/assets-xH6dx_9q.css 259.71 kB │ gzip: 181.71 kB\n../public/vue-assets/assets/directives-DJJeJJOP.js 0.53 kB │ gzip: 0.36 kB │ map: 0.32 kB\n../public/vue-assets/assets/jenesius-vue-modal-BuBhyl83.js 0.54 kB │ gzip: 0.37 kB │ map: 0.47 kB\n../public/vue-assets/assets/spark-D_-Wgfar.js 0.54 kB │ gzip: 0.35 kB │ map: 0.40 kB\n../public/vue-assets/assets/url-messenger-_CGQa-lH.js 0.56 kB │ gzip: 0.37 kB │ map: 0.46 kB\n../public/vue-assets/assets/wavy-bg-Cec-_GDj.js 0.57 kB │ gzip: 0.36 kB │ map: 0.35 kB\n../public/vue-assets/assets/component-css-class-CtO0AVgW.js 0.61 kB │ gzip: 0.40 kB │ map: 0.91 kB\n../public/vue-assets/assets/theme-B5VdyMN_.js 0.63 kB │ gzip: 0.41 kB │ map: 0.49 kB\n../public/vue-assets/assets/pick-C8Pw7kHx.js 0.71 kB │ gzip: 0.45 kB │ map: 1.59 kB\n../public/vue-assets/assets/utils-BjsEoLDB.js 0.77 kB │ gzip: 0.50 kB │ map: 1.68 kB\n../public/vue-assets/assets/throttle-siTOKEMt.js 0.78 kB │ gzip: 0.49 kB │ map: 3.24 kB\n../public/vue-assets/assets/lastFilters-B_B_O5LY.js 0.80 kB │ gzip: 0.51 kB │ map: 1.12 kB\n../public/vue-assets/assets/useAuthState-Bpx8WpBc.js 0.81 kB │ gzip: 0.50 kB │ map: 1.64 kB\n../public/vue-assets/assets/v-focus-618yf9WO.js 0.85 kB │ gzip: 0.52 kB │ map: 1.42 kB\n../public/vue-assets/assets/ListLoader-bfQmkVGu.js 0.87 kB │ gzip: 0.54 kB │ map: 2.90 kB\n../public/vue-assets/assets/utils-CAMifZzY.js 0.88 kB │ gzip: 0.53 kB │ map: 2.45 kB\n../public/vue-assets/assets/mobileApp-QFLS3kId.js 0.89 kB │ gzip: 0.54 kB │ map: 1.12 kB\n../public/vue-assets/assets/pickBy-CsQ1aO-4.js 0.95 kB │ gzip: 0.59 kB │ map: 2.57 kB\n../public/vue-assets/assets/BuildInfo-CIGre86L.js 1.00 kB │ gzip: 0.65 kB │ map: 1.48 kB\n../public/vue-assets/assets/LogoShort100-D_SQdBYB.js 1.01 kB │ gzip: 0.64 kB │ map: 1.23 kB\n../public/vue-assets/assets/useDrawerModal-CmhXI-pZ.js 1.04 kB │ gzip: 0.65 kB │ map: 1.94 kB\n../public/vue-assets/assets/_getAllKeysIn-CkMQAnJa.js 1.12 kB │ gzip: 0.67 kB │ map: 4.83 kB\n../public/vue-assets/assets/useAutosizeTextarea-DyMwh_20.js 1.13 kB │ gzip: 0.70 kB │ map: 3.16 kB\n../public/vue-assets/assets/planhat-DPA36Hcn.js 1.21 kB │ gzip: 0.71 kB │ map: 2.18 kB\n../public/vue-assets/assets/useStoreModule-Cx6UoGVJ.js 1.35 kB │ gzip: 0.72 kB │ map: 6.03 kB\n../public/vue-assets/assets/GenericMessage-BxGsrfvw.js 1.39 kB │ gzip: 0.75 kB │ map: 1.90 kB\n../public/vue-assets/assets/BrowserExtensionInstaller-DimyBPIi.js 1.45 kB │ gzip: 0.78 kB │ map: 2.05 kB\n../public/vue-assets/assets/debounce-7ycH7TWx.js 1.53 kB │ gzip: 0.83 kB │ map: 8.61 kB\n../public/vue-assets/assets/extension-installed-DUcV_uoi.js 1.60 kB │ gzip: 0.91 kB │ map: 3.20 kB\n../public/vue-assets/assets/RecipientsCell-BS9gDGey.js 1.61 kB │ gzip: 0.93 kB │ map: 4.04 kB\n../public/vue-assets/assets/PrimaryButton-DaKR3UAG.js 1.61 kB │ gzip: 0.87 kB │ map: 3.73 kB\n../public/vue-assets/assets/KioskBanner-CYQu1FG7.js 1.66 kB │ gzip: 1.00 kB │ map: 2.88 kB\n../public/vue-assets/assets/LogoLong100-Hid0kvjR.js 1.78 kB │ gzip: 1.08 kB │ map: 5.91 kB\n../public/vue-assets/assets/GoogleLikeButton-CGFy3nbz.js 1.80 kB │ gzip: 0.84 kB │ map: 4.71 kB\n../public/vue-assets/assets/AppAlert-DFp0nftl.js 1.86 kB │ gzip: 1.01 kB │ map: 6.51 kB\n../public/vue-assets/assets/usePusherEventListener-BjhdeObt.js 1.93 kB │ gzip: 1.08 kB │ map: 9.59 kB\n../public/vue-assets/assets/AppForm-BgtH5xsM.js 2.01 kB │ gzip: 1.10 kB │ map: 6.89 kB\n../public/vue-assets/assets/vee-validate-rules-IFeGZHb3.js 2.02 kB │ gzip: 0.89 kB │ map: 22.31 kB\n../public/vue-assets/assets/literals-CXSwYC8y.js 2.05 kB │ gzip: 0.97 kB │ map: 3.08 kB\n../public/vue-assets/assets/TabEmptyState-pk8vRxJt.js 2.16 kB │ gzip: 1.12 kB │ map: 8.26 kB\n../public/vue-assets/assets/Replies-B2cExxmx.js 2.23 kB │ gzip: 1.14 kB │ map: 2.40 kB\n../public/vue-assets/assets/settings--S7_RYRE.js 2.34 kB │ gzip: 1.24 kB │ map: 1.73 kB\n../public/vue-assets/assets/SeekBtn-CMp8sSUA.js 2.47 kB │ gzip: 1.18 kB │ map: 9.27 kB\n../public/vue-assets/assets/AiAutomation-cuK1068l.js 2.59 kB │ gzip: 1.37 kB │ map: 4.45 kB\n../public/vue-assets/assets/locked-DxPLOs1S.js 2.59 kB │ gzip: 1.42 kB │ map: 4.00 kB\n../public/vue-assets/assets/DrawerWidget-BPC-tCgo.js 2.68 kB │ gzip: 1.33 kB │ map: 6.95 kB\n../public/vue-assets/assets/vue-infinite-scroll-D0cI4gH6.js 2.72 kB │ gzip: 1.28 kB │ map: 9.99 kB\n../public/vue-assets/assets/useActivityCustomerName-CYuaZj-p.js 2.87 kB │ gzip: 1.41 kB │ map: 6.27 kB\n../public/vue-assets/assets/other-CVxaD0_B.js 2.97 kB │ gzip: 1.50 kB │ map: 3.38 kB\n../public/vue-assets/assets/InputDropdown-CBM8xiPT.js 3.33 kB │ gzip: 1.61 kB │ map: 6.02 kB\n../public/vue-assets/assets/AvatarsStack-hxex9Ic8.js 3.40 kB │ gzip: 1.51 kB │ map: 9.54 kB\n../public/vue-assets/assets/activity-preview--ydYatRe.js 3.45 kB │ gzip: 1.68 kB │ map: 10.11 kB\n../public/vue-assets/assets/FollowModal-SMYvVwqG.js 3.70 kB │ gzip: 1.81 kB │ map: 8.86 kB\n../public/vue-assets/assets/softphone-coach-CkLTUCy6.js 3.74 kB │ gzip: 1.96 kB │ map: 5.03 kB\n../public/vue-assets/assets/store-DPy-uEfS.js 4.10 kB │ gzip: 2.00 kB │ map: 18.73 kB\n../public/vue-assets/assets/AiContext-D36CpnKB.js 4.34 kB │ gzip: 2.08 kB │ map: 13.11 kB\n../public/vue-assets/assets/meeting-consent-C0YRJ_xR.js 4.43 kB │ gzip: 2.05 kB │ map: 9.72 kB\n../public/vue-assets/assets/vue-multiselect-VB2Agtp1.js 4.49 kB │ gzip: 1.93 kB │ map: 14.85 kB\n../public/vue-assets/assets/snackbarNotifications-DjDIGkWd.js 4.83 kB │ gzip: 1.58 kB │ map: 11.62 kB\n../public/vue-assets/assets/invitation--7sddGcP.js 4.88 kB │ gzip: 2.21 kB │ map: 19.38 kB\n../public/vue-assets/assets/connect-Bfq7LjjQ.js 5.17 kB │ gzip: 2.28 kB │ map: 10.35 kB\n../public/vue-assets/assets/textarea-caret-B2HdIdpZ.js 5.25 kB │ gzip: 2.41 kB │ map: 14.48 kB\n../public/vue-assets/assets/snackbar-Bq-YQ7pz.js 5.48 kB │ gzip: 2.37 kB │ map: 20.43 kB\n../public/vue-assets/assets/RadioField-2SeSZB7D.js 5.65 kB │ gzip: 2.22 kB │ map: 15.82 kB\n../public/vue-assets/assets/filters-D_eXZdIL.js 5.76 kB │ gzip: 2.17 kB │ map: 16.36 kB\n../public/vue-assets/assets/useActivityHelper-Dj5DULYF.js 5.84 kB │ gzip: 2.51 kB │ map: 14.26 kB\n../public/vue-assets/assets/vue-scrollto-ul3pFdrb.js 5.97 kB │ gzip: 2.72 kB │ map: 23.98 kB\n../public/vue-assets/assets/join-conference-gY3HLgqP.js 6.25 kB │ gzip: 2.83 kB │ map: 24.66 kB\n../public/vue-assets/assets/pluralize-BeKVJaE0.js 6.34 kB │ gzip: 2.70 kB │ map: 18.28 kB\n../public/vue-assets/assets/login-Bz_Gi9sn.js 7.02 kB │ gzip: 3.16 kB │ map: 17.51 kB\n../public/vue-assets/assets/AppLinks-Bg20Hslb.js 7.14 kB │ gzip: 2.80 kB │ map: 14.87 kB\n../public/vue-assets/assets/InputField-CkdrqKBp.js 7.20 kB │ gzip: 2.58 kB │ map: 19.39 kB\n../public/vue-assets/assets/UserAvatar-UKkSAS4R.js 7.93 kB │ gzip: 3.32 kB │ map: 34.91 kB\n../public/vue-assets/assets/InputText-Dgwsn6KA.js 7.99 kB │ gzip: 3.05 kB │ map: 20.64 kB\n../public/vue-assets/assets/AiCrmNotes-DNS51mtN.js 8.27 kB │ gzip: 3.32 kB │ map: 34.03 kB\n../public/vue-assets/assets/vue-mq-CmpUzQtD.js 8.84 kB │ gzip: 3.54 kB │ map: 23.69 kB\n../public/vue-assets/assets/pro-duotone-svg-icons-BSBZV3-t.js 9.51 kB │ gzip: 3.43 kB │ map: 3,146.68 kB\n../public/vue-assets/assets/activity-preview-result-CIGPrkbw.js 10.65 kB │ gzip: 3.98 kB │ map: 38.51 kB\n../public/vue-assets/assets/pro-regular-svg-icons-CWpSBR20.js 13.10 kB │ gzip: 4.64 kB │ map: 3,178.15 kB\n../public/vue-assets/assets/add-to-playlist-modal-CPwNAOXA.js 14.99 kB │ gzip: 5.98 kB │ map: 51.34 kB\n../public/vue-assets/assets/ai-reports-DAEn44FQ.js 15.17 kB │ gzip: 6.04 kB │ map: 51.85 kB\n../public/vue-assets/assets/export-portal-DB0CksQW.js 15.68 kB │ gzip: 6.22 kB │ map: 52.78 kB\n../public/vue-assets/assets/Comment-ClBvXCXq.js 15.92 kB │ gzip: 5.72 kB │ map: 39.96 kB\n../public/vue-assets/assets/useragent-DQ4F_O30.js 18.02 kB │ gzip: 8.06 kB │ map: 68.31 kB\n../public/vue-assets/assets/linkify-B_q6MgwI.js 18.58 kB │ gzip: 10.37 kB │ map: 88.73 kB\n../public/vue-assets/assets/ai-reports-manage-BTQmbI4-.js 18.92 kB │ gzip: 6.76 kB │ map: 66.14 kB\n../public/vue-assets/assets/dist-C2hisF0L.js 19.32 kB │ gzip: 7.87 kB │ map: 390.77 kB\n../public/vue-assets/assets/pro-solid-svg-icons-DBOkpln3.js 19.46 kB │ gzip: 6.72 kB │ map: 2,692.14 kB\n../public/vue-assets/assets/purify.es-BxOw6oE6.js 21.49 kB │ gzip: 8.71 kB │ map: 83.43 kB\n../public/vue-assets/assets/ActionItems-CqgLpVFE.js 21.92 kB │ gzip: 8.54 kB │ map: 76.05 kB\n../public/vue-assets/assets/mobile-4e6kQ9l_.js 23.34 kB │ gzip: 9.71 kB │ map: 50.90 kB\n../public/vue-assets/assets/basic-modal-BdXsnFwC.js 23.87 kB │ gzip: 9.06 kB │ map: 64.15 kB\n../public/vue-assets/assets/GridView-CocIYUaz.js 26.60 kB │ gzip: 10.05 kB │ map: 92.74 kB\n../public/vue-assets/assets/ondemand-CE8XLH98.js 26.88 kB │ gzip: 9.38 kB │ map: 73.94 kB\n../public/vue-assets/assets/CrmLink-DKYsnHnx.js 27.91 kB │ gzip: 10.18 kB │ map: 93.18 kB\n../public/vue-assets/assets/liquor-tree-COUefof4.js 30.75 kB │ gzip: 9.58 kB │ map: 78.74 kB\n../public/vue-assets/assets/DealRiskList-3DSxnTNQ.js 34.39 kB │ gzip: 10.62 kB │ map: 115.18 kB\n../public/vue-assets/assets/AskAnything-BQ36EoGS.js 39.50 kB │ gzip: 14.97 kB │ map: 173.20 kB\n../public/vue-assets/assets/lib-CwM9toD2.js 39.69 kB │ gzip: 12.70 kB │ map: 138.34 kB\n../public/vue-assets/assets/AppFormField-CqCXZwrk.js 41.91 kB │ gzip: 12.69 kB │ map: 150.73 kB\n../public/vue-assets/assets/deal-view-B92qaj32.js 43.22 kB │ gzip: 14.34 kB │ map: 150.62 kB\n../public/vue-assets/assets/exports-D1lmea4O.js 47.84 kB │ gzip: 16.46 kB │ map: 294.48 kB\n../public/vue-assets/assets/playlists-u3UeUUz5.js 48.28 kB │ gzip: 15.07 kB │ map: 153.25 kB\n../public/vue-assets/assets/callScoringTemplates-zeRn40uL.js 55.13 kB │ gzip: 13.28 kB │ map: 65.85 kB\n../public/vue-assets/assets/_copyObject-USkOnlaQ.js 61.28 kB │ gzip: 20.09 kB │ map: 239.59 kB\n../public/vue-assets/assets/pusher-znYCfz7U.js 62.98 kB │ gzip: 18.88 kB │ map: 219.27 kB\n../public/vue-assets/assets/onboard-CCnYJmCP.js 63.11 kB │ gzip: 21.85 kB │ map: 201.38 kB\n../public/vue-assets/assets/StatusBadge-CNGFZm8P.js 64.66 kB │ gzip: 22.96 kB │ map: 244.72 kB\n../public/vue-assets/assets/kiosk-D6N5-ivP.js 79.60 kB │ gzip: 22.65 kB │ map: 300.68 kB\n../public/vue-assets/assets/preload-helper-DCvhahzG.js 82.59 kB │ gzip: 27.46 kB │ map: 3,452.35 kB\n../public/vue-assets/assets/deal-insights-jlkz65MZ.js 94.84 kB │ gzip: 28.16 kB │ map: 292.79 kB\n../public/vue-assets/assets/ListView-DmKqYVi1.js 115.71 kB │ gzip: 33.78 kB │ map: 308.10 kB\n../public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js 117.59 kB │ gzip: 38.71 kB │ map: 500.60 kB\n../public/vue-assets/assets/WelcomeLayout-B6wd32HG.js 120.67 kB │ gzip: 34.16 kB │ map: 258.56 kB\n../public/vue-assets/assets/dashboard-BWoBIPKA.js 128.71 kB │ gzip: 40.05 kB │ map: 410.48 kB\n../public/vue-assets/assets/emoji-input-CSq87OVy.js 129.28 kB │ gzip: 36.72 kB │ map: 266.15 kB\n../public/vue-assets/assets/AppButton-D3qMdODr.js 133.44 kB │ gzip: 43.03 kB │ map: 516.67 kB\n../public/vue-assets/assets/sentry-icPKypZa.js 164.28 kB │ gzip: 52.24 kB │ map: 831.82 kB\n../public/vue-assets/assets/OrgSettingsLayout-BZ-J0giH.js 176.33 kB │ gzip: 56.15 kB │ map: 623.43 kB\n../public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js 180.40 kB │ gzip: 67.85 kB │ map: 836.88 kB\n../public/vue-assets/assets/playback-DKsqp_aL.js 198.79 kB │ gzip: 61.85 kB │ map: 684.87 kB\n../public/vue-assets/assets/index.module-Bjlhgfd1.js 218.14 kB │ gzip: 64.16 kB │ map: 1,108.20 kB\n../public/vue-assets/assets/intl-tel-input-BW4mv40Q.js 264.94 kB │ gzip: 60.31 kB │ map: 475.61 kB\n../public/vue-assets/assets/team-insights-D9P6ojjD.js 298.57 kB │ gzip: 77.22 kB │ map: 959.96 kB\n../public/vue-assets/assets/popper-CQwVcrX4.js 307.13 kB │ gzip: 103.86 kB │ map: 1,245.28 kB\n../public/vue-assets/assets/PhoneField-CwCIoAYm.js 343.99 kB │ gzip: 84.90 kB │ map: 849.05 kB\n../public/vue-assets/assets/live-DWumv-QD.js 367.43 kB │ gzip: 97.05 kB │ map: 792.41 kB\n../public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BNO485xV.js 689.63 kB │ gzip: 202.81 kB │ map: 3,016.64 kB\n../public/vue-assets/assets/index-DiEs6xIb.js 825.23 kB │ gzip: 72.54 kB │ map: 436.62 kB\n../public/vue-assets/assets/logged-in-layout-Cq1ztH5O.js 1,402.70 kB │ gzip: 438.08 kB │ map: 6,283.55 kB\n\n[plugin builtin:vite-reporter] \n(!) Some chunks are larger than 500 kB after minification. Consider:\n- Using dynamic import() to code-split the application\n- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting\n- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.\n✓ built in 13.59s\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yarn build\n[dotenv@17.3.1] injecting env (0) from .env -- tip: ⚙️\u0000 load multiple .env files with { path: ['.env.local', '.env'] }\nvite v8.0.0 building client environment for production...\n✓ 4656 modules transformed.\n[sentry-vite-plugin] Warning: No auth token provided. Will not create release. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\n[sentry-vite-plugin] Warning: No auth token provided. Will not upload source maps. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\ncomputing gzip size...\n../public/vue-assets/index.html 4.58 kB │ gzip: 1.16 kB\n../public/vue-assets/assets/job-adder-B2LcgC4o.png 5.41 kB\n../public/vue-assets/assets/dixa-DzT89tKh.png 7.87 kB\n../public/vue-assets/assets/planhat-CQycOTMW.svg 8.61 kB │ gzip: 3.48 kB\n../public/vue-assets/assets/funding-circle-9iGnDGsz.png 9.43 kB\n../public/vue-assets/assets/cision-uOdx_YiN.svg 14.41 kB │ gzip: 6.32 kB\n../public/vue-assets/assets/les-mills-DWabpmYc.png 15.73 kB\n../public/vue-assets/assets/superside-btT37L9L.png 26.69 kB\n../public/vue-assets/assets/flags-a2kmUSbF.webp 28.18 kB\n../public/vue-assets/assets/quinyx-RGQNHx6o.png 63.17 kB\n../public/vue-assets/assets/flags@2x-gR6KPp3x.webp 66.44 kB\n../public/vue-assets/assets/bg-marketing-CAIHIJMj.png 70.66 kB\n../public/vue-assets/.vite/manifest.json 85.73 kB │ gzip: 8.33 kB\n../public/vue-assets/assets/jiminny-score-DtTbKZaD.svg 131.93 kB │ gzip: 86.07 kB\n../public/vue-assets/assets/wavy-bg-vnCaCm9m.webm 670.97 kB\n../public/vue-assets/assets/wavy-bg-Op6dRIjR.mp4 14,484.63 kB\n../public/vue-assets/assets/AppForm-EEGPL5K8.css 0.04 kB │ gzip: 0.06 kB\n../public/vue-assets/assets/RecipientsCell-DNOSS8k1.css 0.12 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/softphone-coach-Cy_9nuQZ.css 0.13 kB │ gzip: 0.10 kB\n../public/vue-assets/assets/locked-CHS8VPur.css 0.15 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/mobile-KPnGhnTp.css 0.16 kB │ gzip: 0.14 kB\n../public/vue-assets/assets/BuildInfo-CxNQv_OV.css 0.20 kB │ gzip: 0.17 kB\n../public/vue-assets/assets/AvatarsStack-C0Nvfh1V.css 0.28 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/LogoLong100-ByQTvUY3.css 0.32 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/connect--xBDzLbc.css 0.34 kB │ gzip: 0.21 kB\n../public/vue-assets/assets/KioskBanner-xU3FRd8g.css 0.41 kB │ gzip: 0.27 kB\n../public/vue-assets/assets/extension-installed-BPBhG13P.css 0.41 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/FollowModal-A0rJmIOK.css 0.45 kB │ gzip: 0.23 kB\n../public/vue-assets/assets/GenericMessage-DutAlObK.css 0.49 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/textarea-caret-DCiNUER_.css 0.51 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/AppLinks-BznfGcBR.css 0.58 kB │ gzip: 0.33 kB\n../public/vue-assets/assets/SeekBtn-_VMPIk6v.css 0.63 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/emoji-input-kJWRbWf8.css 0.68 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/activity-preview-BDxP2S0-.css 0.70 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/ListLoader-DQqAw-8s.css 0.73 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/filters-Bgb6cQD6.css 0.75 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/AiContext-Bg-zjjbA.css 0.99 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/vue3-daterange-picker-izPOBj3P.css 1.02 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/PrimaryButton-CJj2FzKS.css 1.08 kB │ gzip: 0.34 kB\n../public/vue-assets/assets/other-BTuPXtu_.css 1.15 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/jenesius-vue-modal-DDcfejHO.css 1.17 kB │ gzip: 0.48 kB\n../public/vue-assets/assets/TabEmptyState-DOlIO68k.css 1.21 kB │ gzip: 0.53 kB\n../public/vue-assets/assets/AppAlert-BlSJKiqj.css 1.29 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/useActivityCustomerName-CBjfIUfJ.css 1.42 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/UserAvatar-co8l9mMk.css 1.51 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/basic-modal-Dx7ZBbcA.css 1.55 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/login-DYbcBesM.css 1.58 kB │ gzip: 0.61 kB\n../public/vue-assets/assets/DrawerWidget-poAmqspk.css 1.67 kB │ gzip: 0.69 kB\n../public/vue-assets/assets/ActionItems-5FkZSo3G.css 1.79 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/join-conference-DQ6Yl7eU.css 1.82 kB │ gzip: 0.75 kB\n../public/vue-assets/assets/InputText-C-DxDNSV.css 1.95 kB │ gzip: 0.71 kB\n../public/vue-assets/assets/GoogleLikeButton-DvQHxbqR.css 2.11 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/usePusherEventListener-CDj8aSNp.css 2.20 kB │ gzip: 0.73 kB\n../public/vue-assets/assets/onboard-IzGiQ-EC.css 2.32 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/ai-reports-manage-DaynTl62.css 2.34 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/StatusBadge-B4N12dzU.css 2.35 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/GridView-CsP1Jk-k.css 2.36 kB │ gzip: 0.90 kB\n../public/vue-assets/assets/WelcomeLayout-6AX86p6F.css 2.38 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/snackbar-CS_iqvrv.css 2.45 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/ai-reports-mtqGqPPq.css 2.47 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/Comment-gmAfiuer.css 2.63 kB │ gzip: 1.00 kB\n../public/vue-assets/assets/meeting-consent-BasRvxE3.css 2.66 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/DealRiskList-CwxmDnSC.css 2.67 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/add-to-playlist-modal-C_ad1hht.css 2.84 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/live-_zT5qBc1.css 3.60 kB │ gzip: 1.16 kB\n../public/vue-assets/assets/RadioField-DDp8Y71G.css 3.64 kB │ gzip: 1.20 kB\n../public/vue-assets/assets/vue-multiselect-BkQNV7oL.css 3.80 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/activity-preview-result-Db1Da81T.css 3.89 kB │ gzip: 1.15 kB\n../public/vue-assets/assets/liquor-tree-CB5oh8v1.css 4.08 kB │ gzip: 1.24 kB\n../public/vue-assets/assets/sentry-CSjHcvmn.css 4.47 kB │ gzip: 1.08 kB\n../public/vue-assets/assets/AiAutomation-Cj1t8v5y.css 4.61 kB │ gzip: 0.84 kB\n../public/vue-assets/assets/export-portal-CyIUVTEe.css 5.34 kB │ gzip: 1.65 kB\n../public/vue-assets/assets/InputField-C4hoKJ7Z.css 6.13 kB │ gzip: 1.27 kB\n../public/vue-assets/assets/vue-mq-bh4L87Tr.css 6.64 kB │ gzip: 1.95 kB\n../public/vue-assets/assets/invitation-EPR1t-bH.css 6.74 kB │ gzip: 2.22 kB\n../public/vue-assets/assets/ondemand-C2m0YVGA.css 6.84 kB │ gzip: 2.02 kB\n../public/vue-assets/assets/AskAnything-CyvEhHS1.css 8.60 kB │ gzip: 2.60 kB\n../public/vue-assets/assets/AppButton-BIeoar_U.css 8.88 kB │ gzip: 2.04 kB\n../public/vue-assets/assets/kiosk-BcheyWWL.css 9.56 kB │ gzip: 2.53 kB\n../public/vue-assets/assets/AiCrmNotes-nNTe_mOY.css 10.62 kB │ gzip: 1.93 kB\n../public/vue-assets/assets/deal-view-DJzG7PLp.css 11.70 kB │ gzip: 3.29 kB\n../public/vue-assets/assets/tokens-DZgWrfKd.css 12.44 kB │ gzip: 2.82 kB\n../public/vue-assets/assets/tokens-DL2BPbai.css 12.89 kB │ gzip: 2.87 kB\n../public/vue-assets/assets/playlists-Cy3t3kYU.css 13.54 kB │ gzip: 3.16 kB\n../public/vue-assets/assets/PhoneField-D_kGOah0.css 13.75 kB │ gzip: 3.44 kB\n../public/vue-assets/assets/ListView-Dt0qkyuY.css 17.07 kB │ gzip: 4.11 kB\n../public/vue-assets/assets/intl-tel-input-DgmgTINs.css 17.11 kB │ gzip: 5.37 kB\n../public/vue-assets/assets/AppFormField-DvpfRzi8.css 18.98 kB │ gzip: 4.42 kB\n../public/vue-assets/assets/OrgSettingsLayout-BogdXtgY.css 21.98 kB │ gzip: 5.51 kB\n../public/vue-assets/assets/dashboard-DQw3TKyk.css 22.74 kB │ gzip: 4.35 kB\n../public/vue-assets/assets/deal-insights-CfX3UNzh.css 29.66 kB │ gzip: 6.44 kB\n../public/vue-assets/assets/team-insights-CjVhm0JN.css 42.31 kB │ gzip: 8.46 kB\n../public/vue-assets/assets/playback-CvJP5tX1.css 44.55 kB │ gzip: 10.36 kB\n../public/vue-assets/assets/video-js-skin-DYZluGb-.css 47.73 kB │ gzip: 12.63 kB\n../public/vue-assets/assets/assets-CAbfI4CY.css 83.38 kB │ gzip: 51.71 kB\n../public/vue-assets/assets/logged-in-layout-1mAqkmnS.css 153.67 kB │ gzip: 27.18 kB\n../public/vue-assets/assets/assets-xH6dx_9q.css 259.71 kB │ gzip: 181.71 kB\n../public/vue-assets/assets/directives-DJJeJJOP.js 0.53 kB │ gzip: 0.36 kB │ map: 0.32 kB\n../public/vue-assets/assets/jenesius-vue-modal-BuBhyl83.js 0.54 kB │ gzip: 0.37 kB │ map: 0.47 kB\n../public/vue-assets/assets/spark-D_-Wgfar.js 0.54 kB │ gzip: 0.35 kB │ map: 0.40 kB\n../public/vue-assets/assets/url-messenger-_CGQa-lH.js 0.56 kB │ gzip: 0.37 kB │ map: 0.46 kB\n../public/vue-assets/assets/wavy-bg-Cec-_GDj.js 0.57 kB │ gzip: 0.36 kB │ map: 0.35 kB\n../public/vue-assets/assets/component-css-class-CtO0AVgW.js 0.61 kB │ gzip: 0.40 kB │ map: 0.91 kB\n../public/vue-assets/assets/theme-CrWye7yo.js 0.63 kB │ gzip: 0.41 kB │ map: 0.49 kB\n../public/vue-assets/assets/pick-D-kTQpNV.js 0.71 kB │ gzip: 0.45 kB │ map: 1.59 kB\n../public/vue-assets/assets/utils-BjsEoLDB.js 0.77 kB │ gzip: 0.50 kB │ map: 1.68 kB\n../public/vue-assets/assets/throttle-CEV1oVAM.js 0.78 kB │ gzip: 0.49 kB │ map: 3.24 kB\n../public/vue-assets/assets/lastFilters-D3xNTqAt.js 0.80 kB │ gzip: 0.51 kB │ map: 1.12 kB\n../public/vue-assets/assets/useAuthState-Bpx8WpBc.js 0.81 kB │ gzip: 0.50 kB │ map: 1.64 kB\n../public/vue-assets/assets/v-focus-618yf9WO.js 0.85 kB │ gzip: 0.52 kB │ map: 1.42 kB\n../public/vue-assets/assets/ListLoader-bfQmkVGu.js 0.87 kB │ gzip: 0.54 kB │ map: 2.90 kB\n../public/vue-assets/assets/utils-CAMifZzY.js 0.88 kB │ gzip: 0.53 kB │ map: 2.45 kB\n../public/vue-assets/assets/mobileApp-QFLS3kId.js 0.89 kB │ gzip: 0.54 kB │ map: 1.12 kB\n../public/vue-assets/assets/pickBy-DftTc1_7.js 0.95 kB │ gzip: 0.59 kB │ map: 2.57 kB\n../public/vue-assets/assets/BuildInfo-CIGre86L.js 1.00 kB │ gzip: 0.65 kB │ map: 1.48 kB\n../public/vue-assets/assets/LogoShort100-D_SQdBYB.js 1.01 kB │ gzip: 0.64 kB │ map: 1.23 kB\n../public/vue-assets/assets/useDrawerModal-CmhXI-pZ.js 1.04 kB │ gzip: 0.65 kB │ map: 1.94 kB\n../public/vue-assets/assets/_getAllKeysIn-CkMQAnJa.js 1.12 kB │ gzip: 0.67 kB │ map: 4.83 kB\n../public/vue-assets/assets/useAutosizeTextarea-DyMwh_20.js 1.13 kB │ gzip: 0.70 kB │ map: 3.16 kB\n../public/vue-assets/assets/planhat-DPA36Hcn.js 1.21 kB │ gzip: 0.71 kB │ map: 2.18 kB\n../public/vue-assets/assets/useStoreModule-Cx6UoGVJ.js 1.35 kB │ gzip: 0.72 kB │ map: 6.03 kB\n../public/vue-assets/assets/GenericMessage-BxGsrfvw.js 1.39 kB │ gzip: 0.75 kB │ map: 1.90 kB\n../public/vue-assets/assets/BrowserExtensionInstaller-DimyBPIi.js 1.45 kB │ gzip: 0.78 kB │ map: 2.05 kB\n../public/vue-assets/assets/debounce-CK0K0ozg.js 1.53 kB │ gzip: 0.83 kB │ map: 8.61 kB\n../public/vue-assets/assets/extension-installed-DLwqdmK8.js 1.60 kB │ gzip: 0.91 kB │ map: 3.20 kB\n../public/vue-assets/assets/RecipientsCell-BS9gDGey.js 1.61 kB │ gzip: 0.93 kB │ map: 4.04 kB\n../public/vue-assets/assets/PrimaryButton-DaKR3UAG.js 1.61 kB │ gzip: 0.87 kB │ map: 3.73 kB\n../public/vue-assets/assets/KioskBanner-D8zd6tGl.js 1.66 kB │ gzip: 1.00 kB │ map: 2.88 kB\n../public/vue-assets/assets/LogoLong100-Hid0kvjR.js 1.78 kB │ gzip: 1.08 kB │ map: 5.91 kB\n../public/vue-assets/assets/GoogleLikeButton-CGFy3nbz.js 1.80 kB │ gzip: 0.84 kB │ map: 4.71 kB\n../public/vue-assets/assets/AppAlert-DFp0nftl.js 1.86 kB │ gzip: 1.01 kB │ map: 6.51 kB\n../public/vue-assets/assets/usePusherEventListener-D8AEMpQD.js 1.93 kB │ gzip: 1.08 kB │ map: 9.59 kB\n../public/vue-assets/assets/AppForm-BgtH5xsM.js 2.01 kB │ gzip: 1.10 kB │ map: 6.89 kB\n../public/vue-assets/assets/vee-validate-rules-IFeGZHb3.js 2.02 kB │ gzip: 0.89 kB │ map: 22.31 kB\n../public/vue-assets/assets/literals-CXSwYC8y.js 2.05 kB │ gzip: 0.97 kB │ map: 3.08 kB\n../public/vue-assets/assets/TabEmptyState-pk8vRxJt.js 2.16 kB │ gzip: 1.12 kB │ map: 8.26 kB\n../public/vue-assets/assets/Replies-DZPkx2YQ.js 2.23 kB │ gzip: 1.14 kB │ map: 2.40 kB\n../public/vue-assets/assets/settings-DHjh0S4O.js 2.34 kB │ gzip: 1.24 kB │ map: 1.73 kB\n../public/vue-assets/assets/SeekBtn-CMp8sSUA.js 2.47 kB │ gzip: 1.18 kB │ map: 9.27 kB\n../public/vue-assets/assets/AiAutomation-CTc8r0pe.js 2.59 kB │ gzip: 1.37 kB │ map: 4.45 kB\n../public/vue-assets/assets/locked-CHodD5F8.js 2.59 kB │ gzip: 1.43 kB │ map: 4.00 kB\n../public/vue-assets/assets/DrawerWidget-BPC-tCgo.js 2.68 kB │ gzip: 1.33 kB │ map: 6.95 kB\n../public/vue-assets/assets/vue-infinite-scroll-D0cI4gH6.js 2.72 kB │ gzip: 1.28 kB │ map: 9.99 kB\n../public/vue-assets/assets/useActivityCustomerName-CYuaZj-p.js 2.87 kB │ gzip: 1.41 kB │ map: 6.27 kB\n../public/vue-assets/assets/other-CS9P7JeF.js 2.97 kB │ gzip: 1.50 kB │ map: 3.38 kB\n../public/vue-assets/assets/InputDropdown-CBM8xiPT.js 3.33 kB │ gzip: 1.61 kB │ map: 6.02 kB\n../public/vue-assets/assets/AvatarsStack-hxex9Ic8.js 3.40 kB │ gzip: 1.51 kB │ map: 9.54 kB\n../public/vue-assets/assets/activity-preview-Dkxw0lkq.js 3.45 kB │ gzip: 1.68 kB │ map: 10.11 kB\n../public/vue-assets/assets/FollowModal-SMYvVwqG.js 3.70 kB │ gzip: 1.81 kB │ map: 8.86 kB\n../public/vue-assets/assets/softphone-coach-BdRuUFYY.js 3.74 kB │ gzip: 1.96 kB │ map: 5.03 kB\n../public/vue-assets/assets/store-Cploop1A.js 4.10 kB │ gzip: 2.00 kB │ map: 18.73 kB\n../public/vue-assets/assets/AiContext-Cz-Y-wOJ.js 4.34 kB │ gzip: 2.08 kB │ map: 13.11 kB\n../public/vue-assets/assets/meeting-consent-DZKBS9eW.js 4.43 kB │ gzip: 2.05 kB │ map: 9.72 kB\n../public/vue-assets/assets/vue-multiselect-VB2Agtp1.js 4.49 kB │ gzip: 1.93 kB │ map: 14.85 kB\n../public/vue-assets/assets/snackbarNotifications-DjDIGkWd.js 4.83 kB │ gzip: 1.58 kB │ map: 11.62 kB\n../public/vue-assets/assets/invitation-DUWFWQvr.js 4.88 kB │ gzip: 2.21 kB │ map: 19.38 kB\n../public/vue-assets/assets/textarea-caret-B2HdIdpZ.js 5.25 kB │ gzip: 2.41 kB │ map: 14.48 kB\n../public/vue-assets/assets/connect-LR_dB_VC.js 5.25 kB │ gzip: 2.30 kB │ map: 10.52 kB\n../public/vue-assets/assets/snackbar-Bq-YQ7pz.js 5.48 kB │ gzip: 2.37 kB │ map: 20.43 kB\n../public/vue-assets/assets/RadioField-2SeSZB7D.js 5.65 kB │ gzip: 2.22 kB │ map: 15.82 kB\n../public/vue-assets/assets/filters-D_eXZdIL.js 5.76 kB │ gzip: 2.17 kB │ map: 16.36 kB\n../public/vue-assets/assets/useActivityHelper-C6KkT8e1.js 5.84 kB │ gzip: 2.51 kB │ map: 14.26 kB\n../public/vue-assets/assets/vue-scrollto-ul3pFdrb.js 5.97 kB │ gzip: 2.72 kB │ map: 23.98 kB\n../public/vue-assets/assets/join-conference-D5dWNU9i.js 6.25 kB │ gzip: 2.82 kB │ map: 24.66 kB\n../public/vue-assets/assets/pluralize-BeKVJaE0.js 6.34 kB │ gzip: 2.70 kB │ map: 18.28 kB\n../public/vue-assets/assets/login-DGWP8lD_.js 7.02 kB │ gzip: 3.16 kB │ map: 17.51 kB\n../public/vue-assets/assets/AppLinks-Bg20Hslb.js 7.14 kB │ gzip: 2.80 kB │ map: 14.87 kB\n../public/vue-assets/assets/InputField-CkdrqKBp.js 7.20 kB │ gzip: 2.58 kB │ map: 19.39 kB\n../public/vue-assets/assets/UserAvatar-UKkSAS4R.js 7.93 kB │ gzip: 3.32 kB │ map: 34.91 kB\n../public/vue-assets/assets/InputText-zafDvh_H.js 7.99 kB │ gzip: 3.05 kB │ map: 20.64 kB\n../public/vue-assets/assets/AiCrmNotes-Co9u0t34.js 8.27 kB │ gzip: 3.32 kB │ map: 34.03 kB\n../public/vue-assets/assets/vue-mq-CmpUzQtD.js 8.84 kB │ gzip: 3.54 kB │ map: 23.69 kB\n../public/vue-assets/assets/pro-duotone-svg-icons-BSBZV3-t.js 9.51 kB │ gzip: 3.43 kB │ map: 3,146.68 kB\n../public/vue-assets/assets/activity-preview-result-C820ZfzV.js 10.65 kB │ gzip: 3.98 kB │ map: 38.51 kB\n../public/vue-assets/assets/pro-regular-svg-icons-CWpSBR20.js 13.10 kB │ gzip: 4.64 kB │ map: 3,178.15 kB\n../public/vue-assets/assets/add-to-playlist-modal-CfDB_Ohb.js 14.99 kB │ gzip: 5.98 kB │ map: 51.34 kB\n../public/vue-assets/assets/ai-reports-BhbjZb-1.js 15.17 kB │ gzip: 6.04 kB │ map: 51.85 kB\n../public/vue-assets/assets/export-portal-BM3w9d6h.js 15.68 kB │ gzip: 6.22 kB │ map: 52.78 kB\n../public/vue-assets/assets/Comment-Kj7lCx0-.js 15.92 kB │ gzip: 5.72 kB │ map: 39.96 kB\n../public/vue-assets/assets/useragent-DQ4F_O30.js 18.02 kB │ gzip: 8.06 kB │ map: 68.31 kB\n../public/vue-assets/assets/linkify-B_q6MgwI.js 18.58 kB │ gzip: 10.37 kB │ map: 88.73 kB\n../public/vue-assets/assets/ai-reports-manage-ulzm9gk2.js 18.92 kB │ gzip: 6.76 kB │ map: 66.14 kB\n../public/vue-assets/assets/dist-C2hisF0L.js 19.32 kB │ gzip: 7.87 kB │ map: 390.77 kB\n../public/vue-assets/assets/pro-solid-svg-icons-DBOkpln3.js 19.46 kB │ gzip: 6.72 kB │ map: 2,692.14 kB\n../public/vue-assets/assets/purify.es-BxOw6oE6.js 21.49 kB │ gzip: 8.71 kB │ map: 83.43 kB\n../public/vue-assets/assets/ActionItems-C9INej-u.js 21.92 kB │ gzip: 8.54 kB │ map: 76.05 kB\n../public/vue-assets/assets/mobile-C-6FGhqy.js 23.34 kB │ gzip: 9.71 kB │ map: 50.90 kB\n../public/vue-assets/assets/basic-modal-BdXsnFwC.js 23.87 kB │ gzip: 9.06 kB │ map: 64.15 kB\n../public/vue-assets/assets/GridView-mZhlfeUe.js 26.60 kB │ gzip: 10.05 kB │ map: 92.74 kB\n../public/vue-assets/assets/ondemand-mq9zQOVK.js 26.88 kB │ gzip: 9.39 kB │ map: 73.94 kB\n../public/vue-assets/assets/CrmLink-DKYsnHnx.js 27.91 kB │ gzip: 10.18 kB │ map: 93.18 kB\n../public/vue-assets/assets/liquor-tree-COUefof4.js 30.75 kB │ gzip: 9.58 kB │ map: 78.74 kB\n../public/vue-assets/assets/DealRiskList-BUgW-21G.js 34.39 kB │ gzip: 10.62 kB │ map: 115.18 kB\n../public/vue-assets/assets/AskAnything-CVCNDjsB.js 39.50 kB │ gzip: 14.97 kB │ map: 173.20 kB\n../public/vue-assets/assets/lib-CwM9toD2.js 39.69 kB │ gzip: 12.70 kB │ map: 138.34 kB\n../public/vue-assets/assets/AppFormField-Cu4TbCsZ.js 41.91 kB │ gzip: 12.69 kB │ map: 150.73 kB\n../public/vue-assets/assets/deal-view-CoZZkbGt.js 43.22 kB │ gzip: 14.34 kB │ map: 150.62 kB\n../public/vue-assets/assets/exports-D1lmea4O.js 47.84 kB │ gzip: 16.46 kB │ map: 294.48 kB\n../public/vue-assets/assets/playlists-BpFWtNz1.js 48.28 kB │ gzip: 15.08 kB │ map: 153.25 kB\n../public/vue-assets/assets/callScoringTemplates-zeRn40uL.js 55.13 kB │ gzip: 13.28 kB │ map: 65.85 kB\n../public/vue-assets/assets/_copyObject-USkOnlaQ.js 61.28 kB │ gzip: 20.09 kB │ map: 239.59 kB\n../public/vue-assets/assets/pusher-znYCfz7U.js 62.98 kB │ gzip: 18.88 kB │ map: 219.27 kB\n../public/vue-assets/assets/onboard-D27tiAEp.js 63.11 kB │ gzip: 21.85 kB │ map: 201.38 kB\n../public/vue-assets/assets/StatusBadge-Dmn6sJ5_.js 64.66 kB │ gzip: 22.96 kB │ map: 244.72 kB\n../public/vue-assets/assets/kiosk-DO4wa1Ld.js 79.60 kB │ gzip: 22.65 kB │ map: 300.68 kB\n../public/vue-assets/assets/preload-helper-DCvhahzG.js 82.59 kB │ gzip: 27.46 kB │ map: 3,452.35 kB\n../public/vue-assets/assets/deal-insights-BKxR7Ag9.js 94.84 kB │ gzip: 28.17 kB │ map: 292.79 kB\n../public/vue-assets/assets/ListView-V1cngeeb.js 115.71 kB │ gzip: 33.78 kB │ map: 308.10 kB\n../public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js 117.59 kB │ gzip: 38.71 kB │ map: 500.60 kB\n../public/vue-assets/assets/WelcomeLayout-B6wd32HG.js 120.67 kB │ gzip: 34.16 kB │ map: 258.56 kB\n../public/vue-assets/assets/dashboard-D0nLYfJh.js 128.71 kB │ gzip: 40.05 kB │ map: 410.48 kB\n../public/vue-assets/assets/emoji-input-CSq87OVy.js 129.28 kB │ gzip: 36.72 kB │ map: 266.15 kB\n../public/vue-assets/assets/AppButton-D3qMdODr.js 133.44 kB │ gzip: 43.03 kB │ map: 516.67 kB\n../public/vue-assets/assets/sentry-zRF85Jni.js 164.28 kB │ gzip: 52.24 kB │ map: 831.82 kB\n../public/vue-assets/assets/OrgSettingsLayout-DXmpAhyV.js 176.33 kB │ gzip: 56.15 kB │ map: 623.43 kB\n../public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js 180.40 kB │ gzip: 67.85 kB │ map: 836.88 kB\n../public/vue-assets/assets/playback-BerPTevk.js 198.79 kB │ gzip: 61.85 kB │ map: 684.87 kB\n../public/vue-assets/assets/index.module-Bjlhgfd1.js 218.14 kB │ gzip: 64.16 kB │ map: 1,108.20 kB\n../public/vue-assets/assets/intl-tel-input-BW4mv40Q.js 264.94 kB │ gzip: 60.31 kB │ map: 475.61 kB\n../public/vue-assets/assets/team-insights-Rfo09TvE.js 298.57 kB │ gzip: 77.21 kB │ map: 959.96 kB\n../public/vue-assets/assets/popper-CQwVcrX4.js 307.13 kB │ gzip: 103.86 kB │ map: 1,245.28 kB\n../public/vue-assets/assets/PhoneField-CwCIoAYm.js 343.99 kB │ gzip: 84.90 kB │ map: 849.05 kB\n../public/vue-assets/assets/live-BkQge7FT.js 367.43 kB │ gzip: 97.05 kB │ map: 792.41 kB\n../public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BNO485xV.js 689.63 kB │ gzip: 202.81 kB │ map: 3,016.64 kB\n../public/vue-assets/assets/index-RakUX3TF.js 825.23 kB │ gzip: 72.54 kB │ map: 436.62 kB\n../public/vue-assets/assets/logged-in-layout-WZXlxzkM.js 1,402.70 kB │ gzip: 438.07 kB │ map: 6,283.55 kB\n\n[plugin builtin:vite-reporter] \n(!) Some chunks are larger than 500 kB after minification. Consider:\n- Using dynamic import() to code-split the application\n- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting\n- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.\n✓ built in 25.43s\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $ yarn build\n[dotenv@17.3.1] injecting env (0) from .env -- tip: ⚡️\u0000 secrets for agents: https://dotenvx.com/as2\nvite v8.0.0 building client environment for production...\n✓ 4656 modules transformed.\n[sentry-vite-plugin] Warning: No auth token provided. Will not create release. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\n[sentry-vite-plugin] Warning: No auth token provided. Will not upload source maps. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\ncomputing gzip size...\n../public/vue-assets/index.html 4.58 kB │ gzip: 1.16 kB\n../public/vue-assets/assets/job-adder-B2LcgC4o.png 5.41 kB\n../public/vue-assets/assets/dixa-DzT89tKh.png 7.87 kB\n../public/vue-assets/assets/planhat-CQycOTMW.svg 8.61 kB │ gzip: 3.48 kB\n../public/vue-assets/assets/funding-circle-9iGnDGsz.png 9.43 kB\n../public/vue-assets/assets/cision-uOdx_YiN.svg 14.41 kB │ gzip: 6.32 kB\n../public/vue-assets/assets/les-mills-DWabpmYc.png 15.73 kB\n../public/vue-assets/assets/superside-btT37L9L.png 26.69 kB\n../public/vue-assets/assets/flags-a2kmUSbF.webp 28.18 kB\n../public/vue-assets/assets/quinyx-RGQNHx6o.png 63.17 kB\n../public/vue-assets/assets/flags@2x-gR6KPp3x.webp 66.44 kB\n../public/vue-assets/assets/bg-marketing-CAIHIJMj.png 70.66 kB\n../public/vue-assets/.vite/manifest.json 85.73 kB │ gzip: 8.32 kB\n../public/vue-assets/assets/jiminny-score-DtTbKZaD.svg 131.93 kB │ gzip: 86.07 kB\n../public/vue-assets/assets/wavy-bg-vnCaCm9m.webm 670.97 kB\n../public/vue-assets/assets/wavy-bg-Op6dRIjR.mp4 14,484.63 kB\n../public/vue-assets/assets/AppForm-EEGPL5K8.css 0.04 kB │ gzip: 0.06 kB\n../public/vue-assets/assets/RecipientsCell-DNOSS8k1.css 0.12 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/softphone-coach-Cy_9nuQZ.css 0.13 kB │ gzip: 0.10 kB\n../public/vue-assets/assets/locked-CHS8VPur.css 0.15 kB │ gzip: 0.12 kB\n../public/vue-assets/assets/mobile-KPnGhnTp.css 0.16 kB │ gzip: 0.14 kB\n../public/vue-assets/assets/BuildInfo-CxNQv_OV.css 0.20 kB │ gzip: 0.17 kB\n../public/vue-assets/assets/AvatarsStack-C0Nvfh1V.css 0.28 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/LogoLong100-ByQTvUY3.css 0.32 kB │ gzip: 0.20 kB\n../public/vue-assets/assets/connect--xBDzLbc.css 0.34 kB │ gzip: 0.21 kB\n../public/vue-assets/assets/KioskBanner-xU3FRd8g.css 0.41 kB │ gzip: 0.27 kB\n../public/vue-assets/assets/extension-installed-BPBhG13P.css 0.41 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/FollowModal-A0rJmIOK.css 0.45 kB │ gzip: 0.23 kB\n../public/vue-assets/assets/GenericMessage-DutAlObK.css 0.49 kB │ gzip: 0.26 kB\n../public/vue-assets/assets/textarea-caret-DCiNUER_.css 0.51 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/AppLinks-BznfGcBR.css 0.58 kB │ gzip: 0.33 kB\n../public/vue-assets/assets/SeekBtn-_VMPIk6v.css 0.63 kB │ gzip: 0.32 kB\n../public/vue-assets/assets/emoji-input-kJWRbWf8.css 0.68 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/activity-preview-BDxP2S0-.css 0.70 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/ListLoader-DQqAw-8s.css 0.73 kB │ gzip: 0.38 kB\n../public/vue-assets/assets/filters-Bgb6cQD6.css 0.75 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/AiContext-Bg-zjjbA.css 0.99 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/vue3-daterange-picker-izPOBj3P.css 1.02 kB │ gzip: 0.39 kB\n../public/vue-assets/assets/PrimaryButton-CJj2FzKS.css 1.08 kB │ gzip: 0.34 kB\n../public/vue-assets/assets/other-BTuPXtu_.css 1.15 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/jenesius-vue-modal-DDcfejHO.css 1.17 kB │ gzip: 0.48 kB\n../public/vue-assets/assets/TabEmptyState-DOlIO68k.css 1.21 kB │ gzip: 0.53 kB\n../public/vue-assets/assets/AppAlert-BlSJKiqj.css 1.29 kB │ gzip: 0.41 kB\n../public/vue-assets/assets/useActivityCustomerName-CBjfIUfJ.css 1.42 kB │ gzip: 0.45 kB\n../public/vue-assets/assets/UserAvatar-co8l9mMk.css 1.51 kB │ gzip: 0.49 kB\n../public/vue-assets/assets/basic-modal-Dx7ZBbcA.css 1.55 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/login-DYbcBesM.css 1.58 kB │ gzip: 0.61 kB\n../public/vue-assets/assets/DrawerWidget-poAmqspk.css 1.67 kB │ gzip: 0.69 kB\n../public/vue-assets/assets/ActionItems-5FkZSo3G.css 1.79 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/join-conference-DQ6Yl7eU.css 1.82 kB │ gzip: 0.75 kB\n../public/vue-assets/assets/InputText-C-DxDNSV.css 1.95 kB │ gzip: 0.71 kB\n../public/vue-assets/assets/GoogleLikeButton-DvQHxbqR.css 2.11 kB │ gzip: 0.66 kB\n../public/vue-assets/assets/usePusherEventListener-CDj8aSNp.css 2.20 kB │ gzip: 0.73 kB\n../public/vue-assets/assets/onboard-IzGiQ-EC.css 2.32 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/ai-reports-manage-DaynTl62.css 2.34 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/StatusBadge-B4N12dzU.css 2.35 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/GridView-CsP1Jk-k.css 2.36 kB │ gzip: 0.90 kB\n../public/vue-assets/assets/WelcomeLayout-6AX86p6F.css 2.38 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/snackbar-CS_iqvrv.css 2.45 kB │ gzip: 0.80 kB\n../public/vue-assets/assets/ai-reports-mtqGqPPq.css 2.47 kB │ gzip: 0.91 kB\n../public/vue-assets/assets/Comment-gmAfiuer.css 2.63 kB │ gzip: 1.00 kB\n../public/vue-assets/assets/meeting-consent-BasRvxE3.css 2.66 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/DealRiskList-CwxmDnSC.css 2.67 kB │ gzip: 0.96 kB\n../public/vue-assets/assets/add-to-playlist-modal-C_ad1hht.css 2.84 kB │ gzip: 0.95 kB\n../public/vue-assets/assets/live-_zT5qBc1.css 3.60 kB │ gzip: 1.16 kB\n../public/vue-assets/assets/RadioField-DDp8Y71G.css 3.64 kB │ gzip: 1.20 kB\n../public/vue-assets/assets/vue-multiselect-BkQNV7oL.css 3.80 kB │ gzip: 0.86 kB\n../public/vue-assets/assets/activity-preview-result-Db1Da81T.css 3.89 kB │ gzip: 1.15 kB\n../public/vue-assets/assets/liquor-tree-CB5oh8v1.css 4.08 kB │ gzip: 1.24 kB\n../public/vue-assets/assets/sentry-CSjHcvmn.css 4.47 kB │ gzip: 1.08 kB\n../public/vue-assets/assets/AiAutomation-Cj1t8v5y.css 4.61 kB │ gzip: 0.84 kB\n../public/vue-assets/assets/export-portal-CyIUVTEe.css 5.34 kB │ gzip: 1.65 kB\n../public/vue-assets/assets/InputField-C4hoKJ7Z.css 6.13 kB │ gzip: 1.27 kB\n../public/vue-assets/assets/vue-mq-bh4L87Tr.css 6.64 kB │ gzip: 1.95 kB\n../public/vue-assets/assets/invitation-EPR1t-bH.css 6.74 kB │ gzip: 2.22 kB\n../public/vue-assets/assets/ondemand-C2m0YVGA.css 6.84 kB │ gzip: 2.02 kB\n../public/vue-assets/assets/AskAnything-CyvEhHS1.css 8.60 kB │ gzip: 2.60 kB\n../public/vue-assets/assets/AppButton-BIeoar_U.css 8.88 kB │ gzip: 2.04 kB\n../public/vue-assets/assets/kiosk-BcheyWWL.css 9.56 kB │ gzip: 2.53 kB\n../public/vue-assets/assets/AiCrmNotes-nNTe_mOY.css 10.62 kB │ gzip: 1.93 kB\n../public/vue-assets/assets/deal-view-DJzG7PLp.css 11.70 kB │ gzip: 3.29 kB\n../public/vue-assets/assets/tokens-DZgWrfKd.css 12.44 kB │ gzip: 2.82 kB\n../public/vue-assets/assets/tokens-DL2BPbai.css 12.89 kB │ gzip: 2.87 kB\n../public/vue-assets/assets/playlists-Cy3t3kYU.css 13.54 kB │ gzip: 3.16 kB\n../public/vue-assets/assets/PhoneField-D_kGOah0.css 13.75 kB │ gzip: 3.44 kB\n../public/vue-assets/assets/ListView-Dt0qkyuY.css 17.07 kB │ gzip: 4.11 kB\n../public/vue-assets/assets/intl-tel-input-DgmgTINs.css 17.11 kB │ gzip: 5.37 kB\n../public/vue-assets/assets/AppFormField-DvpfRzi8.css 18.98 kB │ gzip: 4.42 kB\n../public/vue-assets/assets/OrgSettingsLayout-BogdXtgY.css 21.98 kB │ gzip: 5.51 kB\n../public/vue-assets/assets/dashboard-DQw3TKyk.css 22.74 kB │ gzip: 4.35 kB\n../public/vue-assets/assets/deal-insights-CfX3UNzh.css 29.66 kB │ gzip: 6.44 kB\n../public/vue-assets/assets/team-insights-CjVhm0JN.css 42.31 kB │ gzip: 8.46 kB\n../public/vue-assets/assets/playback-CvJP5tX1.css 44.55 kB │ gzip: 10.36 kB\n../public/vue-assets/assets/video-js-skin-DYZluGb-.css 47.73 kB │ gzip: 12.63 kB\n../public/vue-assets/assets/assets-CAbfI4CY.css 83.38 kB │ gzip: 51.71 kB\n../public/vue-assets/assets/logged-in-layout-1mAqkmnS.css 153.67 kB │ gzip: 27.18 kB\n../public/vue-assets/assets/assets-xH6dx_9q.css 259.71 kB │ gzip: 181.71 kB\n../public/vue-assets/assets/directives-DJJeJJOP.js 0.53 kB │ gzip: 0.36 kB │ map: 0.32 kB\n../public/vue-assets/assets/jenesius-vue-modal-BuBhyl83.js 0.54 kB │ gzip: 0.37 kB │ map: 0.47 kB\n../public/vue-assets/assets/spark-D_-Wgfar.js 0.54 kB │ gzip: 0.35 kB │ map: 0.40 kB\n../public/vue-assets/assets/url-messenger-_CGQa-lH.js 0.56 kB │ gzip: 0.37 kB │ map: 0.46 kB\n../public/vue-assets/assets/wavy-bg-Cec-_GDj.js 0.57 kB │ gzip: 0.36 kB │ map: 0.35 kB\n../public/vue-assets/assets/component-css-class-CtO0AVgW.js 0.61 kB │ gzip: 0.40 kB │ map: 0.91 kB\n../public/vue-assets/assets/theme-Cy-WIInU.js 0.63 kB │ gzip: 0.42 kB │ map: 0.49 kB\n../public/vue-assets/assets/pick-BGRRe-Qj.js 0.71 kB │ gzip: 0.45 kB │ map: 1.59 kB\n../public/vue-assets/assets/utils-BjsEoLDB.js 0.77 kB │ gzip: 0.50 kB │ map: 1.68 kB\n../public/vue-assets/assets/throttle-DmtOZ8h1.js 0.78 kB │ gzip: 0.49 kB │ map: 3.24 kB\n../public/vue-assets/assets/lastFilters-CzSBGyMt.js 0.80 kB │ gzip: 0.51 kB │ map: 1.12 kB\n../public/vue-assets/assets/useAuthState-Bpx8WpBc.js 0.81 kB │ gzip: 0.50 kB │ map: 1.64 kB\n../public/vue-assets/assets/v-focus-618yf9WO.js 0.85 kB │ gzip: 0.52 kB │ map: 1.42 kB\n../public/vue-assets/assets/ListLoader-bfQmkVGu.js 0.87 kB │ gzip: 0.54 kB │ map: 2.90 kB\n../public/vue-assets/assets/utils-CAMifZzY.js 0.88 kB │ gzip: 0.53 kB │ map: 2.45 kB\n../public/vue-assets/assets/mobileApp-QFLS3kId.js 0.89 kB │ gzip: 0.54 kB │ map: 1.12 kB\n../public/vue-assets/assets/pickBy-BtwCuAYd.js 0.95 kB │ gzip: 0.59 kB │ map: 2.57 kB\n../public/vue-assets/assets/BuildInfo-CIGre86L.js 1.00 kB │ gzip: 0.65 kB │ map: 1.48 kB\n../public/vue-assets/assets/LogoShort100-D_SQdBYB.js 1.01 kB │ gzip: 0.64 kB │ map: 1.23 kB\n../public/vue-assets/assets/useDrawerModal-CmhXI-pZ.js 1.04 kB │ gzip: 0.65 kB │ map: 1.94 kB\n../public/vue-assets/assets/_getAllKeysIn-CkMQAnJa.js 1.12 kB │ gzip: 0.67 kB │ map: 4.83 kB\n../public/vue-assets/assets/useAutosizeTextarea-DyMwh_20.js 1.13 kB │ gzip: 0.70 kB │ map: 3.16 kB\n../public/vue-assets/assets/planhat-DPA36Hcn.js 1.21 kB │ gzip: 0.71 kB │ map: 2.18 kB\n../public/vue-assets/assets/useStoreModule-Cx6UoGVJ.js 1.35 kB │ gzip: 0.72 kB │ map: 6.03 kB\n../public/vue-assets/assets/GenericMessage-BxGsrfvw.js 1.39 kB │ gzip: 0.75 kB │ map: 1.90 kB\n../public/vue-assets/assets/BrowserExtensionInstaller-DimyBPIi.js 1.45 kB │ gzip: 0.78 kB │ map: 2.05 kB\n../public/vue-assets/assets/debounce-DG3xxJjJ.js 1.53 kB │ gzip: 0.83 kB │ map: 8.61 kB\n../public/vue-assets/assets/extension-installed-mV_4t574.js 1.60 kB │ gzip: 0.91 kB │ map: 3.20 kB\n../public/vue-assets/assets/RecipientsCell-BS9gDGey.js 1.61 kB │ gzip: 0.93 kB │ map: 4.04 kB\n../public/vue-assets/assets/PrimaryButton-DaKR3UAG.js 1.61 kB │ gzip: 0.87 kB │ map: 3.73 kB\n../public/vue-assets/assets/KioskBanner-g60shJd4.js 1.66 kB │ gzip: 1.00 kB │ map: 2.88 kB\n../public/vue-assets/assets/LogoLong100-Hid0kvjR.js 1.78 kB │ gzip: 1.08 kB │ map: 5.91 kB\n../public/vue-assets/assets/GoogleLikeButton-CGFy3nbz.js 1.80 kB │ gzip: 0.84 kB │ map: 4.71 kB\n../public/vue-assets/assets/AppAlert-DFp0nftl.js 1.86 kB │ gzip: 1.01 kB │ map: 6.51 kB\n../public/vue-assets/assets/usePusherEventListener-Z27Z5klt.js 1.93 kB │ gzip: 1.08 kB │ map: 9.59 kB\n../public/vue-assets/assets/AppForm-BgtH5xsM.js 2.01 kB │ gzip: 1.10 kB │ map: 6.89 kB\n../public/vue-assets/assets/vee-validate-rules-IFeGZHb3.js 2.02 kB │ gzip: 0.89 kB │ map: 22.31 kB\n../public/vue-assets/assets/literals-CXSwYC8y.js 2.05 kB │ gzip: 0.97 kB │ map: 3.08 kB\n../public/vue-assets/assets/TabEmptyState-pk8vRxJt.js 2.16 kB │ gzip: 1.12 kB │ map: 8.26 kB\n../public/vue-assets/assets/Replies-Cn_qGwMd.js 2.23 kB │ gzip: 1.14 kB │ map: 2.40 kB\n../public/vue-assets/assets/settings-BSSIkavG.js 2.34 kB │ gzip: 1.24 kB │ map: 1.73 kB\n../public/vue-assets/assets/SeekBtn-CMp8sSUA.js 2.47 kB │ gzip: 1.18 kB │ map: 9.27 kB\n../public/vue-assets/assets/AiAutomation-C5_UlocO.js 2.59 kB │ gzip: 1.37 kB │ map: 4.45 kB\n../public/vue-assets/assets/locked-Bad3U1k5.js 2.59 kB │ gzip: 1.43 kB │ map: 4.00 kB\n../public/vue-assets/assets/DrawerWidget-BPC-tCgo.js 2.68 kB │ gzip: 1.33 kB │ map: 6.95 kB\n../public/vue-assets/assets/vue-infinite-scroll-D0cI4gH6.js 2.72 kB │ gzip: 1.28 kB │ map: 9.99 kB\n../public/vue-assets/assets/useActivityCustomerName-CYuaZj-p.js 2.87 kB │ gzip: 1.41 kB │ map: 6.27 kB\n../public/vue-assets/assets/other-D11UHzKP.js 2.97 kB │ gzip: 1.51 kB │ map: 3.38 kB\n../public/vue-assets/assets/InputDropdown-CBM8xiPT.js 3.33 kB │ gzip: 1.61 kB │ map: 6.02 kB\n../public/vue-assets/assets/AvatarsStack-hxex9Ic8.js 3.40 kB │ gzip: 1.51 kB │ map: 9.54 kB\n../public/vue-assets/assets/activity-preview-CoaCdIkx.js 3.45 kB │ gzip: 1.68 kB │ map: 10.11 kB\n../public/vue-assets/assets/FollowModal-SMYvVwqG.js 3.70 kB │ gzip: 1.81 kB │ map: 8.86 kB\n../public/vue-assets/assets/softphone-coach-C452yEnj.js 3.74 kB │ gzip: 1.96 kB │ map: 5.03 kB\n../public/vue-assets/assets/store-CloJVGCY.js 4.10 kB │ gzip: 2.00 kB │ map: 18.73 kB\n../public/vue-assets/assets/AiContext-DJ4M3rJ1.js 4.34 kB │ gzip: 2.08 kB │ map: 13.11 kB\n../public/vue-assets/assets/meeting-consent-B24mqvl1.js 4.43 kB │ gzip: 2.05 kB │ map: 9.72 kB\n../public/vue-assets/assets/vue-multiselect-VB2Agtp1.js 4.49 kB │ gzip: 1.93 kB │ map: 14.85 kB\n../public/vue-assets/assets/snackbarNotifications-DjDIGkWd.js 4.83 kB │ gzip: 1.58 kB │ map: 11.62 kB\n../public/vue-assets/assets/invitation-B5F0aLwl.js 4.88 kB │ gzip: 2.21 kB │ map: 19.38 kB\n../public/vue-assets/assets/textarea-caret-B2HdIdpZ.js 5.25 kB │ gzip: 2.41 kB │ map: 14.48 kB\n../public/vue-assets/assets/connect-DvCp38Q2.js 5.31 kB │ gzip: 2.31 kB │ map: 10.62 kB\n../public/vue-assets/assets/snackbar-Bq-YQ7pz.js 5.48 kB │ gzip: 2.37 kB │ map: 20.43 kB\n../public/vue-assets/assets/RadioField-2SeSZB7D.js 5.65 kB │ gzip: 2.22 kB │ map: 15.82 kB\n../public/vue-assets/assets/filters-D_eXZdIL.js 5.76 kB │ gzip: 2.17 kB │ map: 16.36 kB\n../public/vue-assets/assets/useActivityHelper-BvWVN0nz.js 5.84 kB │ gzip: 2.51 kB │ map: 14.26 kB\n../public/vue-assets/assets/vue-scrollto-ul3pFdrb.js 5.97 kB │ gzip: 2.72 kB │ map: 23.98 kB\n../public/vue-assets/assets/join-conference-CLdXMCwD.js 6.25 kB │ gzip: 2.83 kB │ map: 24.66 kB\n../public/vue-assets/assets/pluralize-BeKVJaE0.js 6.34 kB │ gzip: 2.70 kB │ map: 18.28 kB\n../public/vue-assets/assets/login-PrWVOMsr.js 7.02 kB │ gzip: 3.17 kB │ map: 17.51 kB\n../public/vue-assets/assets/AppLinks-Bg20Hslb.js 7.14 kB │ gzip: 2.80 kB │ map: 14.87 kB\n../public/vue-assets/assets/InputField-CkdrqKBp.js 7.20 kB │ gzip: 2.58 kB │ map: 19.39 kB\n../public/vue-assets/assets/UserAvatar-UKkSAS4R.js 7.93 kB │ gzip: 3.32 kB │ map: 34.91 kB\n../public/vue-assets/assets/InputText-BH9WFuby.js 7.99 kB │ gzip: 3.05 kB │ map: 20.64 kB\n../public/vue-assets/assets/AiCrmNotes-DmPJHAz_.js 8.27 kB │ gzip: 3.32 kB │ map: 34.03 kB\n../public/vue-assets/assets/vue-mq-CmpUzQtD.js 8.84 kB │ gzip: 3.54 kB │ map: 23.69 kB\n../public/vue-assets/assets/pro-duotone-svg-icons-BSBZV3-t.js 9.51 kB │ gzip: 3.43 kB │ map: 3,146.68 kB\n../public/vue-assets/assets/activity-preview-result-CQou30yT.js 10.65 kB │ gzip: 3.98 kB │ map: 38.51 kB\n../public/vue-assets/assets/pro-regular-svg-icons-CWpSBR20.js 13.10 kB │ gzip: 4.64 kB │ map: 3,178.15 kB\n../public/vue-assets/assets/add-to-playlist-modal-BS8w3ovp.js 14.99 kB │ gzip: 5.98 kB │ map: 51.34 kB\n../public/vue-assets/assets/ai-reports-Cy_xmRg8.js 15.17 kB │ gzip: 6.04 kB │ map: 51.85 kB\n../public/vue-assets/assets/export-portal-CZ2b-FlS.js 15.68 kB │ gzip: 6.22 kB │ map: 52.78 kB\n../public/vue-assets/assets/Comment-CdvjTszI.js 15.92 kB │ gzip: 5.72 kB │ map: 39.96 kB\n../public/vue-assets/assets/useragent-DQ4F_O30.js 18.02 kB │ gzip: 8.06 kB │ map: 68.31 kB\n../public/vue-assets/assets/linkify-B_q6MgwI.js 18.58 kB │ gzip: 10.37 kB │ map: 88.73 kB\n../public/vue-assets/assets/ai-reports-manage-B1v5HdqS.js 18.92 kB │ gzip: 6.76 kB │ map: 66.14 kB\n../public/vue-assets/assets/dist-C2hisF0L.js 19.32 kB │ gzip: 7.87 kB │ map: 390.77 kB\n../public/vue-assets/assets/pro-solid-svg-icons-DBOkpln3.js 19.46 kB │ gzip: 6.72 kB │ map: 2,692.14 kB\n../public/vue-assets/assets/purify.es-BxOw6oE6.js 21.49 kB │ gzip: 8.71 kB │ map: 83.43 kB\n../public/vue-assets/assets/ActionItems-uKCSfiLo.js 21.92 kB │ gzip: 8.55 kB │ map: 76.05 kB\n../public/vue-assets/assets/mobile-zCtNIOGN.js 23.34 kB │ gzip: 9.71 kB │ map: 50.90 kB\n../public/vue-assets/assets/basic-modal-BdXsnFwC.js 23.87 kB │ gzip: 9.06 kB │ map: 64.15 kB\n../public/vue-assets/assets/GridView-D5_z7YdA.js 26.60 kB │ gzip: 10.05 kB │ map: 92.74 kB\n../public/vue-assets/assets/ondemand-mxFxJiHI.js 26.88 kB │ gzip: 9.39 kB │ map: 73.94 kB\n../public/vue-assets/assets/CrmLink-DKYsnHnx.js 27.91 kB │ gzip: 10.18 kB │ map: 93.18 kB\n../public/vue-assets/assets/liquor-tree-COUefof4.js 30.75 kB │ gzip: 9.58 kB │ map: 78.74 kB\n../public/vue-assets/assets/DealRiskList-D7gUVql5.js 34.39 kB │ gzip: 10.62 kB │ map: 115.18 kB\n../public/vue-assets/assets/AskAnything-DgI0NfRA.js 39.50 kB │ gzip: 14.97 kB │ map: 173.20 kB\n../public/vue-assets/assets/lib-CwM9toD2.js 39.69 kB │ gzip: 12.70 kB │ map: 138.34 kB\n../public/vue-assets/assets/AppFormField-BqQiLGFF.js 41.91 kB │ gzip: 12.69 kB │ map: 150.73 kB\n../public/vue-assets/assets/deal-view-D4YlPwr_.js 43.22 kB │ gzip: 14.35 kB │ map: 150.62 kB\n../public/vue-assets/assets/exports-D1lmea4O.js 47.84 kB │ gzip: 16.46 kB │ map: 294.48 kB\n../public/vue-assets/assets/playlists-BWAdERcJ.js 48.28 kB │ gzip: 15.07 kB │ map: 153.25 kB\n../public/vue-assets/assets/callScoringTemplates-zeRn40uL.js 55.13 kB │ gzip: 13.28 kB │ map: 65.85 kB\n../public/vue-assets/assets/_copyObject-USkOnlaQ.js 61.28 kB │ gzip: 20.09 kB │ map: 239.59 kB\n../public/vue-assets/assets/pusher-znYCfz7U.js 62.98 kB │ gzip: 18.88 kB │ map: 219.27 kB\n../public/vue-assets/assets/onboard-CyAPGoFk.js 63.11 kB │ gzip: 21.85 kB │ map: 201.38 kB\n../public/vue-assets/assets/StatusBadge-DNHiCr2i.js 64.66 kB │ gzip: 22.96 kB │ map: 244.72 kB\n../public/vue-assets/assets/kiosk-dfcpodo5.js 79.60 kB │ gzip: 22.65 kB │ map: 300.68 kB\n../public/vue-assets/assets/preload-helper-DCvhahzG.js 82.59 kB │ gzip: 27.46 kB │ map: 3,452.35 kB\n../public/vue-assets/assets/deal-insights-BVnPilVP.js 94.84 kB │ gzip: 28.17 kB │ map: 292.79 kB\n../public/vue-assets/assets/ListView-DJD6SV4A.js 115.71 kB │ gzip: 33.79 kB │ map: 308.10 kB\n../public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js 117.59 kB │ gzip: 38.71 kB │ map: 500.60 kB\n../public/vue-assets/assets/WelcomeLayout-B6wd32HG.js 120.67 kB │ gzip: 34.16 kB │ map: 258.56 kB\n../public/vue-assets/assets/dashboard-CsDOiLAi.js 128.71 kB │ gzip: 40.05 kB │ map: 410.48 kB\n../public/vue-assets/assets/emoji-input-CSq87OVy.js 129.28 kB │ gzip: 36.72 kB │ map: 266.15 kB\n../public/vue-assets/assets/AppButton-D3qMdODr.js 133.44 kB │ gzip: 43.03 kB │ map: 516.67 kB\n../public/vue-assets/assets/sentry-B3B1ZM6O.js 164.28 kB │ gzip: 52.24 kB │ map: 831.82 kB\n../public/vue-assets/assets/OrgSettingsLayout-DatDldIe.js 176.33 kB │ gzip: 56.15 kB │ map: 623.43 kB\n../public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js 180.40 kB │ gzip: 67.85 kB │ map: 836.88 kB\n../public/vue-assets/assets/playback-D1gm80qQ.js 198.79 kB │ gzip: 61.85 kB │ map: 684.87 kB\n../public/vue-assets/assets/index.module-Bjlhgfd1.js 218.14 kB │ gzip: 64.16 kB │ map: 1,108.20 kB\n../public/vue-assets/assets/intl-tel-input-BW4mv40Q.js 264.94 kB │ gzip: 60.31 kB │ map: 475.61 kB\n../public/vue-assets/assets/team-insights-DRugjYCA.js 298.57 kB │ gzip: 77.22 kB │ map: 959.96 kB\n../public/vue-assets/assets/popper-CQwVcrX4.js 307.13 kB │ gzip: 103.86 kB │ map: 1,245.28 kB\n../public/vue-assets/assets/PhoneField-CwCIoAYm.js 343.99 kB │ gzip: 84.90 kB │ map: 849.05 kB\n../public/vue-assets/assets/live-CxSmZv7h.js 367.43 kB │ gzip: 97.05 kB │ map: 792.41 kB\n../public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BNO485xV.js 689.63 kB │ gzip: 202.81 kB │ map: 3,016.64 kB\n../public/vue-assets/assets/index-Cp0YOK4U.js 825.23 kB │ gzip: 72.54 kB │ map: 436.62 kB\n../public/vue-assets/assets/logged-in-layout-CE9ox17M.js 1,402.70 kB │ gzip: 438.07 kB │ map: 6,283.55 kB\n\n✓ built in 26.01s\n[plugin builtin:vite-reporter] \n(!) Some chunks are larger than 500 kB after minification. Consider:\n- Using dynamic import() to code-split the application\n- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting\n- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-token-auth-response-change) $","is_focused":true},{"role":"AXRadioButton","text":"DOCKER","depth":2,"bounds":{"left":0.23359375,"top":1.0,"width":0.06914063,"height":-0.037500024},"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.2359375,"top":1.0,"width":0.00625,"height":-0.04027772},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"DEV (docker)","depth":2,"bounds":{"left":0.30273438,"top":1.0,"width":0.06914063,"height":-0.037500024},"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.30507812,"top":1.0,"width":0.00625,"height":-0.04027772},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"APP (-zsh)","depth":2,"bounds":{"left":0.371875,"top":1.0,"width":0.06914063,"height":-0.037500024},"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.37421876,"top":1.0,"width":0.00625,"height":-0.04027772},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.44101563,"top":1.0,"width":0.06914063,"height":-0.037500024},"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.44335938,"top":1.0,"width":0.00625,"height":-0.04027772},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.5101563,"top":1.0,"width":0.06914063,"height":-0.037500024},"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.5125,"top":1.0,"width":0.00625,"height":-0.04027772},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"✳ Review screenpipe usage and Boosteroid integration (claude)","depth":2,"bounds":{"left":0.5792969,"top":1.0,"width":0.06914063,"height":-0.037500024},"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.5816406,"top":1.0,"width":0.00625,"height":-0.04027772},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"ec2-user@ip-10-30-159-186:~ (nc)","depth":2,"bounds":{"left":0.6484375,"top":1.0,"width":0.06914063,"height":-0.037500024},"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.6507813,"top":1.0,"width":0.00625,"height":-0.04027772},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"ec2-user@ip-10-20-6-111:~ (nc)","depth":2,"bounds":{"left":0.7175781,"top":1.0,"width":0.06914063,"height":-0.037500024},"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.7199219,"top":1.0,"width":0.00625,"height":-0.04027772},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"⌥⌘1","depth":1,"bounds":{"left":0.77070314,"top":1.0,"width":0.021875,"height":-0.020833373},"automation_id":"_NS:8","role_description":"text"},{"role":"AXStaticText","text":"APP (-zsh)","depth":1,"bounds":{"left":0.50039065,"top":1.0,"width":0.02890625,"height":-0.021527767},"role_description":"text"}]...
|
-2407191479157132203
|
3429803486563842285
|
idle
|
accessibility
|
NULL
|
info Visit [URL_WITH_CREDENTIALS] ~/jiminny/app/fr info Visit [URL_WITH_CREDENTIALS] ~/jiminny/app/front-end (JY-20692-fix-integration-app-[API_KEY]) $ yanrn build
zsh: command not found: yanrn
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-[API_KEY]) $ yarn build
[dotenv@17.3.1] injecting env (0) from .env -- tip: ⚡️ secrets for agents: [URL_WITH_CREDENTIALS] ~/jiminny/app/front-end (JY-20692-fix-integration-app-[API_KEY]) $ yarn install
➤ YN0000: · Yarn 4.12.0
➤ YN0000: ┌ Resolution step
➤ YN0000: └ Completed
➤ YN0000: ┌ Post-resolution validation
➤ YN0002: │ root-workspace-0b6124@workspace:. doesn't provide @testing-library/dom (pea6ced), requested by @testing-library/user-event.
➤ YN0002: │ root-workspace-0b6124@workspace:. doesn't provide noty (p5bdb0d), requested by vue-components.
➤ YN0086: │ Some peer dependencies are incorrectly met by your project; run yarn explain peer-requirements <hash> for details, where <hash> is the six-letter p-prefixed code.
➤ YN0086: │ Some peer dependencies are incorrectly met by dependencies; run yarn explain peer-requirements for details.
➤ YN0000: └ Completed
➤ YN0000: ┌ Fetch step
➤ YN0000: └ Completed in 1s 655ms
➤ YN0000: ┌ Link step
➤ YN0008: │ root-workspace-0b6124@workspace:. must be rebuilt because its dependency tree changed
➤ YN0000: └ Completed in 0s 969ms
➤ YN0000: · Done with warnings in 3s 14ms
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app/front-end (JY-20692-fix-integration-app-[API_KEY]) $ yarn build
[dotenv@17.3.1] injecting env (0) from .env -- tip: ⚙️ override existing env vars with { override: true }
vite v8.0.0 building client environment for production...
✓ 4656 modules transformed.
[sentry-vite-plugin] Warning: No auth token provided. Will not create release. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/
[sentry-vite-plugin] Warning: No auth token provided. Will not upload source maps. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/
computing gzip size...
../public/vue-assets/index.html 4.58 kB │ gzip: 1.15 kB
../public/vue-assets/assets/job-adder-B2LcgC4o.png 5.41 kB
../public/vue-assets/assets/dixa-DzT89tKh.png 7.87 kB
../public/vue-assets/assets/planhat-CQycOTMW.svg 8.61 kB │ gzip: 3.48 kB
../public/vue-assets/assets/funding-circle-9iGnDGsz.png 9.43 kB
../public/vue-assets/assets/cision-uOdx_YiN.svg 14.41 kB │ gzip: 6.32 kB
../public/vue-assets/assets/les-mills-DWabpmYc.png 15.73 kB
../public/vue-assets/assets/superside-btT37L9L.png 26.69 kB
../public/vue-assets/assets/flags-a2kmUSbF.webp 28.18 kB
../public/vue-assets/assets/quinyx-RGQNHx6o.png 63.17 kB
../public/vue-assets/assets/[EMAIL] 66.44 kB
../public/vue-assets/assets/bg-marketing-CAIHIJMj.png 70.66 kB
../public/vue-assets/.vite/manifest.json 85.73 kB │ gzip: 8.32 kB
../public/vue-assets/assets/jiminny-score-DtTbKZaD.svg 131.93 kB │ gzip: 86.07 kB
../public/vue-assets/assets/wavy-bg-vnCaCm9m.webm 670.97 kB
../public/vue-assets/assets/wavy-bg-Op6dRIjR.mp4 14,484.63 kB
../public/vue-assets/assets/AppForm-EEGPL5K8.css 0.04 kB │ gzip: 0.06 kB
../public/vue-assets/assets/RecipientsCell-DNOSS8k1.css 0.12 kB │ gzip: 0.12 kB
../public/vue-assets/assets/softphone-coach-Cy_9nuQZ.css 0.13 kB │ gzip: 0.10 kB
../public/vue-assets/assets/locked-CHS8VPur.css 0.15 kB │ gzip: 0.12 kB
../public/vue-assets/assets/mobile-KPnGhnTp.css 0.16 kB │ gzip: 0.14 kB
../public/vue-assets/assets/BuildInfo-CxNQv_OV.css 0.20 kB │ gzip: 0.17 kB
../public/vue-assets/assets/AvatarsStack-C0Nvfh1V.css 0.28 kB │ gzip: 0.20 kB
../public/vue-assets/assets/LogoLong100-ByQTvUY3.css 0.32 kB │ gzip: 0.20 kB
../public/vue-assets/assets/connect--xBDzLbc.css 0.34 kB │ gzip: 0.21 kB
../public/vue-assets/assets/KioskBanner-xU3FRd8g.css 0.41 kB │ gzip: 0.27 kB
../public/vue-assets/assets/extension-installed-BPBhG13P.css 0.41 kB │ gzip: 0.26 kB
../public/vue-assets/assets/FollowModal-A0rJmIOK.css 0.45 kB │ gzip: 0.23 kB
../public/vue-assets/assets/GenericMessage-DutAlObK.css 0.49 kB │ gzip: 0.26 kB
../public/vue-assets/assets/textarea-caret-DCiNUER_.css 0.51 kB │ gzip: 0.32 kB
../public/vue-assets/assets/AppLinks-BznfGcBR.css 0.58 kB │ gzip: 0.33 kB
../public/vue-assets/assets/SeekBtn-_VMPIk6v.css 0.63 kB │ gzip: 0.32 kB
../public/vue-assets/assets/emoji-input-kJWRbWf8.css 0.68 kB │ gzip: 0.38 kB
../public/vue-assets/assets/activity-preview-BDxP2S0-.css 0.70 kB │ gzip: 0.41 kB
../public/vue-assets/assets/ListLoader-DQqAw-8s.css 0.73 kB │ gzip: 0.38 kB
../public/vue-assets/assets/filters-Bgb6cQD6.css 0.75 kB │ gzip: 0.39 kB
../public/vue-assets/assets/AiContext-Bg-zjjbA.css 0.99 kB │ gzip: 0.45 kB
../public/vue-assets/assets/vue3-daterange-picker-izPOBj3P.css 1.02 kB │ gzip: 0.39 kB
../public/vue-assets/assets/PrimaryButton-CJj2FzKS.css 1.08 kB │ gzip: 0.34 kB
../public/vue-assets/assets/other-BTuPXtu_.css 1.15 kB │ gzip: 0.49 kB
../public/vue-assets/assets/jenesius-vue-modal-DDcfejHO.css 1.17 kB │ gzip: 0.48 kB
../public/vue-assets/assets/TabEmptyState-DOlIO68k.css 1.21 kB │ gzip: 0.53 kB
../public/vue-assets/assets/AppAlert-BlSJKiqj.css 1.29 kB │ gzip: 0.41 kB
../public/vue-assets/assets/useActivityCustomerName-CBjfIUfJ.css 1.42 kB │ gzip: 0.45 kB
../public/vue-assets/assets/UserAvatar-co8l9mMk.css 1.51 kB │ gzip: 0.49 kB
../public/vue-assets/assets/basic-modal-Dx7ZBbcA.css 1.55 kB │ gzip: 0.66 kB
../public/vue-assets/assets/login-DYbcBesM.css 1.58 kB │ gzip: 0.61 kB
../public/vue-assets/assets/DrawerWidget-poAmqspk.css 1.67 kB │ gzip: 0.69 kB
../public/vue-assets/assets/ActionItems-5FkZSo3G.css 1.79 kB │ gzip: 0.80 kB
../public/vue-assets/assets/join-conference-DQ6Yl7eU.css 1.82 kB │ gzip: 0.75 kB
../public/vue-assets/assets/InputText-C-DxDNSV.css 1.95 kB │ gzip: 0.71 kB
../public/vue-assets/assets/GoogleLikeButton-DvQHxbqR.css 2.11 kB │ gzip: 0.66 kB
../public/vue-assets/assets/usePusherEventListener-CDj8aSNp.css 2.20 kB │ gzip: 0.73 kB
../public/vue-assets/assets/onboard-IzGiQ-EC.css 2.32 kB │ gzip: 0.95 kB
../public/vue-assets/assets/ai-reports-manage-DaynTl62.css 2.34 kB │ gzip: 0.86 kB
../public/vue-assets/assets/StatusBadge-B4N12dzU.css 2.35 kB │ gzip: 0.86 kB
../public/vue-assets/assets/GridView-CsP1Jk-k.css 2.36 kB │ gzip: 0.90 kB
../public/vue-assets/assets/WelcomeLayout-6AX86p6F.css 2.38 kB │ gzip: 0.91 kB
../public/vue-assets/assets/snackbar-CS_iqvrv.css 2.45 kB │ gzip: 0.80 kB
../public/vue-assets/assets/ai-reports-mtqGqPPq.css 2.47 kB │ gzip: 0.91 kB
../public/vue-assets/assets/Comment-gmAfiuer.css 2.63 kB │ gzip: 1.00 kB
../public/vue-assets/assets/meeting-consent-BasRvxE3.css 2.66 kB │ gzip: 0.96 kB
../public/vue-assets/assets/DealRiskList-CwxmDnSC.css 2.67 kB │ gzip: 0.96 kB
../public/vue-assets/assets/add-to-playlist-modal-C_ad1hht.css 2.84 kB │ gzip: 0.95 kB
../public/vue-assets/assets/live-_zT5qBc1.css 3.60 kB │ gzip: 1.16 kB
../public/vue-assets/assets/RadioField-DDp8Y71G.css 3.64 kB │ gzip: 1.20 kB
../public/vue-assets/assets/vue-multiselect-BkQNV7oL.css 3.80 kB │ gzip: 0.86 kB
../public/vue-assets/assets/activity-preview-result-Db1Da81T.css 3.89 kB │ gzip: 1.15 kB
../public/vue-assets/assets/liquor-tree-CB5oh8v1.css 4.08 kB │ gzip: 1.24 kB
../public/vue-assets/assets/sentry-CSjHcvmn.css 4.47 kB │ gzip: 1.08 kB
../public/vue-assets/assets/AiAutomation-Cj1t8v5y.css 4.61 kB │ gzip: 0.84 kB
../public/vue-assets/assets/export-portal-CyIUVTEe.css 5.34 kB │ gzip: 1.65 kB
../public/vue-assets/assets/InputField-C4hoKJ7Z.css 6.13 kB │ gzip: 1.27 kB
../public/vue-assets/assets/vue-mq-bh4L87Tr.css 6.64 kB │ gzip: 1.95 kB
../public/vue-assets/assets/invitation-EPR1t-bH.css 6.74 kB │ gzip: 2.22 kB
../public/vue-assets/assets/ondemand-C2m0YVGA.css 6.84 kB │ gzip: 2.02 kB
../public/vue-assets/assets/AskAnything-CyvEhHS1.css 8.60 kB │ gzip: 2.60 kB
../public/vue-assets/assets/AppButton-BIeoar_U.css 8.88 kB │ gzip: 2.04 kB
../public/vue-assets/assets/kiosk-BcheyWWL.css 9.56 kB │ gzip: 2.53 kB
../public/vue-assets/assets/AiCrmNotes-nNTe_mOY.css 10.62 kB │ gzip: 1.93 kB
../public/vue-assets/assets/deal-view-DJzG7PLp.css 11.70 kB │ gzip: 3.29 kB
../public/vue-assets/assets/tokens-DZgWrfKd.css 12.44 kB │ gzip: 2.82 kB
../public/vue-assets/assets/tokens-DL2BPbai.css 12.89 kB │ gzip: 2.87 kB
../public/vue-assets/assets/playlists-Cy3t3kYU.css 13.54 kB │ gzip: 3.16 kB
../public/vue-assets/assets/PhoneField-D_kGOah0.css 13.75 kB │ gzip: 3.44 kB
../public/vue-assets/assets/ListView-Dt0qkyuY.css 17.07 kB │ gzip: 4.11 kB
../public/vue-assets/assets/intl-tel-input-DgmgTINs.css 17.11 kB │ gzip: 5.37 kB
../public/vue-assets/assets/AppFormField-DvpfRzi8.css 18.98 kB │ gzip: 4.42 kB
../public/vue-assets/assets/OrgSettingsLayout-BogdXtgY.css 21.98 kB │ gzip: 5.51 kB
../public/vue-assets/assets/dashboard-DQw3TKyk.css 22.74 kB │ gzip: 4.35 kB
../public/vue-assets/assets/deal-insights-CfX3UNzh.css 29.66 kB │ gzip: 6.44 kB
../public/vue-assets/assets/team-insights-CjVhm0JN.css 42.31 kB │ gzip: 8.46 kB
../public/vue-assets/assets/playback-CvJP5tX1.css 44.55 kB │ gzip: 10.36 kB
../public/vue-assets/assets/video-js-skin-DYZluGb-.css 47.73 kB │ gzip: 12.63 kB
../public/vue-assets/assets/assets-CAbfI4CY.css 83.38 kB │ gzip: 51.71 kB
../public/vue-assets/assets/logged-in-layout-1mAqkmnS.css 153.67 kB │ gzip: 27.18 kB
../public/vue-assets/assets/assets-xH6dx_9q.css 259.71 kB │ gzip: 181.71 kB
../public/vue-assets/assets/directives-DJJeJJOP.js 0.53 kB │ gzip: 0.36 kB │ map: 0.32 kB
../public/vue-assets/assets/jenesius-vue-modal-BuBhyl83.js 0.54 kB │ gzip: 0.37 kB │ map: 0.47 kB
../public/vue-assets/assets/spark-D_-Wgfar.js 0.54 kB │ gzip: 0.35 kB │ map: 0.40 kB
../public/vue-assets/assets/url-messenger-_CGQa-lH.js 0.56 kB │ gzip: 0.37 kB │ map: 0.46 kB
../public/vue-assets/assets/wavy-bg-Cec-_GDj.js 0.57 kB │ gzip: 0.36 kB │ map: 0.35 kB
../public/vue-assets/assets/component-css-class-CtO0AVgW.js 0.61 kB │ gzip: 0.40 kB │ map: 0.91 kB
../public/vue-assets/assets/theme-B5VdyMN_.js 0.63 kB │ gzip: 0.41 kB │ map: 0.49 kB
../public/vue-assets/assets/pick-C8Pw7kHx.js 0.71 kB │ gzip: 0.45 kB │ map: 1.59 kB
../public/vue-assets/assets/utils-BjsEoLDB.js 0.77 kB │ gzip: 0.50 kB │ map: 1.68 kB
../public/vue-assets/assets/throttle-siTOKEMt.js 0.78 kB │ gzip: 0.49 kB │ map: 3.24 kB
../public/vue-assets/assets/lastFilters-B_B_O5LY.js 0.80 kB │ gzip: 0.51 kB │ map: 1.12 kB
../public/vue-assets/assets/useAuthState-Bpx8WpBc.js 0.81 kB │ gz...
|
NULL
|
|
46471
|
NULL
|
0
|
2026-04-17T10:36:37.901077+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776422197901_m2.jpg...
|
PhpStorm
|
faVsco.js – ~/jiminny/app/front-end/src/components faVsco.js – ~/jiminny/app/front-end/src/components/connect/connect.vue...
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20692-fix-integration- Project: faVsco.js, menu
JY-20692-fix-integration-app-[API_KEY], menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
cachedStages
New Line
Match Case
Words
Regex...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.03046875,"top":0.017361112,"width":0.0453125,"height":0.022222223},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20692-fix-integration-app-token-auth-response-change, menu","depth":5,"bounds":{"left":0.07578125,"top":0.017361112,"width":0.15898438,"height":0.022222223},"help_text":"Git Branch: JY-20692-fix-integration-app-token-auth-response-change","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.78515625,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsCommandTest","depth":6,"bounds":{"left":0.803125,"top":0.017361112,"width":0.09765625,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsCommandTest'","depth":6,"bounds":{"left":0.9007813,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsCommandTest'","depth":6,"bounds":{"left":0.9140625,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9273437,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96015626,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9734375,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9867188,"top":0.017361112,"width":0.013281226,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show Replace Field","depth":4,"bounds":{"left":0.12382813,"top":0.22083333,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Search History","depth":3,"bounds":{"left":0.13867188,"top":0.22013889,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"cachedStages","depth":4,"bounds":{"left":0.1515625,"top":0.22013889,"width":0.0515625,"height":0.013888889},"value":"cachedStages","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"bounds":{"left":0.21367188,"top":0.22013889,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Match Case","depth":3,"bounds":{"left":0.22539063,"top":0.22013889,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Words","depth":3,"bounds":{"left":0.23554687,"top":0.22013889,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Regex","depth":3,"bounds":{"left":0.24570313,"top":0.22013889,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
5574872765237312613
|
-7735694548458591292
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
JY-20692-fix-integration- Project: faVsco.js, menu
JY-20692-fix-integration-app-[API_KEY], menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
cachedStages
New Line
Match Case
Words
Regex
PhpStormFileFditViewNavigateCodelaravelRetactonRunToolsWindowHelpFV faVsco.s vT° JY-20692-fix-integration-app-[API_KEY] v>W testsU connect.lessV connect.vuedashboardW DeallnsightsD errorPages_ export-portalextension-installledinvitationvonconterenceWa VoUT_ LiveCoach_ Lockedlogin_ MeetingConsent_ mobilel onboardl•__mocks_•U_tests_V MobileAppDownk1 Onboard.lessVenhoard vilPC AutomatedReportsService.phpC) SendReportJob.phpC SendReportMailJob.phpC ReportController.phpTokenBuilder.phpc leamsetuocontroller.onppnp apl.ono© Filesystem.php© Team.php© CreateHeldActivityEvent.phpC) TrackProviderInstalledEvent.phpC RequestGenerateReportJob.phpOpportunitySyncTrait.phpC Opportunity.phpT InteractsWithPivotTable.phgOpoorunityupdated.oneOpportunitystageupdatea.pnpc) FventService?rovider.onmonoortunitvPendingAiAnalvsisatterstadechanded.omC RunOpportunityAiAnalysis.phpC ProcessAlAutomationAnalysiskesults.ong© ImportOpportunityBatch.phpTImportBatchJobTrait.php(C) Service.phpcachedStagesCc W.*trait upportunitysynctraitA33 V2 V1910181028private function resolveForecastCategory(?string $forecastCategory): stringf=1482 usages150private function importExternalFieldData(array $properties, int $opportunityid110501033$crmFields = $this->getOpportunitySyncableFields:Sthis->inportOpportunitvCrmFieldDataSproperties. ScrmFields.Sopportunitx154$155-1501034(aJ Outputini # 1695 X2 rowsvIX. AutovДQMAФW expires Y÷Irefresh token expires Yo_provider y1776336176<null> googleICIsImtpZC1617VmNWFhZGFkLTQwZDktNDLkNy04ZW12LTAzNmNLM.1729613615<null> zoom-phone= custom.loc= aravel.logc SF liminny@localhostC scratch 1.isonconnect.vue XV Onboard.vue< Hs local liminnyalocalnost4 console [EUlC CrmEntityRepository.pho(iii) crm configurations [EU]¿ console IPROD¿ consoe STAGINGII<script>101138methods:async integrationApp0nClickO{cons connecron = avalt unteoraronAodnteoramonuuns. locaurrovloer.naley.openNewConnectiongshowPoweredBy: false.allowMultipleConnections: falser.catch((err) => %console.LoglIntegrationApp openNewconnection rejected:', err):return null;7):all to Lascade, al to commandconsole.log('[IntegrationApp] openNewConnection resolved:'.wsun.scrinontyconnectlonoanex Relec File nae+ 2 of 2 files →loblSupport Daily • in 1h 24 mA100% [z7Fri 17 Apr 13:36:37AutomatedRenortsCommandTestCascadeAutomated Renort RetDebugging OpportuniUpdate Connection Loratner than resolving with a nul/disconnectea connection ob ect as it dla berore.To contirm. add a catch." isconst connection = awalt intedratlonaoointegration(this. Loca(Provider.name)onenNewconnectionshowPoweredBy: talse,allowMultipleConnections: false,}).catch((err) => 1console. loo untegrationApp openNewlonneccion relected: err;return nultLtlles with changesfront-end/src/components/connect/Jconnect.vue +2 -3tront-eno/src/componenis/onboarconooara.vue +l -2ASk anyinind dtl+ &CodeClaude Sonnet 4.6WServicesv _ DatabaseVdEUa console 1 s 59 msua crm_contigurations 1s 391 msd jiminny@localhost4SFd moocallV APROD< console ls o0o msV L STAGINGd console• Dockero_stateytul-rerreshfull-refreshIauth scope Yopenid https://www.googleap1s.com/auth/userinfo.prof1le https://www.googleap1s.com/auth/calendar http..phone:read:admin user:read:adminView allliReject allAccept allOJCSV.L+OgI retry after YIn crea12026-0.<null>2024-11Shortcuts conflicts: Clone Caret Above and 1 more shortcut conflict with macOS shortcuts. Modify these shortcuts or change macOS system settings. // Modify Shortcuts // Don't Show Again (today 12:22W Windsurf Teams 152:1 V UTF-8fh 2 spaces...
|
NULL
|
|
46470
|
NULL
|
0
|
2026-04-17T10:36:37.925635+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776422197925_m1.jpg...
|
NULL
|
NULL
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
iTerm2ShellEditViewSessionScriptsProfilesWindowHel iTerm2ShellEditViewSessionScriptsProfilesWindowHelp>0 l4lSupport Daily - in 1h 24 m100% <47-zshAPP (-zsh)|X4-zshDOCKER881DEV (-zsh)APP (-zsh)X3./public/vue-assets/assets/ondemand-CE8XLH98.js:./public/vue-assets/assets/CrmLink-DKYsnHnx.js./public/vue-assets/assets/liquor-tree-COUefof4.js./public/vue-assets/assets/DealRiskList-3DSxnTNQ.js:./public/vue-assets/assets/AskAnything-BQ36E0GS.js:./public/vue-assets/assets/lib-CwM9toD2.js./public/vue-assets/assets/AppFormField-CqCXZwrk.js./public/vue-assets/assets/deal-view-B92qaj32.js:./public/vue-assets/assets/exports-D1lmea40.js../public/vue-assets/assets/playlists-u3UeUUz5.js../public/vue-assets/assets/callScoringTemplates-zeRn40ul.js../public/vue-assets/assets/_copy0bject-USkOnlaQ.js../public/vue-assets/assets/pusher-znYCfz7U.js•/public/vue-assets/assets/onboard-CCnYJmCP.js./public/vue-assets/assets/StatusBadge-CNGFZm8P.js./public/vue-assets/assets/kiosk-D6N5-ivP.js./public/vue-assets/assets/preload-helper-DCvhahzG.js../public/vue-assets/assets/deal-insights-jlkz65MZ.js../public/vue-assets/assets/ListView-DmKqYVi1.js:./public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js./public/vue-assets/assets/WelcomeLayout-B6wd32HG.js../public/vue-assets/assets/dashboard-BWoBIPKA.js:./public/vue-assets/assets/emoji-input-CSq87OVy.js../public/vue-assets/assets/AppButton-D3qMdODr.js../public/vue-assets/assets/sentry-icPKypZa.js:./public/vue-assets/assets/OrgSettingsLayout-BZ-JOgiH.js./public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js./public/vue-assets/assets/playback-DKsqp_al.js./public/vue-assets/assets/index.module-Bjlhgfd1.js./public/vue-assets/assets/intl-tel-input-BW4mv40Q.js:./public/vue-assets/assets/team-insights-D9P6ojjD.js../public/vue-assets/assets/popper-CQwVcrX4.js../public/vue-assets/assets/PhoneField-CwCIoAYm.js./public/vue-assets/assets/live-DWumv-QD.js../public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BN0485xV.js../public/vue-assets/assets/index-DiEs6xIb.js:./public/vue-assets/assets/logged-in-layout-Cq1ztH50.jsH8526.88kB27.91kB30.75kB34.39kB39.50kB39.69kB41.91kB43.22kB47.84kB48.28kB55.13kB61.28kB62.98kB63.11kB64.66kB79.60kB82.59kB94.84kB115.71kB117.59 kB120.67kB128.71kB129.28 kB133.44kB164.28kB176.33kB180.40kB198.79kB218.14kB264.94 kВ298.57kB307.13kB343.99kB367.43kB689.63kB825.23 kB1,402.70 kB[plugin builtin:vite-reporter](!) Somechunks are larger than 500 kB after minification. Consider:- Using dynamic import() to code-split the application- Use build.rolldown0ptions.output.codeSplitting to improve chunking: [URL_WITH_CREDENTIALS] ₴8APP...
|
NULL
|
-7952806826740830795
|
NULL
|
click
|
ocr
|
NULL
|
iTerm2ShellEditViewSessionScriptsProfilesWindowHel iTerm2ShellEditViewSessionScriptsProfilesWindowHelp>0 l4lSupport Daily - in 1h 24 m100% <47-zshAPP (-zsh)|X4-zshDOCKER881DEV (-zsh)APP (-zsh)X3./public/vue-assets/assets/ondemand-CE8XLH98.js:./public/vue-assets/assets/CrmLink-DKYsnHnx.js./public/vue-assets/assets/liquor-tree-COUefof4.js./public/vue-assets/assets/DealRiskList-3DSxnTNQ.js:./public/vue-assets/assets/AskAnything-BQ36E0GS.js:./public/vue-assets/assets/lib-CwM9toD2.js./public/vue-assets/assets/AppFormField-CqCXZwrk.js./public/vue-assets/assets/deal-view-B92qaj32.js:./public/vue-assets/assets/exports-D1lmea40.js../public/vue-assets/assets/playlists-u3UeUUz5.js../public/vue-assets/assets/callScoringTemplates-zeRn40ul.js../public/vue-assets/assets/_copy0bject-USkOnlaQ.js../public/vue-assets/assets/pusher-znYCfz7U.js•/public/vue-assets/assets/onboard-CCnYJmCP.js./public/vue-assets/assets/StatusBadge-CNGFZm8P.js./public/vue-assets/assets/kiosk-D6N5-ivP.js./public/vue-assets/assets/preload-helper-DCvhahzG.js../public/vue-assets/assets/deal-insights-jlkz65MZ.js../public/vue-assets/assets/ListView-DmKqYVi1.js:./public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js./public/vue-assets/assets/WelcomeLayout-B6wd32HG.js../public/vue-assets/assets/dashboard-BWoBIPKA.js:./public/vue-assets/assets/emoji-input-CSq87OVy.js../public/vue-assets/assets/AppButton-D3qMdODr.js../public/vue-assets/assets/sentry-icPKypZa.js:./public/vue-assets/assets/OrgSettingsLayout-BZ-JOgiH.js./public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js./public/vue-assets/assets/playback-DKsqp_al.js./public/vue-assets/assets/index.module-Bjlhgfd1.js./public/vue-assets/assets/intl-tel-input-BW4mv40Q.js:./public/vue-assets/assets/team-insights-D9P6ojjD.js../public/vue-assets/assets/popper-CQwVcrX4.js../public/vue-assets/assets/PhoneField-CwCIoAYm.js./public/vue-assets/assets/live-DWumv-QD.js../public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BN0485xV.js../public/vue-assets/assets/index-DiEs6xIb.js:./public/vue-assets/assets/logged-in-layout-Cq1ztH50.jsH8526.88kB27.91kB30.75kB34.39kB39.50kB39.69kB41.91kB43.22kB47.84kB48.28kB55.13kB61.28kB62.98kB63.11kB64.66kB79.60kB82.59kB94.84kB115.71kB117.59 kB120.67kB128.71kB129.28 kB133.44kB164.28kB176.33kB180.40kB198.79kB218.14kB264.94 kВ298.57kB307.13kB343.99kB367.43kB689.63kB825.23 kB1,402.70 kB[plugin builtin:vite-reporter](!) Somechunks are larger than 500 kB after minification. Consider:- Using dynamic import() to code-split the application- Use build.rolldown0ptions.output.codeSplitting to improve chunking: [URL_WITH_CREDENTIALS] ₴8APP...
|
46468
|
|
46365
|
NULL
|
0
|
2026-04-17T10:31:20.687848+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776421880687_m2.jpg...
|
PhpStorm
|
faVsco.js – ~/jiminny/app/front-end/src/components faVsco.js – ~/jiminny/app/front-end/src/components/connect/connect.vue...
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20692-fix-integration- Project: faVsco.js, menu
JY-20692-fix-integration-app-[API_KEY], menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
cachedStages
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
2/4
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Code changed:
Hide
Sync Changes
Hide This Notification
33
2
19
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use HubSpot\Client\Crm\Deals\Model\CollectionResponseAssociatedId;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Models\Account;
use Exception;
use Jiminny\Component\DealInsights\Forecast\Forecast;
use Jiminny\Jobs\Crm\MatchActivitiesToNewOpportunity;
use Jiminny\Models\Contact;
use Jiminny\Models\Crm\BusinessProcess;
use Jiminny\Exceptions\CrmException;
use Jiminny\Models\Opportunity;
use Illuminate\Support\Collection;
use Jiminny\Models\Stage;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Crm\Hubspot\DealFieldsService;
use Jiminny\Services\Crm\Hubspot\OpportunitySyncStrategy\HubspotSingleSyncStrategy;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Services\Crm\OpportunitySyncStrategyResolver;
use Jiminny\Utils\CurrencyFormatter;
/**
* Optimized sync methods for better performance
* These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains
*/
trait OpportunitySyncTrait
{
private const int BATCH_SIZE = 100;
private const int BATCH_PROCESS_SIZE = 800;
protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;
protected CrmEntityRepository $crmEntityRepository;
protected DealFieldsService $dealFieldsService;
private ?array $cachedClosedDealStages = null;
private array $cachedBusinessProcesses = [];
private array $cachedStages = [];
public function syncOpportunities(array $parameters, ?string $strategy = null): int
{
$strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);
$parameters['config'] = $this->config;
$syncCount = 0;
$reportedTotal = 0;
$lastSyncedId = [];
try {
foreach ($strategies as $strategyName => $syncStrategy) {
$this->logger->info(
'[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .
$strategyName
);
$total = 0;
$lastId = null;
$buffer = [];
// HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies
foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {
$buffer[] = $hsOpportunity;
// process every 800 rows (fits < 1 000 association limit)
if (\count($buffer) >= self::BATCH_PROCESS_SIZE) {
$syncCount += $this->processOpportunityBatch($buffer);
$buffer = [];
}
}
// leftovers
if ($buffer) {
$syncCount += $this->processOpportunityBatch($buffer);
}
$reportedTotal += $total;
$lastSyncedId = $lastId;
}
} catch (\HubSpot\Client\Crm\Deals\ApiException | CrmException $e) {
$this->handleSyncException($e, $parameters);
}
$this->logger->info(
'[HubSpot] Synced opportunities',
[
'team' => $this->team->getId(),
'sync_count' => $syncCount,
'total' => $reportedTotal,
'last_synced_id' => $lastSyncedId,
]
);
return $reportedTotal;
}
private function handleSyncException(\Throwable $e, array $parameters): void
{
if (($parameters['since'] ?? null) instanceof Carbon) {
$parameters['since'] = $parameters['since']->toDateTimeString();
}
$parameters['config'] = $this->config->getId();
$this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [
'teamId' => $this->team->getUuid(),
'parameters' => $parameters,
'reason' => $e->getMessage(),
]);
}
/**
* @inheritdoc
*/
public function syncOpportunity(string $crmId): ?Opportunity
{
$strategy = $this->opportunitySyncStrategyResolver->resolve(
$this->config,
OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,
);
$parameters = [
'config' => $this->config,
'crm_id' => $crmId,
];
try {
if (! $strategy instanceof HubspotSingleSyncStrategy) {
throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');
}
$hsOpportunity = $strategy->fetchOpportunity($parameters);
} catch (\HubSpot\Client\Crm\Deals\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
$hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);
return $this->importOrUpdateOpportunity($hsOpportunity);
}
/**
* Process webhook-collected opportunity batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportOpportunityBatch jobs for batch processing.
*
* @return int Number of opportunity IDs dispatched to jobs
*/
public function batchSyncOpportunities(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,
$configId
);
}
/**
* Import a batch of opportunities by their CRM IDs.
* Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().
*
* @param array<string> $crmIds HubSpot deal CRM IDs
*
* @return array{success: array, failed_ids: array, errors?: array<string, string>}
*/
public function importOpportunityBatchByIds(array $crmIds): array
{
$fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);
$allDeals = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
$deals = $this->client->getOpportunitiesByIds($chunk, $fields);
foreach ($deals as $deal) {
$allDeals[] = $deal;
}
}
// IDs not returned by HubSpot are likely deleted or inaccessible deals.
// These are not failures — retrying won't bring them back.
$fetchedIds = array_map('strval', array_column($allDeals, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allDeals),
]);
}
if (empty($allDeals)) {
return ['success' => [], 'failed_ids' => []];
}
return $this->importOpportunityBatch($allDeals);
}
private function getClosedDealStages(): array
{
if ($this->cachedClosedDealStages !== null) {
return $this->cachedClosedDealStages;
}
$stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);
$data = [
'lost' => [],
'won' => [],
];
foreach ($stages as $stage) {
if ($stage->probability == 0.00) {
$data['lost'][] = $stage->crm_provider_id;
}
if ($stage->probability == 100.00) {
$data['won'][] = $stage->crm_provider_id;
}
}
$this->cachedClosedDealStages = $data;
return $data;
}
/**
* Import deals into the database with pre-fetched associations.
*
* API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT
* caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()
* where Laravel retries the whole job with backoff. After all retries exhausted,
* failed() requeues all IDs to Redis.
*
* The per-deal loop catches exceptions individually. A deal can end up in three states:
* - success: imported/updated successfully
* - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)
* These are permanent issues — retrying won't fix them.
* - skipped (null): missing dependencies (no account, unknown pipeline/stage).
* This is acceptable — the deal cannot be imported until those exist.
*/
private function importOpportunityBatch(array $deals): array
{
$syncedOpportunities = [
'success' => [],
'failed_ids' => [],
];
$dealIds = array_column($deals, 'id');
// Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the
// queue job retries the whole batch and eventually requeues all deal IDs back to Redis.
try {
$companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');
$contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');
$associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);
$existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(
$this->config,
array_map('strval', $dealIds)
);
$existingCrmIdSet = array_flip($existingCrmIds);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [
'teamId' => $this->team->getId(),
'dealCount' => count($dealIds),
'error' => $e->getMessage(),
]);
throw $e;
}
foreach ($deals as $deal) {
try {
$deal['associations'] = $this->prepareAssociationsForOpportunity(
$deal['id'],
$companyAssociations,
$contactAssociations,
$associationsData
);
$syncedOpportunity = $this->importOrUpdateOpportunity(
$deal,
isset($existingCrmIdSet[(string) $deal['id']])
);
if ($syncedOpportunity) {
$syncedOpportunities['success'][] = $syncedOpportunity;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [
'teamId' => $this->team->getId(),
'crmId' => $deal['id'],
'error' => $e->getMessage(),
]);
$syncedOpportunities['failed_ids'][] = $deal['id'];
$syncedOpportunities['errors'][$deal['id']] = $e->getMessage();
}
}
return $syncedOpportunities;
}
/**
* Prepare associated entities for opportunities with optimized batch processing
* Returns structured data with CRM ID to DB ID mappings for each opportunity
*/
private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array
{
// Step 1: Collect all unique company and contact IDs from associations
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
// Step 2: Batch sync missing entities and get CRM ID to DB ID mappings
$companyIdMappings = [];
$contactIdMappings = [];
if (! empty($allCompanyIds)) {
$companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);
}
if (! empty($allContactIds)) {
$contactIdMappings = $this->prepareAssociatedContacts($allContactIds);
}
return [
'company_id_mappings' => $companyIdMappings,
'contact_id_mappings' => $contactIdMappings,
];
}
/**
* Flatten association data to get unique IDs
*/
private function flattenAssociationIds(array $associations): array
{
$ids = [];
foreach ($associations as $dealAssociations) {
if (is_array($dealAssociations)) {
foreach ($dealAssociations as $id) {
$ids[$id] = true;
}
}
}
return array_keys($ids);
}
/**
* Batch sync missing accounts
*/
private function prepareAssociatedAccounts(array $companyIds): array
{
// Find which accounts already exist
$existingAccounts = $this->crmEntityRepository
->findAccountsByExternalIds($this->config, $companyIds);
$existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();
$existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {
return [$account->getCrmProviderId() => $account->getId()];
})->toArray();
$missingCompanyIds = array_diff($companyIds, $existingCompanyIds);
if (empty($missingCompanyIds)) {
return $existingAccountsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [
'teamId' => $this->team->getUuid(),
'total_companies' => count($companyIds),
'existing_companies' => count($existingCompanyIds),
'missing_companies' => count($missingCompanyIds),
]);
// we already have limit on opportunity ids count
// Initialize variable before try block
$syncedAccountsData = [];
try {
$syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [
'size' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
$syncedAccountsData = [];
}
return $existingAccountsData + $syncedAccountsData;
}
/**
* Prepare associated contacts - find existing and sync missing ones
* Returns mapping of CRM ID to DB ID
*/
private function prepareAssociatedContacts(array $contactIds): array
{
// Find which contacts already exist
$existingContacts = $this->crmEntityRepository
->findContactsByExternalIds($this->config, $contactIds);
$existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();
// Create mapping for existing contacts
$existingContactsData = $existingContacts->mapWithKeys(function ($contact) {
return [$contact->getCrmProviderId() => $contact->getId()];
})->toArray();
$missingContactIds = array_diff($contactIds, $existingContactIds);
if (empty($missingContactIds)) {
return $existingContactsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [
'teamId' => $this->team->getUuid(),
'total_contacts' => count($contactIds),
'existing_contacts' => count($existingContactIds),
'missing_contacts' => count($missingContactIds),
]);
// Sync missing contacts using batch API
try {
$syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [
'size' => count($missingContactIds),
'error' => $e->getMessage(),
]);
$syncedContactsData = [];
}
return $existingContactsData + $syncedContactsData;
}
private function batchSyncCrmObjects(string $objectType, array $crmIds): array
{
$syncObjects = [];
$crmObjectIds = array_values($crmIds);
foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {
try {
$objects = $objectType === 'companies' ?
$this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :
$this->client->getContactsByIds($chunk, $this->getContactFields());
foreach ($objects as $objectId => $objectData) {
$this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [
'requested_count' => count($chunk),
'synced_count' => count($objects),
]);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [
'ids' => $chunk,
'error' => $e->getMessage(),
]);
}
}
return $syncObjects;
}
private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void
{
try {
$object = $objectType === 'companies' ?
$this->importAccount($objectData) :
$this->importContact($objectData);
if ($object) {
$syncObjects[$object->getCrmProviderId()] = $object->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [
'id' => $objectId,
'error' => $e->getMessage(),
]);
}
}
/**
* Prepare associations for a single opportunity
*
* The return value is an array with the following structure:
* [
* 'companies' => [
* $companyCrmId => $companyId,
* ...
* ],
* 'contacts' => [
* $contactCrmId => $contactId,
* ...
* ],
* 'account_id' => $accountId,
* ]
*/
private function prepareAssociationsForOpportunity(
string $oppCrmId,
array $companyAssociations,
array $contactAssociations,
array $associationsData
): array {
$associations = [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
$oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];
foreach ($oppCompanyIds as $companyCrmId) {
if (isset($associationsData['company_id_mappings'][$companyCrmId])) {
$associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];
// Set primary account (first company becomes primary account)
if ($associations['account_id'] === null) {
$associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];
}
}
}
$oppContactIds = $contactAssociations[$oppCrmId] ?? [];
foreach ($oppContactIds as $contactCrmId) {
if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {
$associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];
}
}
return $associations;
}
/**
* Update only associations for an opportunity
*/
private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void
{
// Update contact associations
$this->importOpportunityContacts($opportunity, $associations['contacts']);
// Update company (account) associations
$this->updateOpportunityAccount($opportunity, $associations['account_id']);
}
/**
* Remove all contact associations from an opportunity
*/
private function removeAllOpportunityContacts(Opportunity $opportunity): void
{
$currentCount = (int) $opportunity->contacts()->count();
if ($currentCount > 0) {
$opportunity->contacts()->detach();
$this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_count' => $currentCount,
]);
}
}
private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void
{
if ($accountId === null) {
// No account ID provided - keep current account
return;
}
$currentAccountId = $opportunity->getAccountId();
// Only update if account has changed
if ($currentAccountId !== $accountId) {
$opportunity->account_id = $accountId;
$opportunity->save();
$this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [
'opportunity_id' => $opportunity->getId(),
'old_account_id' => $currentAccountId,
'new_account_id' => $accountId,
]);
}
}
/**
* Find existing opportunities by external IDs (OPTIMIZED VERSION)
* Uses batch query for better performance
*/
private function findExistingOpportunities(array $crmIds): Collection
{
return $this->crmEntityRepository
->findOpportunitiesByExternalIds($this->config, $crmIds);
}
private function processOpportunityBatch(array $opportunities): int
{
$syncedOpportunities = $this->importOpportunityBatch($opportunities);
return count($syncedOpportunities['success'] ?? []);
}
/**
* Convert single deal associations from HubSpot format to internal format
* Handles both HubSpot SDK objects and array formats
*
* @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed
*
* @return array Processed associations with DB IDs
*/
private function convertDealAssociations(array $opportunityAssociations): array
{
$associations = $this->initializeAssociationsStructure();
if (empty($opportunityAssociations)) {
return $associations;
}
$associationIds = $this->extractAssociationIds($opportunityAssociations);
$this->processCompanyAssociations($associationIds, $associations);
$this->processContactAssociations($associationIds, $associations);
return $associations;
}
private function initializeAssociationsStructure(): array
{
return [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
}
private function extractAssociationIds(array $opportunityAssociations): array
{
$associationIds = [];
foreach ($opportunityAssociations as $type => $associationData) {
if (! empty($associationData)) {
$associationIds[$type] = $this->convertSingleDealAssociations($associationData);
}
}
return $associationIds;
}
private function processCompanyAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['companies'])) {
return;
}
$companyId = $associationIds['companies'][0];
$account = $this->findOrSyncAccount($companyId);
if ($account instanceof Account) {
$associations['companies'][$companyId] = $account->getId();
$associations['account_id'] = $account->getId();
}
}
private function processContactAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['contacts'])) {
return;
}
foreach ($associationIds['contacts'] as $contactId) {
$contact = $this->findOrSyncContact($contactId);
if ($contact instanceof Contact) {
$associations['contacts'][$contactId] = $contact->getId();
}
}
}
private function findOrSyncAccount(string $companyId): ?Account
{
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);
if (! $account instanceof Account) {
$account = $this->syncAccount($companyId);
}
return $account;
}
private function findOrSyncContact(string $contactId): ?Contact
{
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);
if (! $contact instanceof Contact) {
$contact = $this->syncContact($contactId);
}
return $contact;
}
private function convertSingleDealAssociations($opportunityAssociations = null): array
{
$associationData = [];
if ($opportunityAssociations === null) {
return $associationData;
}
// Handle array input (from extractAssociationIds)
if (is_array($opportunityAssociations)) {
return $opportunityAssociations;
}
// Handle CollectionResponseAssociatedId object
if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {
foreach ($opportunityAssociations->getResults() as $association) {
$associationData[] = $association->getId();
}
}
return $associationData;
}
private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity
{
if (empty($crmData['properties'])) {
return null;
}
$crmId = (string) $crmData['id'];
$properties = $crmData['properties'];
$associations = $crmData['associations'] ?? [];
$opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(
$this->config,
$crmId
);
if ($opportunityExists) {
return $this->updateOpportunity($crmId, $properties, $associations);
} else {
return $this->createOpportunity($crmId, $properties, $associations);
}
}
/**
* Create new opportunity
*/
private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity
{
$accountId = $this->resolveAccountId($associations);
if (! $accountId) {
return null;
}
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
if (! $businessProcess) {
return null;
}
$stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);
if (! $stage) {
return null;
}
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->importOpportunityContacts($opportunity, $associations['contacts']);
if ($opportunity->wasRecentlyCreated) {
MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());
}
return $opportunity;
}
/**
* Update existing opportunity
*/
private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity
{
$accountId = $this->resolveAccountId($associations);
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
$stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->updateOpportunityAssociations($opportunity, $associations);
return $opportunity;
}
private function resolveAccountId(array $associations): ?int
{
if (! empty($associations['accountId'])) {
return $associations['accountId'];
}
if (empty($associations)) {
return null;
}
// we can't resolve multiple account ids (currently SDK returns one company)
foreach ($associations['companies'] as $accountId) {
return $accountId;
}
return null;
}
private function buildOpportunityData(
array $properties,
?int $accountId,
?BusinessProcess $businessProcess,
?Stage $stage
): array {
$ownerId = null;
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$name = 'Unknown';
if (isset($properties['dealname'])) {
$name = mb_strimwidth($properties['dealname'], 0, 128);
}
$amount = $this->resolveAmount($properties);
$currency = $properties['deal_currency_code'] ?? null;
$closeDate = null;
if (! empty($properties['closedate'])) {
$closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');
}
$remotelyCreatedAt = null;
if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {
$date = $this->parseCleanDatetime($properties['createdate']);
$remotelyCreatedAt = $date?->format('Y-m-d H:i:s');
}
$closedStages = $this->getClosedDealStages();
$isWon = in_array($properties['dealstage'], $closedStages['won']);
$isLost = in_array($properties['dealstage'], $closedStages['lost']);
$data = [
'team_id' => $this->team->getId(),
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => $name,
'value' => ! empty($amount) ? $amount : null,
'currency_code' => CurrencyFormatter::formatCode($currency),
'close_date' => $closeDate,
'is_closed' => $isWon || $isLost,
'is_won' => $isWon,
'remotely_created_at' => $remotelyCreatedAt,
'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),
'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),
];
if ($accountId) {
$data['account_id'] = $accountId;
}
if ($stage) {
$data['stage_id'] = $stage->id;
}
if ($businessProcess) {
$recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);
if ($recordType) {
$data['record_type_id'] = $recordType->id;
}
}
return $data;
}
private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess
{
if ($pipelineId === null) {
return null;
}
if (isset($this->cachedBusinessProcesses[$pipelineId])) {
return $this->cachedBusinessProcesses[$pipelineId];
}
$businessProcess = $this->getBusinessProcess($pipelineId);
if (! $businessProcess instanceof BusinessProcess) {
$this->importStages();
$businessProcess = $this->getBusinessProcess($pipelineId);
}
if (! $businessProcess instanceof BusinessProcess) {
$this->logger->info(
'[HubSpot] Deal is not attached to a pipeline',
[
'pipeline' => $pipelineId]
);
}
$this->cachedBusinessProcesses[$pipelineId] = $businessProcess;
return $businessProcess;
}
private function getBusinessProcess(string $pipelineId): ?BusinessProcess
{
return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);
}
private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage
{
if (empty($stageId)) {
return null;
}
$cacheKey = $businessProcess->getId() . ':' . $stageId;
if (isset($this->cachedStages[$cacheKey])) {
return $this->cachedStages[$cacheKey];
}
$stage = $this->crmEntityRepository->getPipelineStageByConditions(
$businessProcess,
[
'crm_provider_id' => $stageId,
'type' => Stage::TYPE_OPPORTUNITY,
]
);
if ($stage === null) {
$this->importStages(null, $stageId);
}
if ($stage === null) {
$this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);
}
$this->cachedStages[$cacheKey] = $stage;
return $stage;
}
private function resolveAmount(array $properties): ?string
{
$amount = null;
if (! empty($properties['amount'])) {
$amount = str_replace(',', '', $properties['amount']);
}
if ($this->config->hasDefaultCurrencyFieldSet()) {
$valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();
$amount = $properties[$valueFieldName] ?? $amount;
}
return $amount;
}
private function parseCleanDatetime(string $datetime): ?Carbon
{
// Treat pre-1980 values as invalid
$minValidDate = Carbon::parse('1980-01-01 00:00:00');
try {
$date = Carbon::parse($datetime);
if ($minValidDate->gt($date)) {
return null;
}
return $date;
} catch (Exception) {
return null; // On parse error, treat as null
}
}
private function resolveDealProbability(?string $stageProbability): int
{
if ($stageProbability === null) {
return 0;
}
$probability = (float) $stageProbability;
return $probability > 1 ? 0 : (int) ($probability * 100);
}
private function resolveForecastCategory(?string $forecastCategory): string
{
if (! $forecastCategory) {
return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;
}
$forecastCategory = str_replace('_', ' ', $forecastCategory);
return ucwords(strtolower($forecastCategory));
}
private function importExternalFieldData(array $properties, int $opportunityId): void
{
$crmFields = $this->getOpportunitySyncableFields();
$this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);
}
private function importOpportunityContacts(Opportunity $opportunity, array $associations): void
{
// Handle empty or missing contact associations
if (empty($associations)) {
// Remove all existing contact associations if none provided
$this->removeAllOpportunityContacts($opportunity);
return;
}
// Use differential sync approach for better performance and accuracy
$this->syncOpportunityContactsDifferential($opportunity, $associations);
}
/**
* Sync opportunity contacts using differential approach
* This compares current vs new associations and only makes necessary changes
*/
private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void
{
$currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);
$contactAssociationIds = array_keys($contactAssociations);
$contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);
$contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);
if (empty($contactsToAdd) && empty($contactsToRemove)) {
return;
}
$this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);
$this->removeContactAssociations($opportunity, $contactsToRemove);
$this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);
}
private function getCurrentContactCrmIds(Opportunity $opportunity): array
{
return $opportunity->contacts()
->pluck('contacts.crm_provider_id')
->toArray();
}
private function logContactAssociationChanges(
Opportunity $opportunity,
array $currentContactCrmIds,
array $contactAssociations,
array $contactsToAdd,
array $contactsToRemove
): void {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [
'opportunity_id' => $opportunity->getId(),
'current_contacts' => $currentContactCrmIds,
'new_contacts' => $contactAssociations,
'contacts_to_add' => $contactsToAdd,
'contacts_to_remove' => $contactsToRemove,
]);
}
private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void
{
if (empty($contactsToRemove)) {
return;
}
$contactsToDetach = $opportunity->contacts()
->whereIn('contacts.crm_provider_id', $contactsToRemove)
->pluck('contacts.id')
->toArray();
if (! empty($contactsToDetach)) {
$opportunity->contacts()->detach($contactsToDetach);
$this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_contact_crm_ids' => $contactsToRemove,
'removed_contact_count' => count($contactsToDetach),
]);
}
}
private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void
{
if (empty($contactsToAdd)) {
return;
}
$contactsAdded = [];
foreach ($contactsToAdd as $crmId) {
$id = $contactAssociations[$crmId];
if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {
$contactsAdded[] = $crmId;
}
}
$this->logAddedContacts($opportunity, $contactsAdded);
}
private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool
{
try {
$contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);
if (! $contact) {
return false;
}
return $this->performContactAttachment($opportunity, $contact, $crmId);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [
'opportunity_id' => $opportunity->getId(),
'contact_crm_id' => $crmId,
'error' => $e->getMessage(),
]);
return false;
}
}
private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool
{
try {
$opportunity->contacts()->attach($contact->getId(), [
'crm_provider_id' => $crmId,
]);
return true;
} catch (\Illuminate\Database\QueryException $e) {
if (str_contains($e->getMessage(), 'Duplicate entry')) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [
'contact_id' => $contact->getId(),
'contact_crm_id' => $crmId,
'opportunity_id' => $opportunity->getId(),
]);
return false;
}
throw $e;
}
}
private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void
{
if (! empty($contactsAdded)) {
$this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [
'opportunity_id' => $opportunity->getId(),
'contacts_to_add_count' => count($contactsAdded),
'added_contact_crm_ids' => $contactsAdded,
'added_contacts_count' => count($contactsAdded),
]);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
1
Previous Highlighted Error
Next Highlighted Error
<template>
<WelcomeLayout
title="Account disconnected"
textPosition="center"
:icon="faUnlink"
:class="$style.layout"
>
<div :class="$style.container" v-if="providersLoaded">
<p>
<strong>
It looks like your {{ localProvider.displayName }} account has become
disconnected
</strong>
</p>
<p :class="$style.small">Please re-connect to continue</p>
<p v-if="isInIframe">
We'll open the {{ localProvider.displayName }} authentication in a new
tab. Please return here and refresh the page once complete
</p>
<GoogleLikeButton
v-if="localProvider.viaIntegrationApp && crmTokenLoaded"
as="a"
:key="localProvider.name"
:brand-logo="localProvider.name"
:class="$style.connectButton"
@click="integrationAppOnClick"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
<GoogleLikeButton
v-if="!localProvider.viaIntegrationApp"
as="a"
:key="localProvider.name"
:href="`/auth/redirect/${localProvider.name}`"
:target="target"
:brand-logo="localProvider.name"
:class="$style.connectButton"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
</div>
<BuildInfo />
<KioskBanner />
</WelcomeLayout>
</template>
<script>
import window from "window";
import axios from "axios";
import { faUnlink } from "@fortawesome/pro-regular-svg-icons";
import isInIframe from "@/utils/isInIframe";
import BuildInfo from "@/components/layout/BuildInfo/BuildInfo.vue";
import KioskBanner from "@/components/shared/KioskBanner/KioskBanner.vue";
import WelcomeLayout from "@/components/layout/WelcomeLayout/WelcomeLayout.vue";
import GoogleLikeButton from "@/components/shared/Buttons/GoogleLikeButton.vue";
import { showSnackbarError, normalizeError } from "@/utils/index";
import { IntegrationAppClient } from "@integration-app/sdk";
export default {
name: "ConnectPage",
components: {
BuildInfo,
KioskBanner,
WelcomeLayout,
GoogleLikeButton,
},
data() {
return {
...window.connectData,
crmToken: null,
faUnlink,
isInIframe,
providers: [],
providersLoaded: false,
crmTokenLoaded: false,
};
},
computed: {
localProvider() {
return this.providers.find((e) => e.name === this.provider);
},
target() {
return this.isInIframe ? "_blank" : null;
},
},
created() {
this.getProviders();
},
mounted() {
this.showErrors();
},
watch: {
providersLoaded() {
if (this.providersLoaded) {
this.prepareIntegrationAppConnection();
}
},
},
methods: {
showErrors() {
if (!this.error) return;
showSnackbarError(this.error, undefined, undefined, false);
},
unwrapEntityResponse({ data }) {
return data.map(({ icon, name, displayName, viaIntegrationApp }) => {
return { icon, name, displayName, viaIntegrationApp };
});
},
async getProviders() {
try {
const response = await axios.get("/api/v1/connect-providers");
this.providers = this.unwrapEntityResponse(response);
this.providersLoaded = true;
} catch {
showSnackbarError(
"An error occurred, while loading form data (connect providers).",
);
}
},
async prepareIntegrationAppConnection() {
if (this.localProvider.viaIntegrationApp) {
try {
const response = await axios.get("/api/v1/integration-app-token");
this.crmToken = response.data.token;
this.crmTokenLoaded = true;
} catch (error) {
console.log(error);
showSnackbarError(
`An error occurred while preparing the page.
Try refreshing, if the error persists get in touch with the Jiminny team.`,
);
}
}
},
async integrationAppOnClick() {
const integrationApp = new IntegrationAppClient({
token: this.crmToken,
});
const connection = await integrationApp
.integration(this.localProvider.name)
.openNewConnection({
showPoweredBy: false,
allowMultipleConnections: false,
});
console.log('[IntegrationApp] openNewConnection resolved:', JSON.stringify(connection));
// [IntegrationApp] openNewConnection resolved: {
// "id":"69e0b41a67d0068c2ca0b48e",
// "name":"Zoho CRM",
// "userId":"1ece66c8-feb1-4df1-b321-21607daf4623",
// "tenantId":"69e0b3faef3e7b6248189289",
// "isTest":false,
// "connected":true,
// "state":"READY",
// "errors":[],
// "integrationId":"66fe6c913202f3a165e3c14d",
// "externalAppId":"6671653e7e2d642e4e41b0fa",
// "authOptionKey":"",
// "createdAt":"2026-04-16T10:04:10.420Z",
// "updatedAt":"2026-04-16T10:04:10.575Z",
// "retryAttempts":0,
// "isDeactivated":false
// }
if (connection && connection.disconnected !== true && connection.connected !== false) {
console.log('[IntegrationApp] connection condition matched');
try {
const saveRequest = await axios.post(
"/api/v1/integration-app-connect",
);
if (saveRequest.data && saveRequest.data.success === true) {
/** If all is good refresh the page here */
window.location = "/dashboard";
return;
}
throw new Error(saveRequest.data.message);
} catch (error) {
console.log(error);
showSnackbarError(normalizeError(error));
}
}
},
},
};
</script>
<style module lang="less" src="./connect.less"></style>
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.03046875,"top":0.017361112,"width":0.0453125,"height":0.022222223},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20692-fix-integration-app-token-auth-response-change, menu","depth":5,"bounds":{"left":0.07578125,"top":0.017361112,"width":0.15898438,"height":0.022222223},"help_text":"Git Branch: JY-20692-fix-integration-app-token-auth-response-change","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.78515625,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsCommandTest","depth":6,"bounds":{"left":0.803125,"top":0.017361112,"width":0.09765625,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsCommandTest'","depth":6,"bounds":{"left":0.9007813,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsCommandTest'","depth":6,"bounds":{"left":0.9140625,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9273437,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96015626,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9734375,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9867188,"top":0.017361112,"width":0.013281226,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show Replace Field","depth":4,"bounds":{"left":0.12382813,"top":0.22083333,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Search History","depth":3,"bounds":{"left":0.13867188,"top":0.22013889,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"cachedStages","depth":4,"bounds":{"left":0.1515625,"top":0.22013889,"width":0.0515625,"height":0.013888889},"value":"cachedStages","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"bounds":{"left":0.21367188,"top":0.22013889,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Match Case","depth":3,"bounds":{"left":0.22539063,"top":0.22013889,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Words","depth":3,"bounds":{"left":0.23554687,"top":0.22013889,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Regex","depth":3,"bounds":{"left":0.24570313,"top":0.22013889,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Replace History","depth":3,"bounds":{"left":0.23320313,"top":1.0,"width":0.00859375,"height":0.0},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Replace","depth":4,"role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"bounds":{"left":0.23320313,"top":1.0,"width":0.00859375,"height":0.0},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Preserve case","depth":3,"bounds":{"left":0.23320313,"top":1.0,"width":0.00859375,"height":0.0},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2/4","depth":4,"bounds":{"left":0.26171875,"top":0.21944444,"width":0.030078124,"height":0.015277778},"role_description":"text"},{"role":"AXButton","text":"Previous Occurrence","depth":4,"bounds":{"left":0.29179686,"top":0.21875,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Occurrence","depth":4,"bounds":{"left":0.30195314,"top":0.21875,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Filter Search Results","depth":4,"bounds":{"left":0.31210938,"top":0.21875,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open in Window, Multiple Cursors","depth":4,"bounds":{"left":0.32226562,"top":0.21875,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Click to highlight","depth":4,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":4,"bounds":{"left":0.38320312,"top":0.21875,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.049609374,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"33","depth":4,"bounds":{"left":0.3421875,"top":0.24583334,"width":0.012109375,"height":0.013194445},"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.35664064,"top":0.24583334,"width":0.009375,"height":0.013194445},"role_description":"text"},{"role":"AXStaticText","text":"19","depth":4,"bounds":{"left":0.3683594,"top":0.24583334,"width":0.011328125,"height":0.013194445},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.3816406,"top":0.24444444,"width":0.00859375,"height":0.015972223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.39023438,"top":0.24444444,"width":0.008203125,"height":0.015972223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\CollectionResponseAssociatedId;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Models\\Account;\nuse Exception;\nuse Jiminny\\Component\\DealInsights\\Forecast\\Forecast;\nuse Jiminny\\Jobs\\Crm\\MatchActivitiesToNewOpportunity;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Crm\\BusinessProcess;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Models\\Opportunity;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DealFieldsService;\nuse Jiminny\\Services\\Crm\\Hubspot\\OpportunitySyncStrategy\\HubspotSingleSyncStrategy;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Services\\Crm\\OpportunitySyncStrategyResolver;\nuse Jiminny\\Utils\\CurrencyFormatter;\n\n/**\n * Optimized sync methods for better performance\n * These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains\n */\ntrait OpportunitySyncTrait\n{\n private const int BATCH_SIZE = 100;\n private const int BATCH_PROCESS_SIZE = 800;\n\n protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;\n protected CrmEntityRepository $crmEntityRepository;\n protected DealFieldsService $dealFieldsService;\n\n private ?array $cachedClosedDealStages = null;\n private array $cachedBusinessProcesses = [];\n private array $cachedStages = [];\n\n public function syncOpportunities(array $parameters, ?string $strategy = null): int\n {\n $strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);\n $parameters['config'] = $this->config;\n $syncCount = 0;\n $reportedTotal = 0;\n $lastSyncedId = [];\n\n try {\n foreach ($strategies as $strategyName => $syncStrategy) {\n $this->logger->info(\n '[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .\n $strategyName\n );\n\n $total = 0;\n $lastId = null;\n $buffer = [];\n\n // HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies\n foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {\n $buffer[] = $hsOpportunity;\n\n // process every 800 rows (fits < 1 000 association limit)\n if (\\count($buffer) >= self::BATCH_PROCESS_SIZE) {\n $syncCount += $this->processOpportunityBatch($buffer);\n $buffer = [];\n }\n }\n\n // leftovers\n if ($buffer) {\n $syncCount += $this->processOpportunityBatch($buffer);\n }\n\n $reportedTotal += $total;\n $lastSyncedId = $lastId;\n }\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException | CrmException $e) {\n $this->handleSyncException($e, $parameters);\n }\n\n $this->logger->info(\n '[HubSpot] Synced opportunities',\n [\n 'team' => $this->team->getId(),\n 'sync_count' => $syncCount,\n 'total' => $reportedTotal,\n 'last_synced_id' => $lastSyncedId,\n ]\n );\n\n return $reportedTotal;\n }\n\n private function handleSyncException(\\Throwable $e, array $parameters): void\n {\n if (($parameters['since'] ?? null) instanceof Carbon) {\n $parameters['since'] = $parameters['since']->toDateTimeString();\n }\n $parameters['config'] = $this->config->getId();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [\n 'teamId' => $this->team->getUuid(),\n 'parameters' => $parameters,\n 'reason' => $e->getMessage(),\n ]);\n }\n\n /**\n * @inheritdoc\n */\n public function syncOpportunity(string $crmId): ?Opportunity\n {\n $strategy = $this->opportunitySyncStrategyResolver->resolve(\n $this->config,\n OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,\n );\n\n $parameters = [\n 'config' => $this->config,\n 'crm_id' => $crmId,\n ];\n\n try {\n if (! $strategy instanceof HubspotSingleSyncStrategy) {\n throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');\n }\n\n $hsOpportunity = $strategy->fetchOpportunity($parameters);\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);\n\n return $this->importOrUpdateOpportunity($hsOpportunity);\n }\n\n /**\n * Process webhook-collected opportunity batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportOpportunityBatch jobs for batch processing.\n *\n * @return int Number of opportunity IDs dispatched to jobs\n */\n public function batchSyncOpportunities(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,\n $configId\n );\n }\n\n /**\n * Import a batch of opportunities by their CRM IDs.\n * Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().\n *\n * @param array<string> $crmIds HubSpot deal CRM IDs\n *\n * @return array{success: array, failed_ids: array, errors?: array<string, string>}\n */\n public function importOpportunityBatchByIds(array $crmIds): array\n {\n $fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);\n\n $allDeals = [];\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n $deals = $this->client->getOpportunitiesByIds($chunk, $fields);\n foreach ($deals as $deal) {\n $allDeals[] = $deal;\n }\n }\n\n // IDs not returned by HubSpot are likely deleted or inaccessible deals.\n // These are not failures — retrying won't bring them back.\n $fetchedIds = array_map('strval', array_column($allDeals, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allDeals),\n ]);\n }\n\n if (empty($allDeals)) {\n return ['success' => [], 'failed_ids' => []];\n }\n\n return $this->importOpportunityBatch($allDeals);\n }\n\n private function getClosedDealStages(): array\n {\n if ($this->cachedClosedDealStages !== null) {\n return $this->cachedClosedDealStages;\n }\n\n $stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);\n $data = [\n 'lost' => [],\n 'won' => [],\n ];\n\n foreach ($stages as $stage) {\n if ($stage->probability == 0.00) {\n $data['lost'][] = $stage->crm_provider_id;\n }\n if ($stage->probability == 100.00) {\n $data['won'][] = $stage->crm_provider_id;\n }\n }\n\n $this->cachedClosedDealStages = $data;\n\n return $data;\n }\n\n /**\n * Import deals into the database with pre-fetched associations.\n *\n * API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT\n * caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()\n * where Laravel retries the whole job with backoff. After all retries exhausted,\n * failed() requeues all IDs to Redis.\n *\n * The per-deal loop catches exceptions individually. A deal can end up in three states:\n * - success: imported/updated successfully\n * - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)\n * These are permanent issues — retrying won't fix them.\n * - skipped (null): missing dependencies (no account, unknown pipeline/stage).\n * This is acceptable — the deal cannot be imported until those exist.\n */\n private function importOpportunityBatch(array $deals): array\n {\n $syncedOpportunities = [\n 'success' => [],\n 'failed_ids' => [],\n ];\n $dealIds = array_column($deals, 'id');\n\n // Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the\n // queue job retries the whole batch and eventually requeues all deal IDs back to Redis.\n try {\n $companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');\n $contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');\n\n $associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);\n\n $existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(\n $this->config,\n array_map('strval', $dealIds)\n );\n $existingCrmIdSet = array_flip($existingCrmIds);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [\n 'teamId' => $this->team->getId(),\n 'dealCount' => count($dealIds),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n foreach ($deals as $deal) {\n try {\n $deal['associations'] = $this->prepareAssociationsForOpportunity(\n $deal['id'],\n $companyAssociations,\n $contactAssociations,\n $associationsData\n );\n\n $syncedOpportunity = $this->importOrUpdateOpportunity(\n $deal,\n isset($existingCrmIdSet[(string) $deal['id']])\n );\n if ($syncedOpportunity) {\n $syncedOpportunities['success'][] = $syncedOpportunity;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $deal['id'],\n 'error' => $e->getMessage(),\n ]);\n $syncedOpportunities['failed_ids'][] = $deal['id'];\n $syncedOpportunities['errors'][$deal['id']] = $e->getMessage();\n }\n }\n\n return $syncedOpportunities;\n }\n\n /**\n * Prepare associated entities for opportunities with optimized batch processing\n * Returns structured data with CRM ID to DB ID mappings for each opportunity\n */\n private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array\n {\n // Step 1: Collect all unique company and contact IDs from associations\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n\n // Step 2: Batch sync missing entities and get CRM ID to DB ID mappings\n $companyIdMappings = [];\n $contactIdMappings = [];\n\n if (! empty($allCompanyIds)) {\n $companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);\n }\n\n if (! empty($allContactIds)) {\n $contactIdMappings = $this->prepareAssociatedContacts($allContactIds);\n }\n\n return [\n 'company_id_mappings' => $companyIdMappings,\n 'contact_id_mappings' => $contactIdMappings,\n ];\n }\n\n /**\n * Flatten association data to get unique IDs\n */\n private function flattenAssociationIds(array $associations): array\n {\n $ids = [];\n foreach ($associations as $dealAssociations) {\n if (is_array($dealAssociations)) {\n foreach ($dealAssociations as $id) {\n $ids[$id] = true;\n }\n }\n }\n\n return array_keys($ids);\n }\n\n /**\n * Batch sync missing accounts\n */\n private function prepareAssociatedAccounts(array $companyIds): array\n {\n // Find which accounts already exist\n $existingAccounts = $this->crmEntityRepository\n ->findAccountsByExternalIds($this->config, $companyIds);\n\n $existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();\n\n $existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {\n return [$account->getCrmProviderId() => $account->getId()];\n })->toArray();\n\n $missingCompanyIds = array_diff($companyIds, $existingCompanyIds);\n\n if (empty($missingCompanyIds)) {\n return $existingAccountsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [\n 'teamId' => $this->team->getUuid(),\n 'total_companies' => count($companyIds),\n 'existing_companies' => count($existingCompanyIds),\n 'missing_companies' => count($missingCompanyIds),\n ]);\n\n // we already have limit on opportunity ids count\n // Initialize variable before try block\n $syncedAccountsData = [];\n\n try {\n $syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [\n 'size' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedAccountsData = [];\n }\n\n return $existingAccountsData + $syncedAccountsData;\n }\n\n /**\n * Prepare associated contacts - find existing and sync missing ones\n * Returns mapping of CRM ID to DB ID\n */\n private function prepareAssociatedContacts(array $contactIds): array\n {\n // Find which contacts already exist\n $existingContacts = $this->crmEntityRepository\n ->findContactsByExternalIds($this->config, $contactIds);\n\n $existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();\n\n // Create mapping for existing contacts\n $existingContactsData = $existingContacts->mapWithKeys(function ($contact) {\n return [$contact->getCrmProviderId() => $contact->getId()];\n })->toArray();\n\n $missingContactIds = array_diff($contactIds, $existingContactIds);\n\n if (empty($missingContactIds)) {\n return $existingContactsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [\n 'teamId' => $this->team->getUuid(),\n 'total_contacts' => count($contactIds),\n 'existing_contacts' => count($existingContactIds),\n 'missing_contacts' => count($missingContactIds),\n ]);\n\n // Sync missing contacts using batch API\n try {\n $syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [\n 'size' => count($missingContactIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedContactsData = [];\n }\n\n return $existingContactsData + $syncedContactsData;\n }\n\n private function batchSyncCrmObjects(string $objectType, array $crmIds): array\n {\n $syncObjects = [];\n $crmObjectIds = array_values($crmIds);\n\n foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {\n try {\n $objects = $objectType === 'companies' ?\n $this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :\n $this->client->getContactsByIds($chunk, $this->getContactFields());\n\n foreach ($objects as $objectId => $objectData) {\n $this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [\n 'requested_count' => count($chunk),\n 'synced_count' => count($objects),\n ]);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [\n 'ids' => $chunk,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncObjects;\n }\n\n private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void\n {\n try {\n $object = $objectType === 'companies' ?\n $this->importAccount($objectData) :\n $this->importContact($objectData);\n\n if ($object) {\n $syncObjects[$object->getCrmProviderId()] = $object->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [\n 'id' => $objectId,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n /**\n * Prepare associations for a single opportunity\n *\n * The return value is an array with the following structure:\n * [\n * 'companies' => [\n * $companyCrmId => $companyId,\n * ...\n * ],\n * 'contacts' => [\n * $contactCrmId => $contactId,\n * ...\n * ],\n * 'account_id' => $accountId,\n * ]\n */\n private function prepareAssociationsForOpportunity(\n string $oppCrmId,\n array $companyAssociations,\n array $contactAssociations,\n array $associationsData\n ): array {\n $associations = [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n\n $oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];\n foreach ($oppCompanyIds as $companyCrmId) {\n if (isset($associationsData['company_id_mappings'][$companyCrmId])) {\n $associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];\n\n // Set primary account (first company becomes primary account)\n if ($associations['account_id'] === null) {\n $associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];\n }\n }\n }\n\n $oppContactIds = $contactAssociations[$oppCrmId] ?? [];\n foreach ($oppContactIds as $contactCrmId) {\n if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {\n $associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];\n }\n }\n\n return $associations;\n }\n\n /**\n * Update only associations for an opportunity\n */\n private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void\n {\n // Update contact associations\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n // Update company (account) associations\n $this->updateOpportunityAccount($opportunity, $associations['account_id']);\n }\n\n /**\n * Remove all contact associations from an opportunity\n */\n private function removeAllOpportunityContacts(Opportunity $opportunity): void\n {\n $currentCount = (int) $opportunity->contacts()->count();\n\n if ($currentCount > 0) {\n $opportunity->contacts()->detach();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_count' => $currentCount,\n ]);\n }\n }\n\n private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void\n {\n if ($accountId === null) {\n // No account ID provided - keep current account\n return;\n }\n\n $currentAccountId = $opportunity->getAccountId();\n\n // Only update if account has changed\n if ($currentAccountId !== $accountId) {\n $opportunity->account_id = $accountId;\n $opportunity->save();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [\n 'opportunity_id' => $opportunity->getId(),\n 'old_account_id' => $currentAccountId,\n 'new_account_id' => $accountId,\n ]);\n }\n }\n\n /**\n * Find existing opportunities by external IDs (OPTIMIZED VERSION)\n * Uses batch query for better performance\n */\n private function findExistingOpportunities(array $crmIds): Collection\n {\n return $this->crmEntityRepository\n ->findOpportunitiesByExternalIds($this->config, $crmIds);\n }\n\n private function processOpportunityBatch(array $opportunities): int\n {\n $syncedOpportunities = $this->importOpportunityBatch($opportunities);\n\n return count($syncedOpportunities['success'] ?? []);\n }\n\n /**\n * Convert single deal associations from HubSpot format to internal format\n * Handles both HubSpot SDK objects and array formats\n *\n * @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed\n *\n * @return array Processed associations with DB IDs\n */\n private function convertDealAssociations(array $opportunityAssociations): array\n {\n $associations = $this->initializeAssociationsStructure();\n\n if (empty($opportunityAssociations)) {\n return $associations;\n }\n\n $associationIds = $this->extractAssociationIds($opportunityAssociations);\n\n $this->processCompanyAssociations($associationIds, $associations);\n $this->processContactAssociations($associationIds, $associations);\n\n return $associations;\n }\n\n private function initializeAssociationsStructure(): array\n {\n return [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n }\n\n private function extractAssociationIds(array $opportunityAssociations): array\n {\n $associationIds = [];\n\n foreach ($opportunityAssociations as $type => $associationData) {\n if (! empty($associationData)) {\n $associationIds[$type] = $this->convertSingleDealAssociations($associationData);\n }\n }\n\n return $associationIds;\n }\n\n private function processCompanyAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['companies'])) {\n return;\n }\n\n $companyId = $associationIds['companies'][0];\n $account = $this->findOrSyncAccount($companyId);\n\n if ($account instanceof Account) {\n $associations['companies'][$companyId] = $account->getId();\n $associations['account_id'] = $account->getId();\n }\n }\n\n private function processContactAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['contacts'])) {\n return;\n }\n\n foreach ($associationIds['contacts'] as $contactId) {\n $contact = $this->findOrSyncContact($contactId);\n\n if ($contact instanceof Contact) {\n $associations['contacts'][$contactId] = $contact->getId();\n }\n }\n }\n\n private function findOrSyncAccount(string $companyId): ?Account\n {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);\n\n if (! $account instanceof Account) {\n $account = $this->syncAccount($companyId);\n }\n\n return $account;\n }\n\n private function findOrSyncContact(string $contactId): ?Contact\n {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);\n\n if (! $contact instanceof Contact) {\n $contact = $this->syncContact($contactId);\n }\n\n return $contact;\n }\n\n private function convertSingleDealAssociations($opportunityAssociations = null): array\n {\n $associationData = [];\n\n if ($opportunityAssociations === null) {\n return $associationData;\n }\n\n // Handle array input (from extractAssociationIds)\n if (is_array($opportunityAssociations)) {\n return $opportunityAssociations;\n }\n\n // Handle CollectionResponseAssociatedId object\n if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {\n foreach ($opportunityAssociations->getResults() as $association) {\n $associationData[] = $association->getId();\n }\n }\n\n return $associationData;\n }\n\n private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity\n {\n if (empty($crmData['properties'])) {\n return null;\n }\n\n $crmId = (string) $crmData['id'];\n $properties = $crmData['properties'];\n $associations = $crmData['associations'] ?? [];\n\n $opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(\n $this->config,\n $crmId\n );\n\n if ($opportunityExists) {\n return $this->updateOpportunity($crmId, $properties, $associations);\n } else {\n return $this->createOpportunity($crmId, $properties, $associations);\n }\n }\n\n /**\n * Create new opportunity\n */\n private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n if (! $accountId) {\n return null;\n }\n\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n if (! $businessProcess) {\n return null;\n }\n\n $stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);\n if (! $stage) {\n return null;\n }\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n if ($opportunity->wasRecentlyCreated) {\n MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());\n }\n\n return $opportunity;\n }\n\n /**\n * Update existing opportunity\n */\n private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n $stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->updateOpportunityAssociations($opportunity, $associations);\n\n return $opportunity;\n }\n\n private function resolveAccountId(array $associations): ?int\n {\n if (! empty($associations['accountId'])) {\n return $associations['accountId'];\n }\n\n if (empty($associations)) {\n return null;\n }\n\n // we can't resolve multiple account ids (currently SDK returns one company)\n foreach ($associations['companies'] as $accountId) {\n return $accountId;\n }\n\n return null;\n }\n\n private function buildOpportunityData(\n array $properties,\n ?int $accountId,\n ?BusinessProcess $businessProcess,\n ?Stage $stage\n ): array {\n $ownerId = null;\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $name = 'Unknown';\n if (isset($properties['dealname'])) {\n $name = mb_strimwidth($properties['dealname'], 0, 128);\n }\n\n $amount = $this->resolveAmount($properties);\n $currency = $properties['deal_currency_code'] ?? null;\n\n $closeDate = null;\n if (! empty($properties['closedate'])) {\n $closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');\n }\n\n $remotelyCreatedAt = null;\n if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {\n $date = $this->parseCleanDatetime($properties['createdate']);\n $remotelyCreatedAt = $date?->format('Y-m-d H:i:s');\n }\n\n $closedStages = $this->getClosedDealStages();\n $isWon = in_array($properties['dealstage'], $closedStages['won']);\n $isLost = in_array($properties['dealstage'], $closedStages['lost']);\n\n $data = [\n 'team_id' => $this->team->getId(),\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'value' => ! empty($amount) ? $amount : null,\n 'currency_code' => CurrencyFormatter::formatCode($currency),\n 'close_date' => $closeDate,\n 'is_closed' => $isWon || $isLost,\n 'is_won' => $isWon,\n 'remotely_created_at' => $remotelyCreatedAt,\n 'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),\n 'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),\n ];\n\n if ($accountId) {\n $data['account_id'] = $accountId;\n }\n\n if ($stage) {\n $data['stage_id'] = $stage->id;\n }\n\n if ($businessProcess) {\n $recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);\n if ($recordType) {\n $data['record_type_id'] = $recordType->id;\n }\n }\n\n return $data;\n }\n\n private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess\n {\n if ($pipelineId === null) {\n return null;\n }\n\n if (isset($this->cachedBusinessProcesses[$pipelineId])) {\n return $this->cachedBusinessProcesses[$pipelineId];\n }\n\n $businessProcess = $this->getBusinessProcess($pipelineId);\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->importStages();\n $businessProcess = $this->getBusinessProcess($pipelineId);\n }\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->logger->info(\n '[HubSpot] Deal is not attached to a pipeline',\n [\n 'pipeline' => $pipelineId]\n );\n }\n\n $this->cachedBusinessProcesses[$pipelineId] = $businessProcess;\n\n return $businessProcess;\n }\n\n private function getBusinessProcess(string $pipelineId): ?BusinessProcess\n {\n return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);\n }\n\n private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage\n {\n if (empty($stageId)) {\n return null;\n }\n\n $cacheKey = $businessProcess->getId() . ':' . $stageId;\n if (isset($this->cachedStages[$cacheKey])) {\n return $this->cachedStages[$cacheKey];\n }\n\n $stage = $this->crmEntityRepository->getPipelineStageByConditions(\n $businessProcess,\n [\n 'crm_provider_id' => $stageId,\n 'type' => Stage::TYPE_OPPORTUNITY,\n ]\n );\n\n if ($stage === null) {\n $this->importStages(null, $stageId);\n }\n\n if ($stage === null) {\n $this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);\n }\n\n $this->cachedStages[$cacheKey] = $stage;\n\n return $stage;\n }\n\n private function resolveAmount(array $properties): ?string\n {\n $amount = null;\n if (! empty($properties['amount'])) {\n $amount = str_replace(',', '', $properties['amount']);\n }\n\n if ($this->config->hasDefaultCurrencyFieldSet()) {\n $valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();\n $amount = $properties[$valueFieldName] ?? $amount;\n }\n\n return $amount;\n }\n\n private function parseCleanDatetime(string $datetime): ?Carbon\n {\n // Treat pre-1980 values as invalid\n $minValidDate = Carbon::parse('1980-01-01 00:00:00');\n\n try {\n $date = Carbon::parse($datetime);\n\n if ($minValidDate->gt($date)) {\n return null;\n }\n\n return $date;\n } catch (Exception) {\n return null; // On parse error, treat as null\n }\n }\n\n private function resolveDealProbability(?string $stageProbability): int\n {\n if ($stageProbability === null) {\n return 0;\n }\n\n $probability = (float) $stageProbability;\n\n return $probability > 1 ? 0 : (int) ($probability * 100);\n }\n\n private function resolveForecastCategory(?string $forecastCategory): string\n {\n if (! $forecastCategory) {\n return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;\n }\n\n $forecastCategory = str_replace('_', ' ', $forecastCategory);\n\n return ucwords(strtolower($forecastCategory));\n }\n\n private function importExternalFieldData(array $properties, int $opportunityId): void\n {\n $crmFields = $this->getOpportunitySyncableFields();\n $this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);\n }\n\n private function importOpportunityContacts(Opportunity $opportunity, array $associations): void\n {\n // Handle empty or missing contact associations\n if (empty($associations)) {\n // Remove all existing contact associations if none provided\n $this->removeAllOpportunityContacts($opportunity);\n\n return;\n }\n\n // Use differential sync approach for better performance and accuracy\n $this->syncOpportunityContactsDifferential($opportunity, $associations);\n }\n\n /**\n * Sync opportunity contacts using differential approach\n * This compares current vs new associations and only makes necessary changes\n */\n private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void\n {\n $currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);\n $contactAssociationIds = array_keys($contactAssociations);\n\n $contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);\n $contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);\n\n if (empty($contactsToAdd) && empty($contactsToRemove)) {\n return;\n }\n\n $this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);\n\n $this->removeContactAssociations($opportunity, $contactsToRemove);\n $this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);\n }\n\n private function getCurrentContactCrmIds(Opportunity $opportunity): array\n {\n return $opportunity->contacts()\n ->pluck('contacts.crm_provider_id')\n ->toArray();\n }\n\n private function logContactAssociationChanges(\n Opportunity $opportunity,\n array $currentContactCrmIds,\n array $contactAssociations,\n array $contactsToAdd,\n array $contactsToRemove\n ): void {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [\n 'opportunity_id' => $opportunity->getId(),\n 'current_contacts' => $currentContactCrmIds,\n 'new_contacts' => $contactAssociations,\n 'contacts_to_add' => $contactsToAdd,\n 'contacts_to_remove' => $contactsToRemove,\n ]);\n }\n\n private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void\n {\n if (empty($contactsToRemove)) {\n return;\n }\n\n $contactsToDetach = $opportunity->contacts()\n ->whereIn('contacts.crm_provider_id', $contactsToRemove)\n ->pluck('contacts.id')\n ->toArray();\n\n if (! empty($contactsToDetach)) {\n $opportunity->contacts()->detach($contactsToDetach);\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_contact_crm_ids' => $contactsToRemove,\n 'removed_contact_count' => count($contactsToDetach),\n ]);\n }\n }\n\n private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void\n {\n if (empty($contactsToAdd)) {\n return;\n }\n\n $contactsAdded = [];\n foreach ($contactsToAdd as $crmId) {\n $id = $contactAssociations[$crmId];\n\n if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {\n $contactsAdded[] = $crmId;\n }\n }\n\n $this->logAddedContacts($opportunity, $contactsAdded);\n }\n\n private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);\n\n if (! $contact) {\n return false;\n }\n\n return $this->performContactAttachment($opportunity, $contact, $crmId);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [\n 'opportunity_id' => $opportunity->getId(),\n 'contact_crm_id' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool\n {\n try {\n $opportunity->contacts()->attach($contact->getId(), [\n 'crm_provider_id' => $crmId,\n ]);\n\n return true;\n } catch (\\Illuminate\\Database\\QueryException $e) {\n if (str_contains($e->getMessage(), 'Duplicate entry')) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [\n 'contact_id' => $contact->getId(),\n 'contact_crm_id' => $crmId,\n 'opportunity_id' => $opportunity->getId(),\n ]);\n\n return false;\n }\n\n throw $e;\n }\n }\n\n private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void\n {\n if (! empty($contactsAdded)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'contacts_to_add_count' => count($contactsAdded),\n 'added_contact_crm_ids' => $contactsAdded,\n 'added_contacts_count' => count($contactsAdded),\n ]);\n }\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\CollectionResponseAssociatedId;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Models\\Account;\nuse Exception;\nuse Jiminny\\Component\\DealInsights\\Forecast\\Forecast;\nuse Jiminny\\Jobs\\Crm\\MatchActivitiesToNewOpportunity;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Crm\\BusinessProcess;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Models\\Opportunity;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DealFieldsService;\nuse Jiminny\\Services\\Crm\\Hubspot\\OpportunitySyncStrategy\\HubspotSingleSyncStrategy;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Services\\Crm\\OpportunitySyncStrategyResolver;\nuse Jiminny\\Utils\\CurrencyFormatter;\n\n/**\n * Optimized sync methods for better performance\n * These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains\n */\ntrait OpportunitySyncTrait\n{\n private const int BATCH_SIZE = 100;\n private const int BATCH_PROCESS_SIZE = 800;\n\n protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;\n protected CrmEntityRepository $crmEntityRepository;\n protected DealFieldsService $dealFieldsService;\n\n private ?array $cachedClosedDealStages = null;\n private array $cachedBusinessProcesses = [];\n private array $cachedStages = [];\n\n public function syncOpportunities(array $parameters, ?string $strategy = null): int\n {\n $strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);\n $parameters['config'] = $this->config;\n $syncCount = 0;\n $reportedTotal = 0;\n $lastSyncedId = [];\n\n try {\n foreach ($strategies as $strategyName => $syncStrategy) {\n $this->logger->info(\n '[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .\n $strategyName\n );\n\n $total = 0;\n $lastId = null;\n $buffer = [];\n\n // HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies\n foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {\n $buffer[] = $hsOpportunity;\n\n // process every 800 rows (fits < 1 000 association limit)\n if (\\count($buffer) >= self::BATCH_PROCESS_SIZE) {\n $syncCount += $this->processOpportunityBatch($buffer);\n $buffer = [];\n }\n }\n\n // leftovers\n if ($buffer) {\n $syncCount += $this->processOpportunityBatch($buffer);\n }\n\n $reportedTotal += $total;\n $lastSyncedId = $lastId;\n }\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException | CrmException $e) {\n $this->handleSyncException($e, $parameters);\n }\n\n $this->logger->info(\n '[HubSpot] Synced opportunities',\n [\n 'team' => $this->team->getId(),\n 'sync_count' => $syncCount,\n 'total' => $reportedTotal,\n 'last_synced_id' => $lastSyncedId,\n ]\n );\n\n return $reportedTotal;\n }\n\n private function handleSyncException(\\Throwable $e, array $parameters): void\n {\n if (($parameters['since'] ?? null) instanceof Carbon) {\n $parameters['since'] = $parameters['since']->toDateTimeString();\n }\n $parameters['config'] = $this->config->getId();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [\n 'teamId' => $this->team->getUuid(),\n 'parameters' => $parameters,\n 'reason' => $e->getMessage(),\n ]);\n }\n\n /**\n * @inheritdoc\n */\n public function syncOpportunity(string $crmId): ?Opportunity\n {\n $strategy = $this->opportunitySyncStrategyResolver->resolve(\n $this->config,\n OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,\n );\n\n $parameters = [\n 'config' => $this->config,\n 'crm_id' => $crmId,\n ];\n\n try {\n if (! $strategy instanceof HubspotSingleSyncStrategy) {\n throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');\n }\n\n $hsOpportunity = $strategy->fetchOpportunity($parameters);\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);\n\n return $this->importOrUpdateOpportunity($hsOpportunity);\n }\n\n /**\n * Process webhook-collected opportunity batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportOpportunityBatch jobs for batch processing.\n *\n * @return int Number of opportunity IDs dispatched to jobs\n */\n public function batchSyncOpportunities(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,\n $configId\n );\n }\n\n /**\n * Import a batch of opportunities by their CRM IDs.\n * Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().\n *\n * @param array<string> $crmIds HubSpot deal CRM IDs\n *\n * @return array{success: array, failed_ids: array, errors?: array<string, string>}\n */\n public function importOpportunityBatchByIds(array $crmIds): array\n {\n $fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);\n\n $allDeals = [];\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n $deals = $this->client->getOpportunitiesByIds($chunk, $fields);\n foreach ($deals as $deal) {\n $allDeals[] = $deal;\n }\n }\n\n // IDs not returned by HubSpot are likely deleted or inaccessible deals.\n // These are not failures — retrying won't bring them back.\n $fetchedIds = array_map('strval', array_column($allDeals, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allDeals),\n ]);\n }\n\n if (empty($allDeals)) {\n return ['success' => [], 'failed_ids' => []];\n }\n\n return $this->importOpportunityBatch($allDeals);\n }\n\n private function getClosedDealStages(): array\n {\n if ($this->cachedClosedDealStages !== null) {\n return $this->cachedClosedDealStages;\n }\n\n $stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);\n $data = [\n 'lost' => [],\n 'won' => [],\n ];\n\n foreach ($stages as $stage) {\n if ($stage->probability == 0.00) {\n $data['lost'][] = $stage->crm_provider_id;\n }\n if ($stage->probability == 100.00) {\n $data['won'][] = $stage->crm_provider_id;\n }\n }\n\n $this->cachedClosedDealStages = $data;\n\n return $data;\n }\n\n /**\n * Import deals into the database with pre-fetched associations.\n *\n * API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT\n * caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()\n * where Laravel retries the whole job with backoff. After all retries exhausted,\n * failed() requeues all IDs to Redis.\n *\n * The per-deal loop catches exceptions individually. A deal can end up in three states:\n * - success: imported/updated successfully\n * - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)\n * These are permanent issues — retrying won't fix them.\n * - skipped (null): missing dependencies (no account, unknown pipeline/stage).\n * This is acceptable — the deal cannot be imported until those exist.\n */\n private function importOpportunityBatch(array $deals): array\n {\n $syncedOpportunities = [\n 'success' => [],\n 'failed_ids' => [],\n ];\n $dealIds = array_column($deals, 'id');\n\n // Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the\n // queue job retries the whole batch and eventually requeues all deal IDs back to Redis.\n try {\n $companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');\n $contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');\n\n $associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);\n\n $existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(\n $this->config,\n array_map('strval', $dealIds)\n );\n $existingCrmIdSet = array_flip($existingCrmIds);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [\n 'teamId' => $this->team->getId(),\n 'dealCount' => count($dealIds),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n foreach ($deals as $deal) {\n try {\n $deal['associations'] = $this->prepareAssociationsForOpportunity(\n $deal['id'],\n $companyAssociations,\n $contactAssociations,\n $associationsData\n );\n\n $syncedOpportunity = $this->importOrUpdateOpportunity(\n $deal,\n isset($existingCrmIdSet[(string) $deal['id']])\n );\n if ($syncedOpportunity) {\n $syncedOpportunities['success'][] = $syncedOpportunity;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $deal['id'],\n 'error' => $e->getMessage(),\n ]);\n $syncedOpportunities['failed_ids'][] = $deal['id'];\n $syncedOpportunities['errors'][$deal['id']] = $e->getMessage();\n }\n }\n\n return $syncedOpportunities;\n }\n\n /**\n * Prepare associated entities for opportunities with optimized batch processing\n * Returns structured data with CRM ID to DB ID mappings for each opportunity\n */\n private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array\n {\n // Step 1: Collect all unique company and contact IDs from associations\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n\n // Step 2: Batch sync missing entities and get CRM ID to DB ID mappings\n $companyIdMappings = [];\n $contactIdMappings = [];\n\n if (! empty($allCompanyIds)) {\n $companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);\n }\n\n if (! empty($allContactIds)) {\n $contactIdMappings = $this->prepareAssociatedContacts($allContactIds);\n }\n\n return [\n 'company_id_mappings' => $companyIdMappings,\n 'contact_id_mappings' => $contactIdMappings,\n ];\n }\n\n /**\n * Flatten association data to get unique IDs\n */\n private function flattenAssociationIds(array $associations): array\n {\n $ids = [];\n foreach ($associations as $dealAssociations) {\n if (is_array($dealAssociations)) {\n foreach ($dealAssociations as $id) {\n $ids[$id] = true;\n }\n }\n }\n\n return array_keys($ids);\n }\n\n /**\n * Batch sync missing accounts\n */\n private function prepareAssociatedAccounts(array $companyIds): array\n {\n // Find which accounts already exist\n $existingAccounts = $this->crmEntityRepository\n ->findAccountsByExternalIds($this->config, $companyIds);\n\n $existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();\n\n $existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {\n return [$account->getCrmProviderId() => $account->getId()];\n })->toArray();\n\n $missingCompanyIds = array_diff($companyIds, $existingCompanyIds);\n\n if (empty($missingCompanyIds)) {\n return $existingAccountsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [\n 'teamId' => $this->team->getUuid(),\n 'total_companies' => count($companyIds),\n 'existing_companies' => count($existingCompanyIds),\n 'missing_companies' => count($missingCompanyIds),\n ]);\n\n // we already have limit on opportunity ids count\n // Initialize variable before try block\n $syncedAccountsData = [];\n\n try {\n $syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [\n 'size' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedAccountsData = [];\n }\n\n return $existingAccountsData + $syncedAccountsData;\n }\n\n /**\n * Prepare associated contacts - find existing and sync missing ones\n * Returns mapping of CRM ID to DB ID\n */\n private function prepareAssociatedContacts(array $contactIds): array\n {\n // Find which contacts already exist\n $existingContacts = $this->crmEntityRepository\n ->findContactsByExternalIds($this->config, $contactIds);\n\n $existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();\n\n // Create mapping for existing contacts\n $existingContactsData = $existingContacts->mapWithKeys(function ($contact) {\n return [$contact->getCrmProviderId() => $contact->getId()];\n })->toArray();\n\n $missingContactIds = array_diff($contactIds, $existingContactIds);\n\n if (empty($missingContactIds)) {\n return $existingContactsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [\n 'teamId' => $this->team->getUuid(),\n 'total_contacts' => count($contactIds),\n 'existing_contacts' => count($existingContactIds),\n 'missing_contacts' => count($missingContactIds),\n ]);\n\n // Sync missing contacts using batch API\n try {\n $syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [\n 'size' => count($missingContactIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedContactsData = [];\n }\n\n return $existingContactsData + $syncedContactsData;\n }\n\n private function batchSyncCrmObjects(string $objectType, array $crmIds): array\n {\n $syncObjects = [];\n $crmObjectIds = array_values($crmIds);\n\n foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {\n try {\n $objects = $objectType === 'companies' ?\n $this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :\n $this->client->getContactsByIds($chunk, $this->getContactFields());\n\n foreach ($objects as $objectId => $objectData) {\n $this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [\n 'requested_count' => count($chunk),\n 'synced_count' => count($objects),\n ]);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [\n 'ids' => $chunk,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncObjects;\n }\n\n private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void\n {\n try {\n $object = $objectType === 'companies' ?\n $this->importAccount($objectData) :\n $this->importContact($objectData);\n\n if ($object) {\n $syncObjects[$object->getCrmProviderId()] = $object->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [\n 'id' => $objectId,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n /**\n * Prepare associations for a single opportunity\n *\n * The return value is an array with the following structure:\n * [\n * 'companies' => [\n * $companyCrmId => $companyId,\n * ...\n * ],\n * 'contacts' => [\n * $contactCrmId => $contactId,\n * ...\n * ],\n * 'account_id' => $accountId,\n * ]\n */\n private function prepareAssociationsForOpportunity(\n string $oppCrmId,\n array $companyAssociations,\n array $contactAssociations,\n array $associationsData\n ): array {\n $associations = [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n\n $oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];\n foreach ($oppCompanyIds as $companyCrmId) {\n if (isset($associationsData['company_id_mappings'][$companyCrmId])) {\n $associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];\n\n // Set primary account (first company becomes primary account)\n if ($associations['account_id'] === null) {\n $associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];\n }\n }\n }\n\n $oppContactIds = $contactAssociations[$oppCrmId] ?? [];\n foreach ($oppContactIds as $contactCrmId) {\n if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {\n $associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];\n }\n }\n\n return $associations;\n }\n\n /**\n * Update only associations for an opportunity\n */\n private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void\n {\n // Update contact associations\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n // Update company (account) associations\n $this->updateOpportunityAccount($opportunity, $associations['account_id']);\n }\n\n /**\n * Remove all contact associations from an opportunity\n */\n private function removeAllOpportunityContacts(Opportunity $opportunity): void\n {\n $currentCount = (int) $opportunity->contacts()->count();\n\n if ($currentCount > 0) {\n $opportunity->contacts()->detach();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_count' => $currentCount,\n ]);\n }\n }\n\n private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void\n {\n if ($accountId === null) {\n // No account ID provided - keep current account\n return;\n }\n\n $currentAccountId = $opportunity->getAccountId();\n\n // Only update if account has changed\n if ($currentAccountId !== $accountId) {\n $opportunity->account_id = $accountId;\n $opportunity->save();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [\n 'opportunity_id' => $opportunity->getId(),\n 'old_account_id' => $currentAccountId,\n 'new_account_id' => $accountId,\n ]);\n }\n }\n\n /**\n * Find existing opportunities by external IDs (OPTIMIZED VERSION)\n * Uses batch query for better performance\n */\n private function findExistingOpportunities(array $crmIds): Collection\n {\n return $this->crmEntityRepository\n ->findOpportunitiesByExternalIds($this->config, $crmIds);\n }\n\n private function processOpportunityBatch(array $opportunities): int\n {\n $syncedOpportunities = $this->importOpportunityBatch($opportunities);\n\n return count($syncedOpportunities['success'] ?? []);\n }\n\n /**\n * Convert single deal associations from HubSpot format to internal format\n * Handles both HubSpot SDK objects and array formats\n *\n * @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed\n *\n * @return array Processed associations with DB IDs\n */\n private function convertDealAssociations(array $opportunityAssociations): array\n {\n $associations = $this->initializeAssociationsStructure();\n\n if (empty($opportunityAssociations)) {\n return $associations;\n }\n\n $associationIds = $this->extractAssociationIds($opportunityAssociations);\n\n $this->processCompanyAssociations($associationIds, $associations);\n $this->processContactAssociations($associationIds, $associations);\n\n return $associations;\n }\n\n private function initializeAssociationsStructure(): array\n {\n return [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n }\n\n private function extractAssociationIds(array $opportunityAssociations): array\n {\n $associationIds = [];\n\n foreach ($opportunityAssociations as $type => $associationData) {\n if (! empty($associationData)) {\n $associationIds[$type] = $this->convertSingleDealAssociations($associationData);\n }\n }\n\n return $associationIds;\n }\n\n private function processCompanyAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['companies'])) {\n return;\n }\n\n $companyId = $associationIds['companies'][0];\n $account = $this->findOrSyncAccount($companyId);\n\n if ($account instanceof Account) {\n $associations['companies'][$companyId] = $account->getId();\n $associations['account_id'] = $account->getId();\n }\n }\n\n private function processContactAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['contacts'])) {\n return;\n }\n\n foreach ($associationIds['contacts'] as $contactId) {\n $contact = $this->findOrSyncContact($contactId);\n\n if ($contact instanceof Contact) {\n $associations['contacts'][$contactId] = $contact->getId();\n }\n }\n }\n\n private function findOrSyncAccount(string $companyId): ?Account\n {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);\n\n if (! $account instanceof Account) {\n $account = $this->syncAccount($companyId);\n }\n\n return $account;\n }\n\n private function findOrSyncContact(string $contactId): ?Contact\n {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);\n\n if (! $contact instanceof Contact) {\n $contact = $this->syncContact($contactId);\n }\n\n return $contact;\n }\n\n private function convertSingleDealAssociations($opportunityAssociations = null): array\n {\n $associationData = [];\n\n if ($opportunityAssociations === null) {\n return $associationData;\n }\n\n // Handle array input (from extractAssociationIds)\n if (is_array($opportunityAssociations)) {\n return $opportunityAssociations;\n }\n\n // Handle CollectionResponseAssociatedId object\n if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {\n foreach ($opportunityAssociations->getResults() as $association) {\n $associationData[] = $association->getId();\n }\n }\n\n return $associationData;\n }\n\n private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity\n {\n if (empty($crmData['properties'])) {\n return null;\n }\n\n $crmId = (string) $crmData['id'];\n $properties = $crmData['properties'];\n $associations = $crmData['associations'] ?? [];\n\n $opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(\n $this->config,\n $crmId\n );\n\n if ($opportunityExists) {\n return $this->updateOpportunity($crmId, $properties, $associations);\n } else {\n return $this->createOpportunity($crmId, $properties, $associations);\n }\n }\n\n /**\n * Create new opportunity\n */\n private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n if (! $accountId) {\n return null;\n }\n\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n if (! $businessProcess) {\n return null;\n }\n\n $stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);\n if (! $stage) {\n return null;\n }\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n if ($opportunity->wasRecentlyCreated) {\n MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());\n }\n\n return $opportunity;\n }\n\n /**\n * Update existing opportunity\n */\n private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n $stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->updateOpportunityAssociations($opportunity, $associations);\n\n return $opportunity;\n }\n\n private function resolveAccountId(array $associations): ?int\n {\n if (! empty($associations['accountId'])) {\n return $associations['accountId'];\n }\n\n if (empty($associations)) {\n return null;\n }\n\n // we can't resolve multiple account ids (currently SDK returns one company)\n foreach ($associations['companies'] as $accountId) {\n return $accountId;\n }\n\n return null;\n }\n\n private function buildOpportunityData(\n array $properties,\n ?int $accountId,\n ?BusinessProcess $businessProcess,\n ?Stage $stage\n ): array {\n $ownerId = null;\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $name = 'Unknown';\n if (isset($properties['dealname'])) {\n $name = mb_strimwidth($properties['dealname'], 0, 128);\n }\n\n $amount = $this->resolveAmount($properties);\n $currency = $properties['deal_currency_code'] ?? null;\n\n $closeDate = null;\n if (! empty($properties['closedate'])) {\n $closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');\n }\n\n $remotelyCreatedAt = null;\n if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {\n $date = $this->parseCleanDatetime($properties['createdate']);\n $remotelyCreatedAt = $date?->format('Y-m-d H:i:s');\n }\n\n $closedStages = $this->getClosedDealStages();\n $isWon = in_array($properties['dealstage'], $closedStages['won']);\n $isLost = in_array($properties['dealstage'], $closedStages['lost']);\n\n $data = [\n 'team_id' => $this->team->getId(),\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'value' => ! empty($amount) ? $amount : null,\n 'currency_code' => CurrencyFormatter::formatCode($currency),\n 'close_date' => $closeDate,\n 'is_closed' => $isWon || $isLost,\n 'is_won' => $isWon,\n 'remotely_created_at' => $remotelyCreatedAt,\n 'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),\n 'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),\n ];\n\n if ($accountId) {\n $data['account_id'] = $accountId;\n }\n\n if ($stage) {\n $data['stage_id'] = $stage->id;\n }\n\n if ($businessProcess) {\n $recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);\n if ($recordType) {\n $data['record_type_id'] = $recordType->id;\n }\n }\n\n return $data;\n }\n\n private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess\n {\n if ($pipelineId === null) {\n return null;\n }\n\n if (isset($this->cachedBusinessProcesses[$pipelineId])) {\n return $this->cachedBusinessProcesses[$pipelineId];\n }\n\n $businessProcess = $this->getBusinessProcess($pipelineId);\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->importStages();\n $businessProcess = $this->getBusinessProcess($pipelineId);\n }\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->logger->info(\n '[HubSpot] Deal is not attached to a pipeline',\n [\n 'pipeline' => $pipelineId]\n );\n }\n\n $this->cachedBusinessProcesses[$pipelineId] = $businessProcess;\n\n return $businessProcess;\n }\n\n private function getBusinessProcess(string $pipelineId): ?BusinessProcess\n {\n return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);\n }\n\n private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage\n {\n if (empty($stageId)) {\n return null;\n }\n\n $cacheKey = $businessProcess->getId() . ':' . $stageId;\n if (isset($this->cachedStages[$cacheKey])) {\n return $this->cachedStages[$cacheKey];\n }\n\n $stage = $this->crmEntityRepository->getPipelineStageByConditions(\n $businessProcess,\n [\n 'crm_provider_id' => $stageId,\n 'type' => Stage::TYPE_OPPORTUNITY,\n ]\n );\n\n if ($stage === null) {\n $this->importStages(null, $stageId);\n }\n\n if ($stage === null) {\n $this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);\n }\n\n $this->cachedStages[$cacheKey] = $stage;\n\n return $stage;\n }\n\n private function resolveAmount(array $properties): ?string\n {\n $amount = null;\n if (! empty($properties['amount'])) {\n $amount = str_replace(',', '', $properties['amount']);\n }\n\n if ($this->config->hasDefaultCurrencyFieldSet()) {\n $valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();\n $amount = $properties[$valueFieldName] ?? $amount;\n }\n\n return $amount;\n }\n\n private function parseCleanDatetime(string $datetime): ?Carbon\n {\n // Treat pre-1980 values as invalid\n $minValidDate = Carbon::parse('1980-01-01 00:00:00');\n\n try {\n $date = Carbon::parse($datetime);\n\n if ($minValidDate->gt($date)) {\n return null;\n }\n\n return $date;\n } catch (Exception) {\n return null; // On parse error, treat as null\n }\n }\n\n private function resolveDealProbability(?string $stageProbability): int\n {\n if ($stageProbability === null) {\n return 0;\n }\n\n $probability = (float) $stageProbability;\n\n return $probability > 1 ? 0 : (int) ($probability * 100);\n }\n\n private function resolveForecastCategory(?string $forecastCategory): string\n {\n if (! $forecastCategory) {\n return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;\n }\n\n $forecastCategory = str_replace('_', ' ', $forecastCategory);\n\n return ucwords(strtolower($forecastCategory));\n }\n\n private function importExternalFieldData(array $properties, int $opportunityId): void\n {\n $crmFields = $this->getOpportunitySyncableFields();\n $this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);\n }\n\n private function importOpportunityContacts(Opportunity $opportunity, array $associations): void\n {\n // Handle empty or missing contact associations\n if (empty($associations)) {\n // Remove all existing contact associations if none provided\n $this->removeAllOpportunityContacts($opportunity);\n\n return;\n }\n\n // Use differential sync approach for better performance and accuracy\n $this->syncOpportunityContactsDifferential($opportunity, $associations);\n }\n\n /**\n * Sync opportunity contacts using differential approach\n * This compares current vs new associations and only makes necessary changes\n */\n private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void\n {\n $currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);\n $contactAssociationIds = array_keys($contactAssociations);\n\n $contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);\n $contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);\n\n if (empty($contactsToAdd) && empty($contactsToRemove)) {\n return;\n }\n\n $this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);\n\n $this->removeContactAssociations($opportunity, $contactsToRemove);\n $this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);\n }\n\n private function getCurrentContactCrmIds(Opportunity $opportunity): array\n {\n return $opportunity->contacts()\n ->pluck('contacts.crm_provider_id')\n ->toArray();\n }\n\n private function logContactAssociationChanges(\n Opportunity $opportunity,\n array $currentContactCrmIds,\n array $contactAssociations,\n array $contactsToAdd,\n array $contactsToRemove\n ): void {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [\n 'opportunity_id' => $opportunity->getId(),\n 'current_contacts' => $currentContactCrmIds,\n 'new_contacts' => $contactAssociations,\n 'contacts_to_add' => $contactsToAdd,\n 'contacts_to_remove' => $contactsToRemove,\n ]);\n }\n\n private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void\n {\n if (empty($contactsToRemove)) {\n return;\n }\n\n $contactsToDetach = $opportunity->contacts()\n ->whereIn('contacts.crm_provider_id', $contactsToRemove)\n ->pluck('contacts.id')\n ->toArray();\n\n if (! empty($contactsToDetach)) {\n $opportunity->contacts()->detach($contactsToDetach);\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_contact_crm_ids' => $contactsToRemove,\n 'removed_contact_count' => count($contactsToDetach),\n ]);\n }\n }\n\n private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void\n {\n if (empty($contactsToAdd)) {\n return;\n }\n\n $contactsAdded = [];\n foreach ($contactsToAdd as $crmId) {\n $id = $contactAssociations[$crmId];\n\n if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {\n $contactsAdded[] = $crmId;\n }\n }\n\n $this->logAddedContacts($opportunity, $contactsAdded);\n }\n\n private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);\n\n if (! $contact) {\n return false;\n }\n\n return $this->performContactAttachment($opportunity, $contact, $crmId);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [\n 'opportunity_id' => $opportunity->getId(),\n 'contact_crm_id' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool\n {\n try {\n $opportunity->contacts()->attach($contact->getId(), [\n 'crm_provider_id' => $crmId,\n ]);\n\n return true;\n } catch (\\Illuminate\\Database\\QueryException $e) {\n if (str_contains($e->getMessage(), 'Duplicate entry')) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [\n 'contact_id' => $contact->getId(),\n 'contact_crm_id' => $crmId,\n 'opportunity_id' => $opportunity->getId(),\n ]);\n\n return false;\n }\n\n throw $e;\n }\n }\n\n private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void\n {\n if (! empty($contactsAdded)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'contacts_to_add_count' => count($contactsAdded),\n 'added_contact_crm_ids' => $contactsAdded,\n 'added_contacts_count' => count($contactsAdded),\n ]);\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.049609374,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.00859375,"height":0.0},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.00859375,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.008203125,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<template>\n <WelcomeLayout\n title=\"Account disconnected\"\n textPosition=\"center\"\n :icon=\"faUnlink\"\n :class=\"$style.layout\"\n >\n <div :class=\"$style.container\" v-if=\"providersLoaded\">\n <p>\n <strong>\n It looks like your {{ localProvider.displayName }} account has become\n disconnected\n </strong>\n </p>\n <p :class=\"$style.small\">Please re-connect to continue</p>\n <p v-if=\"isInIframe\">\n We'll open the {{ localProvider.displayName }} authentication in a new\n tab. Please return here and refresh the page once complete\n </p>\n\n <GoogleLikeButton\n v-if=\"localProvider.viaIntegrationApp && crmTokenLoaded\"\n as=\"a\"\n :key=\"localProvider.name\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n @click=\"integrationAppOnClick\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n <GoogleLikeButton\n v-if=\"!localProvider.viaIntegrationApp\"\n as=\"a\"\n :key=\"localProvider.name\"\n :href=\"`/auth/redirect/${localProvider.name}`\"\n :target=\"target\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n </div>\n <BuildInfo />\n\n <KioskBanner />\n </WelcomeLayout>\n</template>\n\n<script>\nimport window from \"window\";\nimport axios from \"axios\";\nimport { faUnlink } from \"@fortawesome/pro-regular-svg-icons\";\nimport isInIframe from \"@/utils/isInIframe\";\nimport BuildInfo from \"@/components/layout/BuildInfo/BuildInfo.vue\";\nimport KioskBanner from \"@/components/shared/KioskBanner/KioskBanner.vue\";\nimport WelcomeLayout from \"@/components/layout/WelcomeLayout/WelcomeLayout.vue\";\nimport GoogleLikeButton from \"@/components/shared/Buttons/GoogleLikeButton.vue\";\nimport { showSnackbarError, normalizeError } from \"@/utils/index\";\nimport { IntegrationAppClient } from \"@integration-app/sdk\";\n\nexport default {\n name: \"ConnectPage\",\n components: {\n BuildInfo,\n KioskBanner,\n WelcomeLayout,\n GoogleLikeButton,\n },\n data() {\n return {\n ...window.connectData,\n crmToken: null,\n faUnlink,\n isInIframe,\n providers: [],\n providersLoaded: false,\n crmTokenLoaded: false,\n };\n },\n computed: {\n localProvider() {\n return this.providers.find((e) => e.name === this.provider);\n },\n target() {\n return this.isInIframe ? \"_blank\" : null;\n },\n },\n created() {\n this.getProviders();\n },\n mounted() {\n this.showErrors();\n },\n watch: {\n providersLoaded() {\n if (this.providersLoaded) {\n this.prepareIntegrationAppConnection();\n }\n },\n },\n methods: {\n showErrors() {\n if (!this.error) return;\n\n showSnackbarError(this.error, undefined, undefined, false);\n },\n unwrapEntityResponse({ data }) {\n return data.map(({ icon, name, displayName, viaIntegrationApp }) => {\n return { icon, name, displayName, viaIntegrationApp };\n });\n },\n async getProviders() {\n try {\n const response = await axios.get(\"/api/v1/connect-providers\");\n this.providers = this.unwrapEntityResponse(response);\n this.providersLoaded = true;\n } catch {\n showSnackbarError(\n \"An error occurred, while loading form data (connect providers).\",\n );\n }\n },\n async prepareIntegrationAppConnection() {\n if (this.localProvider.viaIntegrationApp) {\n try {\n const response = await axios.get(\"/api/v1/integration-app-token\");\n this.crmToken = response.data.token;\n this.crmTokenLoaded = true;\n } catch (error) {\n console.log(error);\n showSnackbarError(\n `An error occurred while preparing the page.\n Try refreshing, if the error persists get in touch with the Jiminny team.`,\n );\n }\n }\n },\n async integrationAppOnClick() {\n const integrationApp = new IntegrationAppClient({\n token: this.crmToken,\n });\n\n const connection = await integrationApp\n .integration(this.localProvider.name)\n .openNewConnection({\n showPoweredBy: false,\n allowMultipleConnections: false,\n });\n\n console.log('[IntegrationApp] openNewConnection resolved:', JSON.stringify(connection));\n\n // [IntegrationApp] openNewConnection resolved: {\n // \"id\":\"69e0b41a67d0068c2ca0b48e\",\n // \"name\":\"Zoho CRM\",\n // \"userId\":\"1ece66c8-feb1-4df1-b321-21607daf4623\",\n // \"tenantId\":\"69e0b3faef3e7b6248189289\",\n // \"isTest\":false,\n // \"connected\":true,\n // \"state\":\"READY\",\n // \"errors\":[],\n // \"integrationId\":\"66fe6c913202f3a165e3c14d\",\n // \"externalAppId\":\"6671653e7e2d642e4e41b0fa\",\n // \"authOptionKey\":\"\",\n // \"createdAt\":\"2026-04-16T10:04:10.420Z\",\n // \"updatedAt\":\"2026-04-16T10:04:10.575Z\",\n // \"retryAttempts\":0,\n // \"isDeactivated\":false\n // }\n\n if (connection && connection.disconnected !== true && connection.connected !== false) {\n console.log('[IntegrationApp] connection condition matched');\n try {\n const saveRequest = await axios.post(\n \"/api/v1/integration-app-connect\",\n );\n if (saveRequest.data && saveRequest.data.success === true) {\n /** If all is good refresh the page here */\n window.location = \"/dashboard\";\n return;\n }\n\n throw new Error(saveRequest.data.message);\n } catch (error) {\n console.log(error);\n showSnackbarError(normalizeError(error));\n }\n }\n },\n },\n};\n</script>\n\n<style module lang=\"less\" src=\"./connect.less\"></style>","depth":4,"value":"<template>\n <WelcomeLayout\n title=\"Account disconnected\"\n textPosition=\"center\"\n :icon=\"faUnlink\"\n :class=\"$style.layout\"\n >\n <div :class=\"$style.container\" v-if=\"providersLoaded\">\n <p>\n <strong>\n It looks like your {{ localProvider.displayName }} account has become\n disconnected\n </strong>\n </p>\n <p :class=\"$style.small\">Please re-connect to continue</p>\n <p v-if=\"isInIframe\">\n We'll open the {{ localProvider.displayName }} authentication in a new\n tab. Please return here and refresh the page once complete\n </p>\n\n <GoogleLikeButton\n v-if=\"localProvider.viaIntegrationApp && crmTokenLoaded\"\n as=\"a\"\n :key=\"localProvider.name\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n @click=\"integrationAppOnClick\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n <GoogleLikeButton\n v-if=\"!localProvider.viaIntegrationApp\"\n as=\"a\"\n :key=\"localProvider.name\"\n :href=\"`/auth/redirect/${localProvider.name}`\"\n :target=\"target\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n </div>\n <BuildInfo />\n\n <KioskBanner />\n </WelcomeLayout>\n</template>\n\n<script>\nimport window from \"window\";\nimport axios from \"axios\";\nimport { faUnlink } from \"@fortawesome/pro-regular-svg-icons\";\nimport isInIframe from \"@/utils/isInIframe\";\nimport BuildInfo from \"@/components/layout/BuildInfo/BuildInfo.vue\";\nimport KioskBanner from \"@/components/shared/KioskBanner/KioskBanner.vue\";\nimport WelcomeLayout from \"@/components/layout/WelcomeLayout/WelcomeLayout.vue\";\nimport GoogleLikeButton from \"@/components/shared/Buttons/GoogleLikeButton.vue\";\nimport { showSnackbarError, normalizeError } from \"@/utils/index\";\nimport { IntegrationAppClient } from \"@integration-app/sdk\";\n\nexport default {\n name: \"ConnectPage\",\n components: {\n BuildInfo,\n KioskBanner,\n WelcomeLayout,\n GoogleLikeButton,\n },\n data() {\n return {\n ...window.connectData,\n crmToken: null,\n faUnlink,\n isInIframe,\n providers: [],\n providersLoaded: false,\n crmTokenLoaded: false,\n };\n },\n computed: {\n localProvider() {\n return this.providers.find((e) => e.name === this.provider);\n },\n target() {\n return this.isInIframe ? \"_blank\" : null;\n },\n },\n created() {\n this.getProviders();\n },\n mounted() {\n this.showErrors();\n },\n watch: {\n providersLoaded() {\n if (this.providersLoaded) {\n this.prepareIntegrationAppConnection();\n }\n },\n },\n methods: {\n showErrors() {\n if (!this.error) return;\n\n showSnackbarError(this.error, undefined, undefined, false);\n },\n unwrapEntityResponse({ data }) {\n return data.map(({ icon, name, displayName, viaIntegrationApp }) => {\n return { icon, name, displayName, viaIntegrationApp };\n });\n },\n async getProviders() {\n try {\n const response = await axios.get(\"/api/v1/connect-providers\");\n this.providers = this.unwrapEntityResponse(response);\n this.providersLoaded = true;\n } catch {\n showSnackbarError(\n \"An error occurred, while loading form data (connect providers).\",\n );\n }\n },\n async prepareIntegrationAppConnection() {\n if (this.localProvider.viaIntegrationApp) {\n try {\n const response = await axios.get(\"/api/v1/integration-app-token\");\n this.crmToken = response.data.token;\n this.crmTokenLoaded = true;\n } catch (error) {\n console.log(error);\n showSnackbarError(\n `An error occurred while preparing the page.\n Try refreshing, if the error persists get in touch with the Jiminny team.`,\n );\n }\n }\n },\n async integrationAppOnClick() {\n const integrationApp = new IntegrationAppClient({\n token: this.crmToken,\n });\n\n const connection = await integrationApp\n .integration(this.localProvider.name)\n .openNewConnection({\n showPoweredBy: false,\n allowMultipleConnections: false,\n });\n\n console.log('[IntegrationApp] openNewConnection resolved:', JSON.stringify(connection));\n\n // [IntegrationApp] openNewConnection resolved: {\n // \"id\":\"69e0b41a67d0068c2ca0b48e\",\n // \"name\":\"Zoho CRM\",\n // \"userId\":\"1ece66c8-feb1-4df1-b321-21607daf4623\",\n // \"tenantId\":\"69e0b3faef3e7b6248189289\",\n // \"isTest\":false,\n // \"connected\":true,\n // \"state\":\"READY\",\n // \"errors\":[],\n // \"integrationId\":\"66fe6c913202f3a165e3c14d\",\n // \"externalAppId\":\"6671653e7e2d642e4e41b0fa\",\n // \"authOptionKey\":\"\",\n // \"createdAt\":\"2026-04-16T10:04:10.420Z\",\n // \"updatedAt\":\"2026-04-16T10:04:10.575Z\",\n // \"retryAttempts\":0,\n // \"isDeactivated\":false\n // }\n\n if (connection && connection.disconnected !== true && connection.connected !== false) {\n console.log('[IntegrationApp] connection condition matched');\n try {\n const saveRequest = await axios.post(\n \"/api/v1/integration-app-connect\",\n );\n if (saveRequest.data && saveRequest.data.success === true) {\n /** If all is good refresh the page here */\n window.location = \"/dashboard\";\n return;\n }\n\n throw new Error(saveRequest.data.message);\n } catch (error) {\n console.log(error);\n showSnackbarError(normalizeError(error));\n }\n }\n },\n },\n};\n</script>\n\n<style module lang=\"less\" src=\"./connect.less\"></style>","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.0140625,"top":0.041666668,"width":0.028515626,"height":0.021527778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
6089929419086115132
|
-8178086448081891036
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20692-fix-integration- Project: faVsco.js, menu
JY-20692-fix-integration-app-[API_KEY], menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
cachedStages
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
2/4
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Code changed:
Hide
Sync Changes
Hide This Notification
33
2
19
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use HubSpot\Client\Crm\Deals\Model\CollectionResponseAssociatedId;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Models\Account;
use Exception;
use Jiminny\Component\DealInsights\Forecast\Forecast;
use Jiminny\Jobs\Crm\MatchActivitiesToNewOpportunity;
use Jiminny\Models\Contact;
use Jiminny\Models\Crm\BusinessProcess;
use Jiminny\Exceptions\CrmException;
use Jiminny\Models\Opportunity;
use Illuminate\Support\Collection;
use Jiminny\Models\Stage;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Crm\Hubspot\DealFieldsService;
use Jiminny\Services\Crm\Hubspot\OpportunitySyncStrategy\HubspotSingleSyncStrategy;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Services\Crm\OpportunitySyncStrategyResolver;
use Jiminny\Utils\CurrencyFormatter;
/**
* Optimized sync methods for better performance
* These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains
*/
trait OpportunitySyncTrait
{
private const int BATCH_SIZE = 100;
private const int BATCH_PROCESS_SIZE = 800;
protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;
protected CrmEntityRepository $crmEntityRepository;
protected DealFieldsService $dealFieldsService;
private ?array $cachedClosedDealStages = null;
private array $cachedBusinessProcesses = [];
private array $cachedStages = [];
public function syncOpportunities(array $parameters, ?string $strategy = null): int
{
$strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);
$parameters['config'] = $this->config;
$syncCount = 0;
$reportedTotal = 0;
$lastSyncedId = [];
try {
foreach ($strategies as $strategyName => $syncStrategy) {
$this->logger->info(
'[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .
$strategyName
);
$total = 0;
$lastId = null;
$buffer = [];
// HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies
foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {
$buffer[] = $hsOpportunity;
// process every 800 rows (fits < 1 000 association limit)
if (\count($buffer) >= self::BATCH_PROCESS_SIZE) {
$syncCount += $this->processOpportunityBatch($buffer);
$buffer = [];
}
}
// leftovers
if ($buffer) {
$syncCount += $this->processOpportunityBatch($buffer);
}
$reportedTotal += $total;
$lastSyncedId = $lastId;
}
} catch (\HubSpot\Client\Crm\Deals\ApiException | CrmException $e) {
$this->handleSyncException($e, $parameters);
}
$this->logger->info(
'[HubSpot] Synced opportunities',
[
'team' => $this->team->getId(),
'sync_count' => $syncCount,
'total' => $reportedTotal,
'last_synced_id' => $lastSyncedId,
]
);
return $reportedTotal;
}
private function handleSyncException(\Throwable $e, array $parameters): void
{
if (($parameters['since'] ?? null) instanceof Carbon) {
$parameters['since'] = $parameters['since']->toDateTimeString();
}
$parameters['config'] = $this->config->getId();
$this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [
'teamId' => $this->team->getUuid(),
'parameters' => $parameters,
'reason' => $e->getMessage(),
]);
}
/**
* @inheritdoc
*/
public function syncOpportunity(string $crmId): ?Opportunity
{
$strategy = $this->opportunitySyncStrategyResolver->resolve(
$this->config,
OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,
);
$parameters = [
'config' => $this->config,
'crm_id' => $crmId,
];
try {
if (! $strategy instanceof HubspotSingleSyncStrategy) {
throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');
}
$hsOpportunity = $strategy->fetchOpportunity($parameters);
} catch (\HubSpot\Client\Crm\Deals\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
$hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);
return $this->importOrUpdateOpportunity($hsOpportunity);
}
/**
* Process webhook-collected opportunity batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportOpportunityBatch jobs for batch processing.
*
* @return int Number of opportunity IDs dispatched to jobs
*/
public function batchSyncOpportunities(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,
$configId
);
}
/**
* Import a batch of opportunities by their CRM IDs.
* Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().
*
* @param array<string> $crmIds HubSpot deal CRM IDs
*
* @return array{success: array, failed_ids: array, errors?: array<string, string>}
*/
public function importOpportunityBatchByIds(array $crmIds): array
{
$fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);
$allDeals = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
$deals = $this->client->getOpportunitiesByIds($chunk, $fields);
foreach ($deals as $deal) {
$allDeals[] = $deal;
}
}
// IDs not returned by HubSpot are likely deleted or inaccessible deals.
// These are not failures — retrying won't bring them back.
$fetchedIds = array_map('strval', array_column($allDeals, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allDeals),
]);
}
if (empty($allDeals)) {
return ['success' => [], 'failed_ids' => []];
}
return $this->importOpportunityBatch($allDeals);
}
private function getClosedDealStages(): array
{
if ($this->cachedClosedDealStages !== null) {
return $this->cachedClosedDealStages;
}
$stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);
$data = [
'lost' => [],
'won' => [],
];
foreach ($stages as $stage) {
if ($stage->probability == 0.00) {
$data['lost'][] = $stage->crm_provider_id;
}
if ($stage->probability == 100.00) {
$data['won'][] = $stage->crm_provider_id;
}
}
$this->cachedClosedDealStages = $data;
return $data;
}
/**
* Import deals into the database with pre-fetched associations.
*
* API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT
* caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()
* where Laravel retries the whole job with backoff. After all retries exhausted,
* failed() requeues all IDs to Redis.
*
* The per-deal loop catches exceptions individually. A deal can end up in three states:
* - success: imported/updated successfully
* - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)
* These are permanent issues — retrying won't fix them.
* - skipped (null): missing dependencies (no account, unknown pipeline/stage).
* This is acceptable — the deal cannot be imported until those exist.
*/
private function importOpportunityBatch(array $deals): array
{
$syncedOpportunities = [
'success' => [],
'failed_ids' => [],
];
$dealIds = array_column($deals, 'id');
// Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the
// queue job retries the whole batch and eventually requeues all deal IDs back to Redis.
try {
$companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');
$contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');
$associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);
$existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(
$this->config,
array_map('strval', $dealIds)
);
$existingCrmIdSet = array_flip($existingCrmIds);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [
'teamId' => $this->team->getId(),
'dealCount' => count($dealIds),
'error' => $e->getMessage(),
]);
throw $e;
}
foreach ($deals as $deal) {
try {
$deal['associations'] = $this->prepareAssociationsForOpportunity(
$deal['id'],
$companyAssociations,
$contactAssociations,
$associationsData
);
$syncedOpportunity = $this->importOrUpdateOpportunity(
$deal,
isset($existingCrmIdSet[(string) $deal['id']])
);
if ($syncedOpportunity) {
$syncedOpportunities['success'][] = $syncedOpportunity;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [
'teamId' => $this->team->getId(),
'crmId' => $deal['id'],
'error' => $e->getMessage(),
]);
$syncedOpportunities['failed_ids'][] = $deal['id'];
$syncedOpportunities['errors'][$deal['id']] = $e->getMessage();
}
}
return $syncedOpportunities;
}
/**
* Prepare associated entities for opportunities with optimized batch processing
* Returns structured data with CRM ID to DB ID mappings for each opportunity
*/
private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array
{
// Step 1: Collect all unique company and contact IDs from associations
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
// Step 2: Batch sync missing entities and get CRM ID to DB ID mappings
$companyIdMappings = [];
$contactIdMappings = [];
if (! empty($allCompanyIds)) {
$companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);
}
if (! empty($allContactIds)) {
$contactIdMappings = $this->prepareAssociatedContacts($allContactIds);
}
return [
'company_id_mappings' => $companyIdMappings,
'contact_id_mappings' => $contactIdMappings,
];
}
/**
* Flatten association data to get unique IDs
*/
private function flattenAssociationIds(array $associations): array
{
$ids = [];
foreach ($associations as $dealAssociations) {
if (is_array($dealAssociations)) {
foreach ($dealAssociations as $id) {
$ids[$id] = true;
}
}
}
return array_keys($ids);
}
/**
* Batch sync missing accounts
*/
private function prepareAssociatedAccounts(array $companyIds): array
{
// Find which accounts already exist
$existingAccounts = $this->crmEntityRepository
->findAccountsByExternalIds($this->config, $companyIds);
$existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();
$existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {
return [$account->getCrmProviderId() => $account->getId()];
})->toArray();
$missingCompanyIds = array_diff($companyIds, $existingCompanyIds);
if (empty($missingCompanyIds)) {
return $existingAccountsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [
'teamId' => $this->team->getUuid(),
'total_companies' => count($companyIds),
'existing_companies' => count($existingCompanyIds),
'missing_companies' => count($missingCompanyIds),
]);
// we already have limit on opportunity ids count
// Initialize variable before try block
$syncedAccountsData = [];
try {
$syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [
'size' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
$syncedAccountsData = [];
}
return $existingAccountsData + $syncedAccountsData;
}
/**
* Prepare associated contacts - find existing and sync missing ones
* Returns mapping of CRM ID to DB ID
*/
private function prepareAssociatedContacts(array $contactIds): array
{
// Find which contacts already exist
$existingContacts = $this->crmEntityRepository
->findContactsByExternalIds($this->config, $contactIds);
$existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();
// Create mapping for existing contacts
$existingContactsData = $existingContacts->mapWithKeys(function ($contact) {
return [$contact->getCrmProviderId() => $contact->getId()];
})->toArray();
$missingContactIds = array_diff($contactIds, $existingContactIds);
if (empty($missingContactIds)) {
return $existingContactsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [
'teamId' => $this->team->getUuid(),
'total_contacts' => count($contactIds),
'existing_contacts' => count($existingContactIds),
'missing_contacts' => count($missingContactIds),
]);
// Sync missing contacts using batch API
try {
$syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [
'size' => count($missingContactIds),
'error' => $e->getMessage(),
]);
$syncedContactsData = [];
}
return $existingContactsData + $syncedContactsData;
}
private function batchSyncCrmObjects(string $objectType, array $crmIds): array
{
$syncObjects = [];
$crmObjectIds = array_values($crmIds);
foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {
try {
$objects = $objectType === 'companies' ?
$this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :
$this->client->getContactsByIds($chunk, $this->getContactFields());
foreach ($objects as $objectId => $objectData) {
$this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [
'requested_count' => count($chunk),
'synced_count' => count($objects),
]);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [
'ids' => $chunk,
'error' => $e->getMessage(),
]);
}
}
return $syncObjects;
}
private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void
{
try {
$object = $objectType === 'companies' ?
$this->importAccount($objectData) :
$this->importContact($objectData);
if ($object) {
$syncObjects[$object->getCrmProviderId()] = $object->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [
'id' => $objectId,
'error' => $e->getMessage(),
]);
}
}
/**
* Prepare associations for a single opportunity
*
* The return value is an array with the following structure:
* [
* 'companies' => [
* $companyCrmId => $companyId,
* ...
* ],
* 'contacts' => [
* $contactCrmId => $contactId,
* ...
* ],
* 'account_id' => $accountId,
* ]
*/
private function prepareAssociationsForOpportunity(
string $oppCrmId,
array $companyAssociations,
array $contactAssociations,
array $associationsData
): array {
$associations = [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
$oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];
foreach ($oppCompanyIds as $companyCrmId) {
if (isset($associationsData['company_id_mappings'][$companyCrmId])) {
$associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];
// Set primary account (first company becomes primary account)
if ($associations['account_id'] === null) {
$associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];
}
}
}
$oppContactIds = $contactAssociations[$oppCrmId] ?? [];
foreach ($oppContactIds as $contactCrmId) {
if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {
$associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];
}
}
return $associations;
}
/**
* Update only associations for an opportunity
*/
private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void
{
// Update contact associations
$this->importOpportunityContacts($opportunity, $associations['contacts']);
// Update company (account) associations
$this->updateOpportunityAccount($opportunity, $associations['account_id']);
}
/**
* Remove all contact associations from an opportunity
*/
private function removeAllOpportunityContacts(Opportunity $opportunity): void
{
$currentCount = (int) $opportunity->contacts()->count();
if ($currentCount > 0) {
$opportunity->contacts()->detach();
$this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_count' => $currentCount,
]);
}
}
private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void
{
if ($accountId === null) {
// No account ID provided - keep current account
return;
}
$currentAccountId = $opportunity->getAccountId();
// Only update if account has changed
if ($currentAccountId !== $accountId) {
$opportunity->account_id = $accountId;
$opportunity->save();
$this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [
'opportunity_id' => $opportunity->getId(),
'old_account_id' => $currentAccountId,
'new_account_id' => $accountId,
]);
}
}
/**
* Find existing opportunities by external IDs (OPTIMIZED VERSION)
* Uses batch query for better performance
*/
private function findExistingOpportunities(array $crmIds): Collection
{
return $this->crmEntityRepository
->findOpportunitiesByExternalIds($this->config, $crmIds);
}
private function processOpportunityBatch(array $opportunities): int
{
$syncedOpportunities = $this->importOpportunityBatch($opportunities);
return count($syncedOpportunities['success'] ?? []);
}
/**
* Convert single deal associations from HubSpot format to internal format
* Handles both HubSpot SDK objects and array formats
*
* @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed
*
* @return array Processed associations with DB IDs
*/
private function convertDealAssociations(array $opportunityAssociations): array
{
$associations = $this->initializeAssociationsStructure();
if (empty($opportunityAssociations)) {
return $associations;
}
$associationIds = $this->extractAssociationIds($opportunityAssociations);
$this->processCompanyAssociations($associationIds, $associations);
$this->processContactAssociations($associationIds, $associations);
return $associations;
}
private function initializeAssociationsStructure(): array
{
return [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
}
private function extractAssociationIds(array $opportunityAssociations): array
{
$associationIds = [];
foreach ($opportunityAssociations as $type => $associationData) {
if (! empty($associationData)) {
$associationIds[$type] = $this->convertSingleDealAssociations($associationData);
}
}
return $associationIds;
}
private function processCompanyAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['companies'])) {
return;
}
$companyId = $associationIds['companies'][0];
$account = $this->findOrSyncAccount($companyId);
if ($account instanceof Account) {
$associations['companies'][$companyId] = $account->getId();
$associations['account_id'] = $account->getId();
}
}
private function processContactAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['contacts'])) {
return;
}
foreach ($associationIds['contacts'] as $contactId) {
$contact = $this->findOrSyncContact($contactId);
if ($contact instanceof Contact) {
$associations['contacts'][$contactId] = $contact->getId();
}
}
}
private function findOrSyncAccount(string $companyId): ?Account
{
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);
if (! $account instanceof Account) {
$account = $this->syncAccount($companyId);
}
return $account;
}
private function findOrSyncContact(string $contactId): ?Contact
{
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);
if (! $contact instanceof Contact) {
$contact = $this->syncContact($contactId);
}
return $contact;
}
private function convertSingleDealAssociations($opportunityAssociations = null): array
{
$associationData = [];
if ($opportunityAssociations === null) {
return $associationData;
}
// Handle array input (from extractAssociationIds)
if (is_array($opportunityAssociations)) {
return $opportunityAssociations;
}
// Handle CollectionResponseAssociatedId object
if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {
foreach ($opportunityAssociations->getResults() as $association) {
$associationData[] = $association->getId();
}
}
return $associationData;
}
private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity
{
if (empty($crmData['properties'])) {
return null;
}
$crmId = (string) $crmData['id'];
$properties = $crmData['properties'];
$associations = $crmData['associations'] ?? [];
$opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(
$this->config,
$crmId
);
if ($opportunityExists) {
return $this->updateOpportunity($crmId, $properties, $associations);
} else {
return $this->createOpportunity($crmId, $properties, $associations);
}
}
/**
* Create new opportunity
*/
private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity
{
$accountId = $this->resolveAccountId($associations);
if (! $accountId) {
return null;
}
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
if (! $businessProcess) {
return null;
}
$stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);
if (! $stage) {
return null;
}
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->importOpportunityContacts($opportunity, $associations['contacts']);
if ($opportunity->wasRecentlyCreated) {
MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());
}
return $opportunity;
}
/**
* Update existing opportunity
*/
private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity
{
$accountId = $this->resolveAccountId($associations);
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
$stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->updateOpportunityAssociations($opportunity, $associations);
return $opportunity;
}
private function resolveAccountId(array $associations): ?int
{
if (! empty($associations['accountId'])) {
return $associations['accountId'];
}
if (empty($associations)) {
return null;
}
// we can't resolve multiple account ids (currently SDK returns one company)
foreach ($associations['companies'] as $accountId) {
return $accountId;
}
return null;
}
private function buildOpportunityData(
array $properties,
?int $accountId,
?BusinessProcess $businessProcess,
?Stage $stage
): array {
$ownerId = null;
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$name = 'Unknown';
if (isset($properties['dealname'])) {
$name = mb_strimwidth($properties['dealname'], 0, 128);
}
$amount = $this->resolveAmount($properties);
$currency = $properties['deal_currency_code'] ?? null;
$closeDate = null;
if (! empty($properties['closedate'])) {
$closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');
}
$remotelyCreatedAt = null;
if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {
$date = $this->parseCleanDatetime($properties['createdate']);
$remotelyCreatedAt = $date?->format('Y-m-d H:i:s');
}
$closedStages = $this->getClosedDealStages();
$isWon = in_array($properties['dealstage'], $closedStages['won']);
$isLost = in_array($properties['dealstage'], $closedStages['lost']);
$data = [
'team_id' => $this->team->getId(),
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => $name,
'value' => ! empty($amount) ? $amount : null,
'currency_code' => CurrencyFormatter::formatCode($currency),
'close_date' => $closeDate,
'is_closed' => $isWon || $isLost,
'is_won' => $isWon,
'remotely_created_at' => $remotelyCreatedAt,
'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),
'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),
];
if ($accountId) {
$data['account_id'] = $accountId;
}
if ($stage) {
$data['stage_id'] = $stage->id;
}
if ($businessProcess) {
$recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);
if ($recordType) {
$data['record_type_id'] = $recordType->id;
}
}
return $data;
}
private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess
{
if ($pipelineId === null) {
return null;
}
if (isset($this->cachedBusinessProcesses[$pipelineId])) {
return $this->cachedBusinessProcesses[$pipelineId];
}
$businessProcess = $this->getBusinessProcess($pipelineId);
if (! $businessProcess instanceof BusinessProcess) {
$this->importStages();
$businessProcess = $this->getBusinessProcess($pipelineId);
}
if (! $businessProcess instanceof BusinessProcess) {
$this->logger->info(
'[HubSpot] Deal is not attached to a pipeline',
[
'pipeline' => $pipelineId]
);
}
$this->cachedBusinessProcesses[$pipelineId] = $businessProcess;
return $businessProcess;
}
private function getBusinessProcess(string $pipelineId): ?BusinessProcess
{
return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);
}
private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage
{
if (empty($stageId)) {
return null;
}
$cacheKey = $businessProcess->getId() . ':' . $stageId;
if (isset($this->cachedStages[$cacheKey])) {
return $this->cachedStages[$cacheKey];
}
$stage = $this->crmEntityRepository->getPipelineStageByConditions(
$businessProcess,
[
'crm_provider_id' => $stageId,
'type' => Stage::TYPE_OPPORTUNITY,
]
);
if ($stage === null) {
$this->importStages(null, $stageId);
}
if ($stage === null) {
$this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);
}
$this->cachedStages[$cacheKey] = $stage;
return $stage;
}
private function resolveAmount(array $properties): ?string
{
$amount = null;
if (! empty($properties['amount'])) {
$amount = str_replace(',', '', $properties['amount']);
}
if ($this->config->hasDefaultCurrencyFieldSet()) {
$valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();
$amount = $properties[$valueFieldName] ?? $amount;
}
return $amount;
}
private function parseCleanDatetime(string $datetime): ?Carbon
{
// Treat pre-1980 values as invalid
$minValidDate = Carbon::parse('1980-01-01 00:00:00');
try {
$date = Carbon::parse($datetime);
if ($minValidDate->gt($date)) {
return null;
}
return $date;
} catch (Exception) {
return null; // On parse error, treat as null
}
}
private function resolveDealProbability(?string $stageProbability): int
{
if ($stageProbability === null) {
return 0;
}
$probability = (float) $stageProbability;
return $probability > 1 ? 0 : (int) ($probability * 100);
}
private function resolveForecastCategory(?string $forecastCategory): string
{
if (! $forecastCategory) {
return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;
}
$forecastCategory = str_replace('_', ' ', $forecastCategory);
return ucwords(strtolower($forecastCategory));
}
private function importExternalFieldData(array $properties, int $opportunityId): void
{
$crmFields = $this->getOpportunitySyncableFields();
$this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);
}
private function importOpportunityContacts(Opportunity $opportunity, array $associations): void
{
// Handle empty or missing contact associations
if (empty($associations)) {
// Remove all existing contact associations if none provided
$this->removeAllOpportunityContacts($opportunity);
return;
}
// Use differential sync approach for better performance and accuracy
$this->syncOpportunityContactsDifferential($opportunity, $associations);
}
/**
* Sync opportunity contacts using differential approach
* This compares current vs new associations and only makes necessary changes
*/
private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void
{
$currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);
$contactAssociationIds = array_keys($contactAssociations);
$contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);
$contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);
if (empty($contactsToAdd) && empty($contactsToRemove)) {
return;
}
$this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);
$this->removeContactAssociations($opportunity, $contactsToRemove);
$this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);
}
private function getCurrentContactCrmIds(Opportunity $opportunity): array
{
return $opportunity->contacts()
->pluck('contacts.crm_provider_id')
->toArray();
}
private function logContactAssociationChanges(
Opportunity $opportunity,
array $currentContactCrmIds,
array $contactAssociations,
array $contactsToAdd,
array $contactsToRemove
): void {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [
'opportunity_id' => $opportunity->getId(),
'current_contacts' => $currentContactCrmIds,
'new_contacts' => $contactAssociations,
'contacts_to_add' => $contactsToAdd,
'contacts_to_remove' => $contactsToRemove,
]);
}
private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void
{
if (empty($contactsToRemove)) {
return;
}
$contactsToDetach = $opportunity->contacts()
->whereIn('contacts.crm_provider_id', $contactsToRemove)
->pluck('contacts.id')
->toArray();
if (! empty($contactsToDetach)) {
$opportunity->contacts()->detach($contactsToDetach);
$this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_contact_crm_ids' => $contactsToRemove,
'removed_contact_count' => count($contactsToDetach),
]);
}
}
private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void
{
if (empty($contactsToAdd)) {
return;
}
$contactsAdded = [];
foreach ($contactsToAdd as $crmId) {
$id = $contactAssociations[$crmId];
if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {
$contactsAdded[] = $crmId;
}
}
$this->logAddedContacts($opportunity, $contactsAdded);
}
private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool
{
try {
$contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);
if (! $contact) {
return false;
}
return $this->performContactAttachment($opportunity, $contact, $crmId);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [
'opportunity_id' => $opportunity->getId(),
'contact_crm_id' => $crmId,
'error' => $e->getMessage(),
]);
return false;
}
}
private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool
{
try {
$opportunity->contacts()->attach($contact->getId(), [
'crm_provider_id' => $crmId,
]);
return true;
} catch (\Illuminate\Database\QueryException $e) {
if (str_contains($e->getMessage(), 'Duplicate entry')) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [
'contact_id' => $contact->getId(),
'contact_crm_id' => $crmId,
'opportunity_id' => $opportunity->getId(),
]);
return false;
}
throw $e;
}
}
private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void
{
if (! empty($contactsAdded)) {
$this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [
'opportunity_id' => $opportunity->getId(),
'contacts_to_add_count' => count($contactsAdded),
'added_contact_crm_ids' => $contactsAdded,
'added_contacts_count' => count($contactsAdded),
]);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
1
Previous Highlighted Error
Next Highlighted Error
<template>
<WelcomeLayout
title="Account disconnected"
textPosition="center"
:icon="faUnlink"
:class="$style.layout"
>
<div :class="$style.container" v-if="providersLoaded">
<p>
<strong>
It looks like your {{ localProvider.displayName }} account has become
disconnected
</strong>
</p>
<p :class="$style.small">Please re-connect to continue</p>
<p v-if="isInIframe">
We'll open the {{ localProvider.displayName }} authentication in a new
tab. Please return here and refresh the page once complete
</p>
<GoogleLikeButton
v-if="localProvider.viaIntegrationApp && crmTokenLoaded"
as="a"
:key="localProvider.name"
:brand-logo="localProvider.name"
:class="$style.connectButton"
@click="integrationAppOnClick"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
<GoogleLikeButton
v-if="!localProvider.viaIntegrationApp"
as="a"
:key="localProvider.name"
:href="`/auth/redirect/${localProvider.name}`"
:target="target"
:brand-logo="localProvider.name"
:class="$style.connectButton"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
</div>
<BuildInfo />
<KioskBanner />
</WelcomeLayout>
</template>
<script>
import window from "window";
import axios from "axios";
import { faUnlink } from "@fortawesome/pro-regular-svg-icons";
import isInIframe from "@/utils/isInIframe";
import BuildInfo from "@/components/layout/BuildInfo/BuildInfo.vue";
import KioskBanner from "@/components/shared/KioskBanner/KioskBanner.vue";
import WelcomeLayout from "@/components/layout/WelcomeLayout/WelcomeLayout.vue";
import GoogleLikeButton from "@/components/shared/Buttons/GoogleLikeButton.vue";
import { showSnackbarError, normalizeError } from "@/utils/index";
import { IntegrationAppClient } from "@integration-app/sdk";
export default {
name: "ConnectPage",
components: {
BuildInfo,
KioskBanner,
WelcomeLayout,
GoogleLikeButton,
},
data() {
return {
...window.connectData,
crmToken: null,
faUnlink,
isInIframe,
providers: [],
providersLoaded: false,
crmTokenLoaded: false,
};
},
computed: {
localProvider() {
return this.providers.find((e) => e.name === this.provider);
},
target() {
return this.isInIframe ? "_blank" : null;
},
},
created() {
this.getProviders();
},
mounted() {
this.showErrors();
},
watch: {
providersLoaded() {
if (this.providersLoaded) {
this.prepareIntegrationAppConnection();
}
},
},
methods: {
showErrors() {
if (!this.error) return;
showSnackbarError(this.error, undefined, undefined, false);
},
unwrapEntityResponse({ data }) {
return data.map(({ icon, name, displayName, viaIntegrationApp }) => {
return { icon, name, displayName, viaIntegrationApp };
});
},
async getProviders() {
try {
const response = await axios.get("/api/v1/connect-providers");
this.providers = this.unwrapEntityResponse(response);
this.providersLoaded = true;
} catch {
showSnackbarError(
"An error occurred, while loading form data (connect providers).",
);
}
},
async prepareIntegrationAppConnection() {
if (this.localProvider.viaIntegrationApp) {
try {
const response = await axios.get("/api/v1/integration-app-token");
this.crmToken = response.data.token;
this.crmTokenLoaded = true;
} catch (error) {
console.log(error);
showSnackbarError(
`An error occurred while preparing the page.
Try refreshing, if the error persists get in touch with the Jiminny team.`,
);
}
}
},
async integrationAppOnClick() {
const integrationApp = new IntegrationAppClient({
token: this.crmToken,
});
const connection = await integrationApp
.integration(this.localProvider.name)
.openNewConnection({
showPoweredBy: false,
allowMultipleConnections: false,
});
console.log('[IntegrationApp] openNewConnection resolved:', JSON.stringify(connection));
// [IntegrationApp] openNewConnection resolved: {
// "id":"69e0b41a67d0068c2ca0b48e",
// "name":"Zoho CRM",
// "userId":"1ece66c8-feb1-4df1-b321-21607daf4623",
// "tenantId":"69e0b3faef3e7b6248189289",
// "isTest":false,
// "connected":true,
// "state":"READY",
// "errors":[],
// "integrationId":"66fe6c913202f3a165e3c14d",
// "externalAppId":"6671653e7e2d642e4e41b0fa",
// "authOptionKey":"",
// "createdAt":"2026-04-16T10:04:10.420Z",
// "updatedAt":"2026-04-16T10:04:10.575Z",
// "retryAttempts":0,
// "isDeactivated":false
// }
if (connection && connection.disconnected !== true && connection.connected !== false) {
console.log('[IntegrationApp] connection condition matched');
try {
const saveRequest = await axios.post(
"/api/v1/integration-app-connect",
);
if (saveRequest.data && saveRequest.data.success === true) {
/** If all is good refresh the page here */
window.location = "/dashboard";
return;
}
throw new Error(saveRequest.data.message);
} catch (error) {
console.log(error);
showSnackbarError(normalizeError(error));
}
}
},
},
};
</script>
<style module lang="less" src="./connect.less"></style>
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
|
46364
|
NULL
|
0
|
2026-04-17T10:31:19.600215+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776421879600_m1.jpg...
|
PhpStorm
|
faVsco.js – ~/jiminny/app/front-end/src/components faVsco.js – ~/jiminny/app/front-end/src/components/connect/connect.vue...
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20692-fix-integration- Project: faVsco.js, menu
JY-20692-fix-integration-app-[API_KEY], menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
cachedStages
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
2/4
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Code changed:
Hide
Sync Changes
Hide This Notification
33
2
19
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use HubSpot\Client\Crm\Deals\Model\CollectionResponseAssociatedId;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Models\Account;
use Exception;
use Jiminny\Component\DealInsights\Forecast\Forecast;
use Jiminny\Jobs\Crm\MatchActivitiesToNewOpportunity;
use Jiminny\Models\Contact;
use Jiminny\Models\Crm\BusinessProcess;
use Jiminny\Exceptions\CrmException;
use Jiminny\Models\Opportunity;
use Illuminate\Support\Collection;
use Jiminny\Models\Stage;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Crm\Hubspot\DealFieldsService;
use Jiminny\Services\Crm\Hubspot\OpportunitySyncStrategy\HubspotSingleSyncStrategy;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Services\Crm\OpportunitySyncStrategyResolver;
use Jiminny\Utils\CurrencyFormatter;
/**
* Optimized sync methods for better performance
* These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains
*/
trait OpportunitySyncTrait
{
private const int BATCH_SIZE = 100;
private const int BATCH_PROCESS_SIZE = 800;
protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;
protected CrmEntityRepository $crmEntityRepository;
protected DealFieldsService $dealFieldsService;
private ?array $cachedClosedDealStages = null;
private array $cachedBusinessProcesses = [];
private array $cachedStages = [];
public function syncOpportunities(array $parameters, ?string $strategy = null): int
{
$strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);
$parameters['config'] = $this->config;
$syncCount = 0;
$reportedTotal = 0;
$lastSyncedId = [];
try {
foreach ($strategies as $strategyName => $syncStrategy) {
$this->logger->info(
'[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .
$strategyName
);
$total = 0;
$lastId = null;
$buffer = [];
// HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies
foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {
$buffer[] = $hsOpportunity;
// process every 800 rows (fits < 1 000 association limit)
if (\count($buffer) >= self::BATCH_PROCESS_SIZE) {
$syncCount += $this->processOpportunityBatch($buffer);
$buffer = [];
}
}
// leftovers
if ($buffer) {
$syncCount += $this->processOpportunityBatch($buffer);
}
$reportedTotal += $total;
$lastSyncedId = $lastId;
}
} catch (\HubSpot\Client\Crm\Deals\ApiException | CrmException $e) {
$this->handleSyncException($e, $parameters);
}
$this->logger->info(
'[HubSpot] Synced opportunities',
[
'team' => $this->team->getId(),
'sync_count' => $syncCount,
'total' => $reportedTotal,
'last_synced_id' => $lastSyncedId,
]
);
return $reportedTotal;
}
private function handleSyncException(\Throwable $e, array $parameters): void
{
if (($parameters['since'] ?? null) instanceof Carbon) {
$parameters['since'] = $parameters['since']->toDateTimeString();
}
$parameters['config'] = $this->config->getId();
$this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [
'teamId' => $this->team->getUuid(),
'parameters' => $parameters,
'reason' => $e->getMessage(),
]);
}
/**
* @inheritdoc
*/
public function syncOpportunity(string $crmId): ?Opportunity
{
$strategy = $this->opportunitySyncStrategyResolver->resolve(
$this->config,
OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,
);
$parameters = [
'config' => $this->config,
'crm_id' => $crmId,
];
try {
if (! $strategy instanceof HubspotSingleSyncStrategy) {
throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');
}
$hsOpportunity = $strategy->fetchOpportunity($parameters);
} catch (\HubSpot\Client\Crm\Deals\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
$hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);
return $this->importOrUpdateOpportunity($hsOpportunity);
}
/**
* Process webhook-collected opportunity batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportOpportunityBatch jobs for batch processing.
*
* @return int Number of opportunity IDs dispatched to jobs
*/
public function batchSyncOpportunities(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,
$configId
);
}
/**
* Import a batch of opportunities by their CRM IDs.
* Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().
*
* @param array<string> $crmIds HubSpot deal CRM IDs
*
* @return array{success: array, failed_ids: array, errors?: array<string, string>}
*/
public function importOpportunityBatchByIds(array $crmIds): array
{
$fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);
$allDeals = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
$deals = $this->client->getOpportunitiesByIds($chunk, $fields);
foreach ($deals as $deal) {
$allDeals[] = $deal;
}
}
// IDs not returned by HubSpot are likely deleted or inaccessible deals.
// These are not failures — retrying won't bring them back.
$fetchedIds = array_map('strval', array_column($allDeals, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allDeals),
]);
}
if (empty($allDeals)) {
return ['success' => [], 'failed_ids' => []];
}
return $this->importOpportunityBatch($allDeals);
}
private function getClosedDealStages(): array
{
if ($this->cachedClosedDealStages !== null) {
return $this->cachedClosedDealStages;
}
$stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);
$data = [
'lost' => [],
'won' => [],
];
foreach ($stages as $stage) {
if ($stage->probability == 0.00) {
$data['lost'][] = $stage->crm_provider_id;
}
if ($stage->probability == 100.00) {
$data['won'][] = $stage->crm_provider_id;
}
}
$this->cachedClosedDealStages = $data;
return $data;
}
/**
* Import deals into the database with pre-fetched associations.
*
* API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT
* caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()
* where Laravel retries the whole job with backoff. After all retries exhausted,
* failed() requeues all IDs to Redis.
*
* The per-deal loop catches exceptions individually. A deal can end up in three states:
* - success: imported/updated successfully
* - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)
* These are permanent issues — retrying won't fix them.
* - skipped (null): missing dependencies (no account, unknown pipeline/stage).
* This is acceptable — the deal cannot be imported until those exist.
*/
private function importOpportunityBatch(array $deals): array
{
$syncedOpportunities = [
'success' => [],
'failed_ids' => [],
];
$dealIds = array_column($deals, 'id');
// Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the
// queue job retries the whole batch and eventually requeues all deal IDs back to Redis.
try {
$companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');
$contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');
$associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);
$existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(
$this->config,
array_map('strval', $dealIds)
);
$existingCrmIdSet = array_flip($existingCrmIds);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [
'teamId' => $this->team->getId(),
'dealCount' => count($dealIds),
'error' => $e->getMessage(),
]);
throw $e;
}
foreach ($deals as $deal) {
try {
$deal['associations'] = $this->prepareAssociationsForOpportunity(
$deal['id'],
$companyAssociations,
$contactAssociations,
$associationsData
);
$syncedOpportunity = $this->importOrUpdateOpportunity(
$deal,
isset($existingCrmIdSet[(string) $deal['id']])
);
if ($syncedOpportunity) {
$syncedOpportunities['success'][] = $syncedOpportunity;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [
'teamId' => $this->team->getId(),
'crmId' => $deal['id'],
'error' => $e->getMessage(),
]);
$syncedOpportunities['failed_ids'][] = $deal['id'];
$syncedOpportunities['errors'][$deal['id']] = $e->getMessage();
}
}
return $syncedOpportunities;
}
/**
* Prepare associated entities for opportunities with optimized batch processing
* Returns structured data with CRM ID to DB ID mappings for each opportunity
*/
private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array
{
// Step 1: Collect all unique company and contact IDs from associations
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
// Step 2: Batch sync missing entities and get CRM ID to DB ID mappings
$companyIdMappings = [];
$contactIdMappings = [];
if (! empty($allCompanyIds)) {
$companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);
}
if (! empty($allContactIds)) {
$contactIdMappings = $this->prepareAssociatedContacts($allContactIds);
}
return [
'company_id_mappings' => $companyIdMappings,
'contact_id_mappings' => $contactIdMappings,
];
}
/**
* Flatten association data to get unique IDs
*/
private function flattenAssociationIds(array $associations): array
{
$ids = [];
foreach ($associations as $dealAssociations) {
if (is_array($dealAssociations)) {
foreach ($dealAssociations as $id) {
$ids[$id] = true;
}
}
}
return array_keys($ids);
}
/**
* Batch sync missing accounts
*/
private function prepareAssociatedAccounts(array $companyIds): array
{
// Find which accounts already exist
$existingAccounts = $this->crmEntityRepository
->findAccountsByExternalIds($this->config, $companyIds);
$existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();
$existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {
return [$account->getCrmProviderId() => $account->getId()];
})->toArray();
$missingCompanyIds = array_diff($companyIds, $existingCompanyIds);
if (empty($missingCompanyIds)) {
return $existingAccountsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [
'teamId' => $this->team->getUuid(),
'total_companies' => count($companyIds),
'existing_companies' => count($existingCompanyIds),
'missing_companies' => count($missingCompanyIds),
]);
// we already have limit on opportunity ids count
// Initialize variable before try block
$syncedAccountsData = [];
try {
$syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [
'size' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
$syncedAccountsData = [];
}
return $existingAccountsData + $syncedAccountsData;
}
/**
* Prepare associated contacts - find existing and sync missing ones
* Returns mapping of CRM ID to DB ID
*/
private function prepareAssociatedContacts(array $contactIds): array
{
// Find which contacts already exist
$existingContacts = $this->crmEntityRepository
->findContactsByExternalIds($this->config, $contactIds);
$existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();
// Create mapping for existing contacts
$existingContactsData = $existingContacts->mapWithKeys(function ($contact) {
return [$contact->getCrmProviderId() => $contact->getId()];
})->toArray();
$missingContactIds = array_diff($contactIds, $existingContactIds);
if (empty($missingContactIds)) {
return $existingContactsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [
'teamId' => $this->team->getUuid(),
'total_contacts' => count($contactIds),
'existing_contacts' => count($existingContactIds),
'missing_contacts' => count($missingContactIds),
]);
// Sync missing contacts using batch API
try {
$syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [
'size' => count($missingContactIds),
'error' => $e->getMessage(),
]);
$syncedContactsData = [];
}
return $existingContactsData + $syncedContactsData;
}
private function batchSyncCrmObjects(string $objectType, array $crmIds): array
{
$syncObjects = [];
$crmObjectIds = array_values($crmIds);
foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {
try {
$objects = $objectType === 'companies' ?
$this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :
$this->client->getContactsByIds($chunk, $this->getContactFields());
foreach ($objects as $objectId => $objectData) {
$this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [
'requested_count' => count($chunk),
'synced_count' => count($objects),
]);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [
'ids' => $chunk,
'error' => $e->getMessage(),
]);
}
}
return $syncObjects;
}
private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void
{
try {
$object = $objectType === 'companies' ?
$this->importAccount($objectData) :
$this->importContact($objectData);
if ($object) {
$syncObjects[$object->getCrmProviderId()] = $object->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [
'id' => $objectId,
'error' => $e->getMessage(),
]);
}
}
/**
* Prepare associations for a single opportunity
*
* The return value is an array with the following structure:
* [
* 'companies' => [
* $companyCrmId => $companyId,
* ...
* ],
* 'contacts' => [
* $contactCrmId => $contactId,
* ...
* ],
* 'account_id' => $accountId,
* ]
*/
private function prepareAssociationsForOpportunity(
string $oppCrmId,
array $companyAssociations,
array $contactAssociations,
array $associationsData
): array {
$associations = [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
$oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];
foreach ($oppCompanyIds as $companyCrmId) {
if (isset($associationsData['company_id_mappings'][$companyCrmId])) {
$associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];
// Set primary account (first company becomes primary account)
if ($associations['account_id'] === null) {
$associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];
}
}
}
$oppContactIds = $contactAssociations[$oppCrmId] ?? [];
foreach ($oppContactIds as $contactCrmId) {
if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {
$associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];
}
}
return $associations;
}
/**
* Update only associations for an opportunity
*/
private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void
{
// Update contact associations
$this->importOpportunityContacts($opportunity, $associations['contacts']);
// Update company (account) associations
$this->updateOpportunityAccount($opportunity, $associations['account_id']);
}
/**
* Remove all contact associations from an opportunity
*/
private function removeAllOpportunityContacts(Opportunity $opportunity): void
{
$currentCount = (int) $opportunity->contacts()->count();
if ($currentCount > 0) {
$opportunity->contacts()->detach();
$this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_count' => $currentCount,
]);
}
}
private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void
{
if ($accountId === null) {
// No account ID provided - keep current account
return;
}
$currentAccountId = $opportunity->getAccountId();
// Only update if account has changed
if ($currentAccountId !== $accountId) {
$opportunity->account_id = $accountId;
$opportunity->save();
$this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [
'opportunity_id' => $opportunity->getId(),
'old_account_id' => $currentAccountId,
'new_account_id' => $accountId,
]);
}
}
/**
* Find existing opportunities by external IDs (OPTIMIZED VERSION)
* Uses batch query for better performance
*/
private function findExistingOpportunities(array $crmIds): Collection
{
return $this->crmEntityRepository
->findOpportunitiesByExternalIds($this->config, $crmIds);
}
private function processOpportunityBatch(array $opportunities): int
{
$syncedOpportunities = $this->importOpportunityBatch($opportunities);
return count($syncedOpportunities['success'] ?? []);
}
/**
* Convert single deal associations from HubSpot format to internal format
* Handles both HubSpot SDK objects and array formats
*
* @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed
*
* @return array Processed associations with DB IDs
*/
private function convertDealAssociations(array $opportunityAssociations): array
{
$associations = $this->initializeAssociationsStructure();
if (empty($opportunityAssociations)) {
return $associations;
}
$associationIds = $this->extractAssociationIds($opportunityAssociations);
$this->processCompanyAssociations($associationIds, $associations);
$this->processContactAssociations($associationIds, $associations);
return $associations;
}
private function initializeAssociationsStructure(): array
{
return [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
}
private function extractAssociationIds(array $opportunityAssociations): array
{
$associationIds = [];
foreach ($opportunityAssociations as $type => $associationData) {
if (! empty($associationData)) {
$associationIds[$type] = $this->convertSingleDealAssociations($associationData);
}
}
return $associationIds;
}
private function processCompanyAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['companies'])) {
return;
}
$companyId = $associationIds['companies'][0];
$account = $this->findOrSyncAccount($companyId);
if ($account instanceof Account) {
$associations['companies'][$companyId] = $account->getId();
$associations['account_id'] = $account->getId();
}
}
private function processContactAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['contacts'])) {
return;
}
foreach ($associationIds['contacts'] as $contactId) {
$contact = $this->findOrSyncContact($contactId);
if ($contact instanceof Contact) {
$associations['contacts'][$contactId] = $contact->getId();
}
}
}
private function findOrSyncAccount(string $companyId): ?Account
{
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);
if (! $account instanceof Account) {
$account = $this->syncAccount($companyId);
}
return $account;
}
private function findOrSyncContact(string $contactId): ?Contact
{
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);
if (! $contact instanceof Contact) {
$contact = $this->syncContact($contactId);
}
return $contact;
}
private function convertSingleDealAssociations($opportunityAssociations = null): array
{
$associationData = [];
if ($opportunityAssociations === null) {
return $associationData;
}
// Handle array input (from extractAssociationIds)
if (is_array($opportunityAssociations)) {
return $opportunityAssociations;
}
// Handle CollectionResponseAssociatedId object
if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {
foreach ($opportunityAssociations->getResults() as $association) {
$associationData[] = $association->getId();
}
}
return $associationData;
}
private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity
{
if (empty($crmData['properties'])) {
return null;
}
$crmId = (string) $crmData['id'];
$properties = $crmData['properties'];
$associations = $crmData['associations'] ?? [];
$opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(
$this->config,
$crmId
);
if ($opportunityExists) {
return $this->updateOpportunity($crmId, $properties, $associations);
} else {
return $this->createOpportunity($crmId, $properties, $associations);
}
}
/**
* Create new opportunity
*/
private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity
{
$accountId = $this->resolveAccountId($associations);
if (! $accountId) {
return null;
}
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
if (! $businessProcess) {
return null;
}
$stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);
if (! $stage) {
return null;
}
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->importOpportunityContacts($opportunity, $associations['contacts']);
if ($opportunity->wasRecentlyCreated) {
MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());
}
return $opportunity;
}
/**
* Update existing opportunity
*/
private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity
{
$accountId = $this->resolveAccountId($associations);
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
$stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->updateOpportunityAssociations($opportunity, $associations);
return $opportunity;
}
private function resolveAccountId(array $associations): ?int
{
if (! empty($associations['accountId'])) {
return $associations['accountId'];
}
if (empty($associations)) {
return null;
}
// we can't resolve multiple account ids (currently SDK returns one company)
foreach ($associations['companies'] as $accountId) {
return $accountId;
}
return null;
}
private function buildOpportunityData(
array $properties,
?int $accountId,
?BusinessProcess $businessProcess,
?Stage $stage
): array {
$ownerId = null;
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$name = 'Unknown';
if (isset($properties['dealname'])) {
$name = mb_strimwidth($properties['dealname'], 0, 128);
}
$amount = $this->resolveAmount($properties);
$currency = $properties['deal_currency_code'] ?? null;
$closeDate = null;
if (! empty($properties['closedate'])) {
$closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');
}
$remotelyCreatedAt = null;
if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {
$date = $this->parseCleanDatetime($properties['createdate']);
$remotelyCreatedAt = $date?->format('Y-m-d H:i:s');
}
$closedStages = $this->getClosedDealStages();
$isWon = in_array($properties['dealstage'], $closedStages['won']);
$isLost = in_array($properties['dealstage'], $closedStages['lost']);
$data = [
'team_id' => $this->team->getId(),
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => $name,
'value' => ! empty($amount) ? $amount : null,
'currency_code' => CurrencyFormatter::formatCode($currency),
'close_date' => $closeDate,
'is_closed' => $isWon || $isLost,
'is_won' => $isWon,
'remotely_created_at' => $remotelyCreatedAt,
'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),
'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),
];
if ($accountId) {
$data['account_id'] = $accountId;
}
if ($stage) {
$data['stage_id'] = $stage->id;
}
if ($businessProcess) {
$recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);
if ($recordType) {
$data['record_type_id'] = $recordType->id;
}
}
return $data;
}
private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess
{
if ($pipelineId === null) {
return null;
}
if (isset($this->cachedBusinessProcesses[$pipelineId])) {
return $this->cachedBusinessProcesses[$pipelineId];
}
$businessProcess = $this->getBusinessProcess($pipelineId);
if (! $businessProcess instanceof BusinessProcess) {
$this->importStages();
$businessProcess = $this->getBusinessProcess($pipelineId);
}
if (! $businessProcess instanceof BusinessProcess) {
$this->logger->info(
'[HubSpot] Deal is not attached to a pipeline',
[
'pipeline' => $pipelineId]
);
}
$this->cachedBusinessProcesses[$pipelineId] = $businessProcess;
return $businessProcess;
}
private function getBusinessProcess(string $pipelineId): ?BusinessProcess
{
return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);
}
private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage
{
if (empty($stageId)) {
return null;
}
$cacheKey = $businessProcess->getId() . ':' . $stageId;
if (isset($this->cachedStages[$cacheKey])) {
return $this->cachedStages[$cacheKey];
}
$stage = $this->crmEntityRepository->getPipelineStageByConditions(
$businessProcess,
[
'crm_provider_id' => $stageId,
'type' => Stage::TYPE_OPPORTUNITY,
]
);
if ($stage === null) {
$this->importStages(null, $stageId);
}
if ($stage === null) {
$this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);
}
$this->cachedStages[$cacheKey] = $stage;
return $stage;
}
private function resolveAmount(array $properties): ?string
{
$amount = null;
if (! empty($properties['amount'])) {
$amount = str_replace(',', '', $properties['amount']);
}
if ($this->config->hasDefaultCurrencyFieldSet()) {
$valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();
$amount = $properties[$valueFieldName] ?? $amount;
}
return $amount;
}
private function parseCleanDatetime(string $datetime): ?Carbon
{
// Treat pre-1980 values as invalid
$minValidDate = Carbon::parse('1980-01-01 00:00:00');
try {
$date = Carbon::parse($datetime);
if ($minValidDate->gt($date)) {
return null;
}
return $date;
} catch (Exception) {
return null; // On parse error, treat as null
}
}
private function resolveDealProbability(?string $stageProbability): int
{
if ($stageProbability === null) {
return 0;
}
$probability = (float) $stageProbability;
return $probability > 1 ? 0 : (int) ($probability * 100);
}
private function resolveForecastCategory(?string $forecastCategory): string
{
if (! $forecastCategory) {
return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;
}
$forecastCategory = str_replace('_', ' ', $forecastCategory);
return ucwords(strtolower($forecastCategory));
}
private function importExternalFieldData(array $properties, int $opportunityId): void
{
$crmFields = $this->getOpportunitySyncableFields();
$this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);
}
private function importOpportunityContacts(Opportunity $opportunity, array $associations): void
{
// Handle empty or missing contact associations
if (empty($associations)) {
// Remove all existing contact associations if none provided
$this->removeAllOpportunityContacts($opportunity);
return;
}
// Use differential sync approach for better performance and accuracy
$this->syncOpportunityContactsDifferential($opportunity, $associations);
}
/**
* Sync opportunity contacts using differential approach
* This compares current vs new associations and only makes necessary changes
*/
private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void
{
$currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);
$contactAssociationIds = array_keys($contactAssociations);
$contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);
$contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);
if (empty($contactsToAdd) && empty($contactsToRemove)) {
return;
}
$this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);
$this->removeContactAssociations($opportunity, $contactsToRemove);
$this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);
}
private function getCurrentContactCrmIds(Opportunity $opportunity): array
{
return $opportunity->contacts()
->pluck('contacts.crm_provider_id')
->toArray();
}
private function logContactAssociationChanges(
Opportunity $opportunity,
array $currentContactCrmIds,
array $contactAssociations,
array $contactsToAdd,
array $contactsToRemove
): void {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [
'opportunity_id' => $opportunity->getId(),
'current_contacts' => $currentContactCrmIds,
'new_contacts' => $contactAssociations,
'contacts_to_add' => $contactsToAdd,
'contacts_to_remove' => $contactsToRemove,
]);
}
private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void
{
if (empty($contactsToRemove)) {
return;
}
$contactsToDetach = $opportunity->contacts()
->whereIn('contacts.crm_provider_id', $contactsToRemove)
->pluck('contacts.id')
->toArray();
if (! empty($contactsToDetach)) {
$opportunity->contacts()->detach($contactsToDetach);
$this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_contact_crm_ids' => $contactsToRemove,
'removed_contact_count' => count($contactsToDetach),
]);
}
}
private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void
{
if (empty($contactsToAdd)) {
return;
}
$contactsAdded = [];
foreach ($contactsToAdd as $crmId) {
$id = $contactAssociations[$crmId];
if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {
$contactsAdded[] = $crmId;
}
}
$this->logAddedContacts($opportunity, $contactsAdded);
}
private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool
{
try {
$contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);
if (! $contact) {
return false;
}
return $this->performContactAttachment($opportunity, $contact, $crmId);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [
'opportunity_id' => $opportunity->getId(),
'contact_crm_id' => $crmId,
'error' => $e->getMessage(),
]);
return false;
}
}
private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool
{
try {
$opportunity->contacts()->attach($contact->getId(), [
'crm_provider_id' => $crmId,
]);
return true;
} catch (\Illuminate\Database\QueryException $e) {
if (str_contains($e->getMessage(), 'Duplicate entry')) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [
'contact_id' => $contact->getId(),
'contact_crm_id' => $crmId,
'opportunity_id' => $opportunity->getId(),
]);
return false;
}
throw $e;
}
}
private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void
{
if (! empty($contactsAdded)) {
$this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [
'opportunity_id' => $opportunity->getId(),
'contacts_to_add_count' => count($contactsAdded),
'added_contact_crm_ids' => $contactsAdded,
'added_contacts_count' => count($contactsAdded),
]);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
1
Previous Highlighted Error
Next Highlighted Error
<template>
<WelcomeLayout
title="Account disconnected"
textPosition="center"
:icon="faUnlink"
:class="$style.layout"
>
<div :class="$style.container" v-if="providersLoaded">
<p>
<strong>
It looks like your {{ localProvider.displayName }} account has become
disconnected
</strong>
</p>
<p :class="$style.small">Please re-connect to continue</p>
<p v-if="isInIframe">
We'll open the {{ localProvider.displayName }} authentication in a new
tab. Please return here and refresh the page once complete
</p>
<GoogleLikeButton
v-if="localProvider.viaIntegrationApp && crmTokenLoaded"
as="a"
:key="localProvider.name"
:brand-logo="localProvider.name"
:class="$style.connectButton"
@click="integrationAppOnClick"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
<GoogleLikeButton
v-if="!localProvider.viaIntegrationApp"
as="a"
:key="localProvider.name"
:href="`/auth/redirect/${localProvider.name}`"
:target="target"
:brand-logo="localProvider.name"
:class="$style.connectButton"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
</div>
<BuildInfo />
<KioskBanner />
</WelcomeLayout>
</template>
<script>
import window from "window";
import axios from "axios";
import { faUnlink } from "@fortawesome/pro-regular-svg-icons";
import isInIframe from "@/utils/isInIframe";
import BuildInfo from "@/components/layout/BuildInfo/BuildInfo.vue";
import KioskBanner from "@/components/shared/KioskBanner/KioskBanner.vue";
import WelcomeLayout from "@/components/layout/WelcomeLayout/WelcomeLayout.vue";
import GoogleLikeButton from "@/components/shared/Buttons/GoogleLikeButton.vue";
import { showSnackbarError, normalizeError } from "@/utils/index";
import { IntegrationAppClient } from "@integration-app/sdk";
export default {
name: "ConnectPage",
components: {
BuildInfo,
KioskBanner,
WelcomeLayout,
GoogleLikeButton,
},
data() {
return {
...window.connectData,
crmToken: null,
faUnlink,
isInIframe,
providers: [],
providersLoaded: false,
crmTokenLoaded: false,
};
},
computed: {
localProvider() {
return this.providers.find((e) => e.name === this.provider);
},
target() {
return this.isInIframe ? "_blank" : null;
},
},
created() {
this.getProviders();
},
mounted() {
this.showErrors();
},
watch: {
providersLoaded() {
if (this.providersLoaded) {
this.prepareIntegrationAppConnection();
}
},
},
methods: {
showErrors() {
if (!this.error) return;
showSnackbarError(this.error, undefined, undefined, false);
},
unwrapEntityResponse({ data }) {
return data.map(({ icon, name, displayName, viaIntegrationApp }) => {
return { icon, name, displayName, viaIntegrationApp };
});
},
async getProviders() {
try {
const response = await axios.get("/api/v1/connect-providers");
this.providers = this.unwrapEntityResponse(response);
this.providersLoaded = true;
} catch {
showSnackbarError(
"An error occurred, while loading form data (connect providers).",
);
}
},
async prepareIntegrationAppConnection() {
if (this.localProvider.viaIntegrationApp) {
try {
const response = await axios.get("/api/v1/integration-app-token");
this.crmToken = response.data.token;
this.crmTokenLoaded = true;
} catch (error) {
console.log(error);
showSnackbarError(
`An error occurred while preparing the page.
Try refreshing, if the error persists get in touch with the Jiminny team.`,
);
}
}
},
async integrationAppOnClick() {
const integrationApp = new IntegrationAppClient({
token: this.crmToken,
});
const connection = await integrationApp
.integration(this.localProvider.name)
.openNewConnection({
showPoweredBy: false,
allowMultipleConnections: false,
});
console.log('[IntegrationApp] openNewConnection resolved:', JSON.stringify(connection));
// [IntegrationApp] openNewConnection resolved: {
// "id":"69e0b41a67d0068c2ca0b48e",
// "name":"Zoho CRM",
// "userId":"1ece66c8-feb1-4df1-b321-21607daf4623",
// "tenantId":"69e0b3faef3e7b6248189289",
// "isTest":false,
// "connected":true,
// "state":"READY",
// "errors":[],
// "integrationId":"66fe6c913202f3a165e3c14d",
// "externalAppId":"6671653e7e2d642e4e41b0fa",
// "authOptionKey":"",
// "createdAt":"2026-04-16T10:04:10.420Z",
// "updatedAt":"2026-04-16T10:04:10.575Z",
// "retryAttempts":0,
// "isDeactivated":false
// }
if (connection && connection.disconnected !== true && connection.connected !== false) {
console.log('[IntegrationApp] connection condition matched');
try {
const saveRequest = await axios.post(
"/api/v1/integration-app-connect",
);
if (saveRequest.data && saveRequest.data.success === true) {
/** If all is good refresh the page here */
window.location = "/dashboard";
return;
}
throw new Error(saveRequest.data.message);
} catch (error) {
console.log(error);
showSnackbarError(normalizeError(error));
}
}
},
},
};
</script>
<style module lang="less" src="./connect.less"></style>
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20692-fix-integration-app-token-auth-response-change, menu","depth":5,"help_text":"Git Branch: JY-20692-fix-integration-app-token-auth-response-change","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsCommandTest","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsCommandTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsCommandTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show Replace Field","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Search History","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"cachedStages","depth":4,"value":"cachedStages","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Match Case","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Words","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Regex","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Replace History","depth":3,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Replace","depth":4,"role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Preserve case","depth":3,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2/4","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Occurrence","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Occurrence","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Filter Search Results","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open in Window, Multiple Cursors","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Click to highlight","depth":4,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"33","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"19","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\CollectionResponseAssociatedId;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Models\\Account;\nuse Exception;\nuse Jiminny\\Component\\DealInsights\\Forecast\\Forecast;\nuse Jiminny\\Jobs\\Crm\\MatchActivitiesToNewOpportunity;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Crm\\BusinessProcess;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Models\\Opportunity;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DealFieldsService;\nuse Jiminny\\Services\\Crm\\Hubspot\\OpportunitySyncStrategy\\HubspotSingleSyncStrategy;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Services\\Crm\\OpportunitySyncStrategyResolver;\nuse Jiminny\\Utils\\CurrencyFormatter;\n\n/**\n * Optimized sync methods for better performance\n * These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains\n */\ntrait OpportunitySyncTrait\n{\n private const int BATCH_SIZE = 100;\n private const int BATCH_PROCESS_SIZE = 800;\n\n protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;\n protected CrmEntityRepository $crmEntityRepository;\n protected DealFieldsService $dealFieldsService;\n\n private ?array $cachedClosedDealStages = null;\n private array $cachedBusinessProcesses = [];\n private array $cachedStages = [];\n\n public function syncOpportunities(array $parameters, ?string $strategy = null): int\n {\n $strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);\n $parameters['config'] = $this->config;\n $syncCount = 0;\n $reportedTotal = 0;\n $lastSyncedId = [];\n\n try {\n foreach ($strategies as $strategyName => $syncStrategy) {\n $this->logger->info(\n '[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .\n $strategyName\n );\n\n $total = 0;\n $lastId = null;\n $buffer = [];\n\n // HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies\n foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {\n $buffer[] = $hsOpportunity;\n\n // process every 800 rows (fits < 1 000 association limit)\n if (\\count($buffer) >= self::BATCH_PROCESS_SIZE) {\n $syncCount += $this->processOpportunityBatch($buffer);\n $buffer = [];\n }\n }\n\n // leftovers\n if ($buffer) {\n $syncCount += $this->processOpportunityBatch($buffer);\n }\n\n $reportedTotal += $total;\n $lastSyncedId = $lastId;\n }\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException | CrmException $e) {\n $this->handleSyncException($e, $parameters);\n }\n\n $this->logger->info(\n '[HubSpot] Synced opportunities',\n [\n 'team' => $this->team->getId(),\n 'sync_count' => $syncCount,\n 'total' => $reportedTotal,\n 'last_synced_id' => $lastSyncedId,\n ]\n );\n\n return $reportedTotal;\n }\n\n private function handleSyncException(\\Throwable $e, array $parameters): void\n {\n if (($parameters['since'] ?? null) instanceof Carbon) {\n $parameters['since'] = $parameters['since']->toDateTimeString();\n }\n $parameters['config'] = $this->config->getId();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [\n 'teamId' => $this->team->getUuid(),\n 'parameters' => $parameters,\n 'reason' => $e->getMessage(),\n ]);\n }\n\n /**\n * @inheritdoc\n */\n public function syncOpportunity(string $crmId): ?Opportunity\n {\n $strategy = $this->opportunitySyncStrategyResolver->resolve(\n $this->config,\n OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,\n );\n\n $parameters = [\n 'config' => $this->config,\n 'crm_id' => $crmId,\n ];\n\n try {\n if (! $strategy instanceof HubspotSingleSyncStrategy) {\n throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');\n }\n\n $hsOpportunity = $strategy->fetchOpportunity($parameters);\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);\n\n return $this->importOrUpdateOpportunity($hsOpportunity);\n }\n\n /**\n * Process webhook-collected opportunity batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportOpportunityBatch jobs for batch processing.\n *\n * @return int Number of opportunity IDs dispatched to jobs\n */\n public function batchSyncOpportunities(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,\n $configId\n );\n }\n\n /**\n * Import a batch of opportunities by their CRM IDs.\n * Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().\n *\n * @param array<string> $crmIds HubSpot deal CRM IDs\n *\n * @return array{success: array, failed_ids: array, errors?: array<string, string>}\n */\n public function importOpportunityBatchByIds(array $crmIds): array\n {\n $fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);\n\n $allDeals = [];\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n $deals = $this->client->getOpportunitiesByIds($chunk, $fields);\n foreach ($deals as $deal) {\n $allDeals[] = $deal;\n }\n }\n\n // IDs not returned by HubSpot are likely deleted or inaccessible deals.\n // These are not failures — retrying won't bring them back.\n $fetchedIds = array_map('strval', array_column($allDeals, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allDeals),\n ]);\n }\n\n if (empty($allDeals)) {\n return ['success' => [], 'failed_ids' => []];\n }\n\n return $this->importOpportunityBatch($allDeals);\n }\n\n private function getClosedDealStages(): array\n {\n if ($this->cachedClosedDealStages !== null) {\n return $this->cachedClosedDealStages;\n }\n\n $stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);\n $data = [\n 'lost' => [],\n 'won' => [],\n ];\n\n foreach ($stages as $stage) {\n if ($stage->probability == 0.00) {\n $data['lost'][] = $stage->crm_provider_id;\n }\n if ($stage->probability == 100.00) {\n $data['won'][] = $stage->crm_provider_id;\n }\n }\n\n $this->cachedClosedDealStages = $data;\n\n return $data;\n }\n\n /**\n * Import deals into the database with pre-fetched associations.\n *\n * API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT\n * caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()\n * where Laravel retries the whole job with backoff. After all retries exhausted,\n * failed() requeues all IDs to Redis.\n *\n * The per-deal loop catches exceptions individually. A deal can end up in three states:\n * - success: imported/updated successfully\n * - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)\n * These are permanent issues — retrying won't fix them.\n * - skipped (null): missing dependencies (no account, unknown pipeline/stage).\n * This is acceptable — the deal cannot be imported until those exist.\n */\n private function importOpportunityBatch(array $deals): array\n {\n $syncedOpportunities = [\n 'success' => [],\n 'failed_ids' => [],\n ];\n $dealIds = array_column($deals, 'id');\n\n // Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the\n // queue job retries the whole batch and eventually requeues all deal IDs back to Redis.\n try {\n $companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');\n $contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');\n\n $associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);\n\n $existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(\n $this->config,\n array_map('strval', $dealIds)\n );\n $existingCrmIdSet = array_flip($existingCrmIds);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [\n 'teamId' => $this->team->getId(),\n 'dealCount' => count($dealIds),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n foreach ($deals as $deal) {\n try {\n $deal['associations'] = $this->prepareAssociationsForOpportunity(\n $deal['id'],\n $companyAssociations,\n $contactAssociations,\n $associationsData\n );\n\n $syncedOpportunity = $this->importOrUpdateOpportunity(\n $deal,\n isset($existingCrmIdSet[(string) $deal['id']])\n );\n if ($syncedOpportunity) {\n $syncedOpportunities['success'][] = $syncedOpportunity;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $deal['id'],\n 'error' => $e->getMessage(),\n ]);\n $syncedOpportunities['failed_ids'][] = $deal['id'];\n $syncedOpportunities['errors'][$deal['id']] = $e->getMessage();\n }\n }\n\n return $syncedOpportunities;\n }\n\n /**\n * Prepare associated entities for opportunities with optimized batch processing\n * Returns structured data with CRM ID to DB ID mappings for each opportunity\n */\n private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array\n {\n // Step 1: Collect all unique company and contact IDs from associations\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n\n // Step 2: Batch sync missing entities and get CRM ID to DB ID mappings\n $companyIdMappings = [];\n $contactIdMappings = [];\n\n if (! empty($allCompanyIds)) {\n $companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);\n }\n\n if (! empty($allContactIds)) {\n $contactIdMappings = $this->prepareAssociatedContacts($allContactIds);\n }\n\n return [\n 'company_id_mappings' => $companyIdMappings,\n 'contact_id_mappings' => $contactIdMappings,\n ];\n }\n\n /**\n * Flatten association data to get unique IDs\n */\n private function flattenAssociationIds(array $associations): array\n {\n $ids = [];\n foreach ($associations as $dealAssociations) {\n if (is_array($dealAssociations)) {\n foreach ($dealAssociations as $id) {\n $ids[$id] = true;\n }\n }\n }\n\n return array_keys($ids);\n }\n\n /**\n * Batch sync missing accounts\n */\n private function prepareAssociatedAccounts(array $companyIds): array\n {\n // Find which accounts already exist\n $existingAccounts = $this->crmEntityRepository\n ->findAccountsByExternalIds($this->config, $companyIds);\n\n $existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();\n\n $existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {\n return [$account->getCrmProviderId() => $account->getId()];\n })->toArray();\n\n $missingCompanyIds = array_diff($companyIds, $existingCompanyIds);\n\n if (empty($missingCompanyIds)) {\n return $existingAccountsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [\n 'teamId' => $this->team->getUuid(),\n 'total_companies' => count($companyIds),\n 'existing_companies' => count($existingCompanyIds),\n 'missing_companies' => count($missingCompanyIds),\n ]);\n\n // we already have limit on opportunity ids count\n // Initialize variable before try block\n $syncedAccountsData = [];\n\n try {\n $syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [\n 'size' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedAccountsData = [];\n }\n\n return $existingAccountsData + $syncedAccountsData;\n }\n\n /**\n * Prepare associated contacts - find existing and sync missing ones\n * Returns mapping of CRM ID to DB ID\n */\n private function prepareAssociatedContacts(array $contactIds): array\n {\n // Find which contacts already exist\n $existingContacts = $this->crmEntityRepository\n ->findContactsByExternalIds($this->config, $contactIds);\n\n $existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();\n\n // Create mapping for existing contacts\n $existingContactsData = $existingContacts->mapWithKeys(function ($contact) {\n return [$contact->getCrmProviderId() => $contact->getId()];\n })->toArray();\n\n $missingContactIds = array_diff($contactIds, $existingContactIds);\n\n if (empty($missingContactIds)) {\n return $existingContactsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [\n 'teamId' => $this->team->getUuid(),\n 'total_contacts' => count($contactIds),\n 'existing_contacts' => count($existingContactIds),\n 'missing_contacts' => count($missingContactIds),\n ]);\n\n // Sync missing contacts using batch API\n try {\n $syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [\n 'size' => count($missingContactIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedContactsData = [];\n }\n\n return $existingContactsData + $syncedContactsData;\n }\n\n private function batchSyncCrmObjects(string $objectType, array $crmIds): array\n {\n $syncObjects = [];\n $crmObjectIds = array_values($crmIds);\n\n foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {\n try {\n $objects = $objectType === 'companies' ?\n $this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :\n $this->client->getContactsByIds($chunk, $this->getContactFields());\n\n foreach ($objects as $objectId => $objectData) {\n $this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [\n 'requested_count' => count($chunk),\n 'synced_count' => count($objects),\n ]);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [\n 'ids' => $chunk,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncObjects;\n }\n\n private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void\n {\n try {\n $object = $objectType === 'companies' ?\n $this->importAccount($objectData) :\n $this->importContact($objectData);\n\n if ($object) {\n $syncObjects[$object->getCrmProviderId()] = $object->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [\n 'id' => $objectId,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n /**\n * Prepare associations for a single opportunity\n *\n * The return value is an array with the following structure:\n * [\n * 'companies' => [\n * $companyCrmId => $companyId,\n * ...\n * ],\n * 'contacts' => [\n * $contactCrmId => $contactId,\n * ...\n * ],\n * 'account_id' => $accountId,\n * ]\n */\n private function prepareAssociationsForOpportunity(\n string $oppCrmId,\n array $companyAssociations,\n array $contactAssociations,\n array $associationsData\n ): array {\n $associations = [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n\n $oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];\n foreach ($oppCompanyIds as $companyCrmId) {\n if (isset($associationsData['company_id_mappings'][$companyCrmId])) {\n $associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];\n\n // Set primary account (first company becomes primary account)\n if ($associations['account_id'] === null) {\n $associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];\n }\n }\n }\n\n $oppContactIds = $contactAssociations[$oppCrmId] ?? [];\n foreach ($oppContactIds as $contactCrmId) {\n if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {\n $associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];\n }\n }\n\n return $associations;\n }\n\n /**\n * Update only associations for an opportunity\n */\n private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void\n {\n // Update contact associations\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n // Update company (account) associations\n $this->updateOpportunityAccount($opportunity, $associations['account_id']);\n }\n\n /**\n * Remove all contact associations from an opportunity\n */\n private function removeAllOpportunityContacts(Opportunity $opportunity): void\n {\n $currentCount = (int) $opportunity->contacts()->count();\n\n if ($currentCount > 0) {\n $opportunity->contacts()->detach();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_count' => $currentCount,\n ]);\n }\n }\n\n private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void\n {\n if ($accountId === null) {\n // No account ID provided - keep current account\n return;\n }\n\n $currentAccountId = $opportunity->getAccountId();\n\n // Only update if account has changed\n if ($currentAccountId !== $accountId) {\n $opportunity->account_id = $accountId;\n $opportunity->save();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [\n 'opportunity_id' => $opportunity->getId(),\n 'old_account_id' => $currentAccountId,\n 'new_account_id' => $accountId,\n ]);\n }\n }\n\n /**\n * Find existing opportunities by external IDs (OPTIMIZED VERSION)\n * Uses batch query for better performance\n */\n private function findExistingOpportunities(array $crmIds): Collection\n {\n return $this->crmEntityRepository\n ->findOpportunitiesByExternalIds($this->config, $crmIds);\n }\n\n private function processOpportunityBatch(array $opportunities): int\n {\n $syncedOpportunities = $this->importOpportunityBatch($opportunities);\n\n return count($syncedOpportunities['success'] ?? []);\n }\n\n /**\n * Convert single deal associations from HubSpot format to internal format\n * Handles both HubSpot SDK objects and array formats\n *\n * @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed\n *\n * @return array Processed associations with DB IDs\n */\n private function convertDealAssociations(array $opportunityAssociations): array\n {\n $associations = $this->initializeAssociationsStructure();\n\n if (empty($opportunityAssociations)) {\n return $associations;\n }\n\n $associationIds = $this->extractAssociationIds($opportunityAssociations);\n\n $this->processCompanyAssociations($associationIds, $associations);\n $this->processContactAssociations($associationIds, $associations);\n\n return $associations;\n }\n\n private function initializeAssociationsStructure(): array\n {\n return [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n }\n\n private function extractAssociationIds(array $opportunityAssociations): array\n {\n $associationIds = [];\n\n foreach ($opportunityAssociations as $type => $associationData) {\n if (! empty($associationData)) {\n $associationIds[$type] = $this->convertSingleDealAssociations($associationData);\n }\n }\n\n return $associationIds;\n }\n\n private function processCompanyAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['companies'])) {\n return;\n }\n\n $companyId = $associationIds['companies'][0];\n $account = $this->findOrSyncAccount($companyId);\n\n if ($account instanceof Account) {\n $associations['companies'][$companyId] = $account->getId();\n $associations['account_id'] = $account->getId();\n }\n }\n\n private function processContactAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['contacts'])) {\n return;\n }\n\n foreach ($associationIds['contacts'] as $contactId) {\n $contact = $this->findOrSyncContact($contactId);\n\n if ($contact instanceof Contact) {\n $associations['contacts'][$contactId] = $contact->getId();\n }\n }\n }\n\n private function findOrSyncAccount(string $companyId): ?Account\n {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);\n\n if (! $account instanceof Account) {\n $account = $this->syncAccount($companyId);\n }\n\n return $account;\n }\n\n private function findOrSyncContact(string $contactId): ?Contact\n {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);\n\n if (! $contact instanceof Contact) {\n $contact = $this->syncContact($contactId);\n }\n\n return $contact;\n }\n\n private function convertSingleDealAssociations($opportunityAssociations = null): array\n {\n $associationData = [];\n\n if ($opportunityAssociations === null) {\n return $associationData;\n }\n\n // Handle array input (from extractAssociationIds)\n if (is_array($opportunityAssociations)) {\n return $opportunityAssociations;\n }\n\n // Handle CollectionResponseAssociatedId object\n if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {\n foreach ($opportunityAssociations->getResults() as $association) {\n $associationData[] = $association->getId();\n }\n }\n\n return $associationData;\n }\n\n private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity\n {\n if (empty($crmData['properties'])) {\n return null;\n }\n\n $crmId = (string) $crmData['id'];\n $properties = $crmData['properties'];\n $associations = $crmData['associations'] ?? [];\n\n $opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(\n $this->config,\n $crmId\n );\n\n if ($opportunityExists) {\n return $this->updateOpportunity($crmId, $properties, $associations);\n } else {\n return $this->createOpportunity($crmId, $properties, $associations);\n }\n }\n\n /**\n * Create new opportunity\n */\n private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n if (! $accountId) {\n return null;\n }\n\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n if (! $businessProcess) {\n return null;\n }\n\n $stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);\n if (! $stage) {\n return null;\n }\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n if ($opportunity->wasRecentlyCreated) {\n MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());\n }\n\n return $opportunity;\n }\n\n /**\n * Update existing opportunity\n */\n private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n $stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->updateOpportunityAssociations($opportunity, $associations);\n\n return $opportunity;\n }\n\n private function resolveAccountId(array $associations): ?int\n {\n if (! empty($associations['accountId'])) {\n return $associations['accountId'];\n }\n\n if (empty($associations)) {\n return null;\n }\n\n // we can't resolve multiple account ids (currently SDK returns one company)\n foreach ($associations['companies'] as $accountId) {\n return $accountId;\n }\n\n return null;\n }\n\n private function buildOpportunityData(\n array $properties,\n ?int $accountId,\n ?BusinessProcess $businessProcess,\n ?Stage $stage\n ): array {\n $ownerId = null;\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $name = 'Unknown';\n if (isset($properties['dealname'])) {\n $name = mb_strimwidth($properties['dealname'], 0, 128);\n }\n\n $amount = $this->resolveAmount($properties);\n $currency = $properties['deal_currency_code'] ?? null;\n\n $closeDate = null;\n if (! empty($properties['closedate'])) {\n $closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');\n }\n\n $remotelyCreatedAt = null;\n if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {\n $date = $this->parseCleanDatetime($properties['createdate']);\n $remotelyCreatedAt = $date?->format('Y-m-d H:i:s');\n }\n\n $closedStages = $this->getClosedDealStages();\n $isWon = in_array($properties['dealstage'], $closedStages['won']);\n $isLost = in_array($properties['dealstage'], $closedStages['lost']);\n\n $data = [\n 'team_id' => $this->team->getId(),\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'value' => ! empty($amount) ? $amount : null,\n 'currency_code' => CurrencyFormatter::formatCode($currency),\n 'close_date' => $closeDate,\n 'is_closed' => $isWon || $isLost,\n 'is_won' => $isWon,\n 'remotely_created_at' => $remotelyCreatedAt,\n 'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),\n 'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),\n ];\n\n if ($accountId) {\n $data['account_id'] = $accountId;\n }\n\n if ($stage) {\n $data['stage_id'] = $stage->id;\n }\n\n if ($businessProcess) {\n $recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);\n if ($recordType) {\n $data['record_type_id'] = $recordType->id;\n }\n }\n\n return $data;\n }\n\n private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess\n {\n if ($pipelineId === null) {\n return null;\n }\n\n if (isset($this->cachedBusinessProcesses[$pipelineId])) {\n return $this->cachedBusinessProcesses[$pipelineId];\n }\n\n $businessProcess = $this->getBusinessProcess($pipelineId);\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->importStages();\n $businessProcess = $this->getBusinessProcess($pipelineId);\n }\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->logger->info(\n '[HubSpot] Deal is not attached to a pipeline',\n [\n 'pipeline' => $pipelineId]\n );\n }\n\n $this->cachedBusinessProcesses[$pipelineId] = $businessProcess;\n\n return $businessProcess;\n }\n\n private function getBusinessProcess(string $pipelineId): ?BusinessProcess\n {\n return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);\n }\n\n private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage\n {\n if (empty($stageId)) {\n return null;\n }\n\n $cacheKey = $businessProcess->getId() . ':' . $stageId;\n if (isset($this->cachedStages[$cacheKey])) {\n return $this->cachedStages[$cacheKey];\n }\n\n $stage = $this->crmEntityRepository->getPipelineStageByConditions(\n $businessProcess,\n [\n 'crm_provider_id' => $stageId,\n 'type' => Stage::TYPE_OPPORTUNITY,\n ]\n );\n\n if ($stage === null) {\n $this->importStages(null, $stageId);\n }\n\n if ($stage === null) {\n $this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);\n }\n\n $this->cachedStages[$cacheKey] = $stage;\n\n return $stage;\n }\n\n private function resolveAmount(array $properties): ?string\n {\n $amount = null;\n if (! empty($properties['amount'])) {\n $amount = str_replace(',', '', $properties['amount']);\n }\n\n if ($this->config->hasDefaultCurrencyFieldSet()) {\n $valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();\n $amount = $properties[$valueFieldName] ?? $amount;\n }\n\n return $amount;\n }\n\n private function parseCleanDatetime(string $datetime): ?Carbon\n {\n // Treat pre-1980 values as invalid\n $minValidDate = Carbon::parse('1980-01-01 00:00:00');\n\n try {\n $date = Carbon::parse($datetime);\n\n if ($minValidDate->gt($date)) {\n return null;\n }\n\n return $date;\n } catch (Exception) {\n return null; // On parse error, treat as null\n }\n }\n\n private function resolveDealProbability(?string $stageProbability): int\n {\n if ($stageProbability === null) {\n return 0;\n }\n\n $probability = (float) $stageProbability;\n\n return $probability > 1 ? 0 : (int) ($probability * 100);\n }\n\n private function resolveForecastCategory(?string $forecastCategory): string\n {\n if (! $forecastCategory) {\n return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;\n }\n\n $forecastCategory = str_replace('_', ' ', $forecastCategory);\n\n return ucwords(strtolower($forecastCategory));\n }\n\n private function importExternalFieldData(array $properties, int $opportunityId): void\n {\n $crmFields = $this->getOpportunitySyncableFields();\n $this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);\n }\n\n private function importOpportunityContacts(Opportunity $opportunity, array $associations): void\n {\n // Handle empty or missing contact associations\n if (empty($associations)) {\n // Remove all existing contact associations if none provided\n $this->removeAllOpportunityContacts($opportunity);\n\n return;\n }\n\n // Use differential sync approach for better performance and accuracy\n $this->syncOpportunityContactsDifferential($opportunity, $associations);\n }\n\n /**\n * Sync opportunity contacts using differential approach\n * This compares current vs new associations and only makes necessary changes\n */\n private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void\n {\n $currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);\n $contactAssociationIds = array_keys($contactAssociations);\n\n $contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);\n $contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);\n\n if (empty($contactsToAdd) && empty($contactsToRemove)) {\n return;\n }\n\n $this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);\n\n $this->removeContactAssociations($opportunity, $contactsToRemove);\n $this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);\n }\n\n private function getCurrentContactCrmIds(Opportunity $opportunity): array\n {\n return $opportunity->contacts()\n ->pluck('contacts.crm_provider_id')\n ->toArray();\n }\n\n private function logContactAssociationChanges(\n Opportunity $opportunity,\n array $currentContactCrmIds,\n array $contactAssociations,\n array $contactsToAdd,\n array $contactsToRemove\n ): void {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [\n 'opportunity_id' => $opportunity->getId(),\n 'current_contacts' => $currentContactCrmIds,\n 'new_contacts' => $contactAssociations,\n 'contacts_to_add' => $contactsToAdd,\n 'contacts_to_remove' => $contactsToRemove,\n ]);\n }\n\n private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void\n {\n if (empty($contactsToRemove)) {\n return;\n }\n\n $contactsToDetach = $opportunity->contacts()\n ->whereIn('contacts.crm_provider_id', $contactsToRemove)\n ->pluck('contacts.id')\n ->toArray();\n\n if (! empty($contactsToDetach)) {\n $opportunity->contacts()->detach($contactsToDetach);\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_contact_crm_ids' => $contactsToRemove,\n 'removed_contact_count' => count($contactsToDetach),\n ]);\n }\n }\n\n private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void\n {\n if (empty($contactsToAdd)) {\n return;\n }\n\n $contactsAdded = [];\n foreach ($contactsToAdd as $crmId) {\n $id = $contactAssociations[$crmId];\n\n if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {\n $contactsAdded[] = $crmId;\n }\n }\n\n $this->logAddedContacts($opportunity, $contactsAdded);\n }\n\n private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);\n\n if (! $contact) {\n return false;\n }\n\n return $this->performContactAttachment($opportunity, $contact, $crmId);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [\n 'opportunity_id' => $opportunity->getId(),\n 'contact_crm_id' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool\n {\n try {\n $opportunity->contacts()->attach($contact->getId(), [\n 'crm_provider_id' => $crmId,\n ]);\n\n return true;\n } catch (\\Illuminate\\Database\\QueryException $e) {\n if (str_contains($e->getMessage(), 'Duplicate entry')) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [\n 'contact_id' => $contact->getId(),\n 'contact_crm_id' => $crmId,\n 'opportunity_id' => $opportunity->getId(),\n ]);\n\n return false;\n }\n\n throw $e;\n }\n }\n\n private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void\n {\n if (! empty($contactsAdded)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'contacts_to_add_count' => count($contactsAdded),\n 'added_contact_crm_ids' => $contactsAdded,\n 'added_contacts_count' => count($contactsAdded),\n ]);\n }\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\CollectionResponseAssociatedId;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Models\\Account;\nuse Exception;\nuse Jiminny\\Component\\DealInsights\\Forecast\\Forecast;\nuse Jiminny\\Jobs\\Crm\\MatchActivitiesToNewOpportunity;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Crm\\BusinessProcess;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Models\\Opportunity;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DealFieldsService;\nuse Jiminny\\Services\\Crm\\Hubspot\\OpportunitySyncStrategy\\HubspotSingleSyncStrategy;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Services\\Crm\\OpportunitySyncStrategyResolver;\nuse Jiminny\\Utils\\CurrencyFormatter;\n\n/**\n * Optimized sync methods for better performance\n * These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains\n */\ntrait OpportunitySyncTrait\n{\n private const int BATCH_SIZE = 100;\n private const int BATCH_PROCESS_SIZE = 800;\n\n protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;\n protected CrmEntityRepository $crmEntityRepository;\n protected DealFieldsService $dealFieldsService;\n\n private ?array $cachedClosedDealStages = null;\n private array $cachedBusinessProcesses = [];\n private array $cachedStages = [];\n\n public function syncOpportunities(array $parameters, ?string $strategy = null): int\n {\n $strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);\n $parameters['config'] = $this->config;\n $syncCount = 0;\n $reportedTotal = 0;\n $lastSyncedId = [];\n\n try {\n foreach ($strategies as $strategyName => $syncStrategy) {\n $this->logger->info(\n '[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .\n $strategyName\n );\n\n $total = 0;\n $lastId = null;\n $buffer = [];\n\n // HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies\n foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {\n $buffer[] = $hsOpportunity;\n\n // process every 800 rows (fits < 1 000 association limit)\n if (\\count($buffer) >= self::BATCH_PROCESS_SIZE) {\n $syncCount += $this->processOpportunityBatch($buffer);\n $buffer = [];\n }\n }\n\n // leftovers\n if ($buffer) {\n $syncCount += $this->processOpportunityBatch($buffer);\n }\n\n $reportedTotal += $total;\n $lastSyncedId = $lastId;\n }\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException | CrmException $e) {\n $this->handleSyncException($e, $parameters);\n }\n\n $this->logger->info(\n '[HubSpot] Synced opportunities',\n [\n 'team' => $this->team->getId(),\n 'sync_count' => $syncCount,\n 'total' => $reportedTotal,\n 'last_synced_id' => $lastSyncedId,\n ]\n );\n\n return $reportedTotal;\n }\n\n private function handleSyncException(\\Throwable $e, array $parameters): void\n {\n if (($parameters['since'] ?? null) instanceof Carbon) {\n $parameters['since'] = $parameters['since']->toDateTimeString();\n }\n $parameters['config'] = $this->config->getId();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [\n 'teamId' => $this->team->getUuid(),\n 'parameters' => $parameters,\n 'reason' => $e->getMessage(),\n ]);\n }\n\n /**\n * @inheritdoc\n */\n public function syncOpportunity(string $crmId): ?Opportunity\n {\n $strategy = $this->opportunitySyncStrategyResolver->resolve(\n $this->config,\n OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,\n );\n\n $parameters = [\n 'config' => $this->config,\n 'crm_id' => $crmId,\n ];\n\n try {\n if (! $strategy instanceof HubspotSingleSyncStrategy) {\n throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');\n }\n\n $hsOpportunity = $strategy->fetchOpportunity($parameters);\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);\n\n return $this->importOrUpdateOpportunity($hsOpportunity);\n }\n\n /**\n * Process webhook-collected opportunity batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportOpportunityBatch jobs for batch processing.\n *\n * @return int Number of opportunity IDs dispatched to jobs\n */\n public function batchSyncOpportunities(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,\n $configId\n );\n }\n\n /**\n * Import a batch of opportunities by their CRM IDs.\n * Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().\n *\n * @param array<string> $crmIds HubSpot deal CRM IDs\n *\n * @return array{success: array, failed_ids: array, errors?: array<string, string>}\n */\n public function importOpportunityBatchByIds(array $crmIds): array\n {\n $fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);\n\n $allDeals = [];\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n $deals = $this->client->getOpportunitiesByIds($chunk, $fields);\n foreach ($deals as $deal) {\n $allDeals[] = $deal;\n }\n }\n\n // IDs not returned by HubSpot are likely deleted or inaccessible deals.\n // These are not failures — retrying won't bring them back.\n $fetchedIds = array_map('strval', array_column($allDeals, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allDeals),\n ]);\n }\n\n if (empty($allDeals)) {\n return ['success' => [], 'failed_ids' => []];\n }\n\n return $this->importOpportunityBatch($allDeals);\n }\n\n private function getClosedDealStages(): array\n {\n if ($this->cachedClosedDealStages !== null) {\n return $this->cachedClosedDealStages;\n }\n\n $stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);\n $data = [\n 'lost' => [],\n 'won' => [],\n ];\n\n foreach ($stages as $stage) {\n if ($stage->probability == 0.00) {\n $data['lost'][] = $stage->crm_provider_id;\n }\n if ($stage->probability == 100.00) {\n $data['won'][] = $stage->crm_provider_id;\n }\n }\n\n $this->cachedClosedDealStages = $data;\n\n return $data;\n }\n\n /**\n * Import deals into the database with pre-fetched associations.\n *\n * API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT\n * caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()\n * where Laravel retries the whole job with backoff. After all retries exhausted,\n * failed() requeues all IDs to Redis.\n *\n * The per-deal loop catches exceptions individually. A deal can end up in three states:\n * - success: imported/updated successfully\n * - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)\n * These are permanent issues — retrying won't fix them.\n * - skipped (null): missing dependencies (no account, unknown pipeline/stage).\n * This is acceptable — the deal cannot be imported until those exist.\n */\n private function importOpportunityBatch(array $deals): array\n {\n $syncedOpportunities = [\n 'success' => [],\n 'failed_ids' => [],\n ];\n $dealIds = array_column($deals, 'id');\n\n // Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the\n // queue job retries the whole batch and eventually requeues all deal IDs back to Redis.\n try {\n $companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');\n $contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');\n\n $associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);\n\n $existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(\n $this->config,\n array_map('strval', $dealIds)\n );\n $existingCrmIdSet = array_flip($existingCrmIds);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [\n 'teamId' => $this->team->getId(),\n 'dealCount' => count($dealIds),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n foreach ($deals as $deal) {\n try {\n $deal['associations'] = $this->prepareAssociationsForOpportunity(\n $deal['id'],\n $companyAssociations,\n $contactAssociations,\n $associationsData\n );\n\n $syncedOpportunity = $this->importOrUpdateOpportunity(\n $deal,\n isset($existingCrmIdSet[(string) $deal['id']])\n );\n if ($syncedOpportunity) {\n $syncedOpportunities['success'][] = $syncedOpportunity;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $deal['id'],\n 'error' => $e->getMessage(),\n ]);\n $syncedOpportunities['failed_ids'][] = $deal['id'];\n $syncedOpportunities['errors'][$deal['id']] = $e->getMessage();\n }\n }\n\n return $syncedOpportunities;\n }\n\n /**\n * Prepare associated entities for opportunities with optimized batch processing\n * Returns structured data with CRM ID to DB ID mappings for each opportunity\n */\n private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array\n {\n // Step 1: Collect all unique company and contact IDs from associations\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n\n // Step 2: Batch sync missing entities and get CRM ID to DB ID mappings\n $companyIdMappings = [];\n $contactIdMappings = [];\n\n if (! empty($allCompanyIds)) {\n $companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);\n }\n\n if (! empty($allContactIds)) {\n $contactIdMappings = $this->prepareAssociatedContacts($allContactIds);\n }\n\n return [\n 'company_id_mappings' => $companyIdMappings,\n 'contact_id_mappings' => $contactIdMappings,\n ];\n }\n\n /**\n * Flatten association data to get unique IDs\n */\n private function flattenAssociationIds(array $associations): array\n {\n $ids = [];\n foreach ($associations as $dealAssociations) {\n if (is_array($dealAssociations)) {\n foreach ($dealAssociations as $id) {\n $ids[$id] = true;\n }\n }\n }\n\n return array_keys($ids);\n }\n\n /**\n * Batch sync missing accounts\n */\n private function prepareAssociatedAccounts(array $companyIds): array\n {\n // Find which accounts already exist\n $existingAccounts = $this->crmEntityRepository\n ->findAccountsByExternalIds($this->config, $companyIds);\n\n $existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();\n\n $existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {\n return [$account->getCrmProviderId() => $account->getId()];\n })->toArray();\n\n $missingCompanyIds = array_diff($companyIds, $existingCompanyIds);\n\n if (empty($missingCompanyIds)) {\n return $existingAccountsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [\n 'teamId' => $this->team->getUuid(),\n 'total_companies' => count($companyIds),\n 'existing_companies' => count($existingCompanyIds),\n 'missing_companies' => count($missingCompanyIds),\n ]);\n\n // we already have limit on opportunity ids count\n // Initialize variable before try block\n $syncedAccountsData = [];\n\n try {\n $syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [\n 'size' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedAccountsData = [];\n }\n\n return $existingAccountsData + $syncedAccountsData;\n }\n\n /**\n * Prepare associated contacts - find existing and sync missing ones\n * Returns mapping of CRM ID to DB ID\n */\n private function prepareAssociatedContacts(array $contactIds): array\n {\n // Find which contacts already exist\n $existingContacts = $this->crmEntityRepository\n ->findContactsByExternalIds($this->config, $contactIds);\n\n $existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();\n\n // Create mapping for existing contacts\n $existingContactsData = $existingContacts->mapWithKeys(function ($contact) {\n return [$contact->getCrmProviderId() => $contact->getId()];\n })->toArray();\n\n $missingContactIds = array_diff($contactIds, $existingContactIds);\n\n if (empty($missingContactIds)) {\n return $existingContactsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [\n 'teamId' => $this->team->getUuid(),\n 'total_contacts' => count($contactIds),\n 'existing_contacts' => count($existingContactIds),\n 'missing_contacts' => count($missingContactIds),\n ]);\n\n // Sync missing contacts using batch API\n try {\n $syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [\n 'size' => count($missingContactIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedContactsData = [];\n }\n\n return $existingContactsData + $syncedContactsData;\n }\n\n private function batchSyncCrmObjects(string $objectType, array $crmIds): array\n {\n $syncObjects = [];\n $crmObjectIds = array_values($crmIds);\n\n foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {\n try {\n $objects = $objectType === 'companies' ?\n $this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :\n $this->client->getContactsByIds($chunk, $this->getContactFields());\n\n foreach ($objects as $objectId => $objectData) {\n $this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [\n 'requested_count' => count($chunk),\n 'synced_count' => count($objects),\n ]);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [\n 'ids' => $chunk,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncObjects;\n }\n\n private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void\n {\n try {\n $object = $objectType === 'companies' ?\n $this->importAccount($objectData) :\n $this->importContact($objectData);\n\n if ($object) {\n $syncObjects[$object->getCrmProviderId()] = $object->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [\n 'id' => $objectId,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n /**\n * Prepare associations for a single opportunity\n *\n * The return value is an array with the following structure:\n * [\n * 'companies' => [\n * $companyCrmId => $companyId,\n * ...\n * ],\n * 'contacts' => [\n * $contactCrmId => $contactId,\n * ...\n * ],\n * 'account_id' => $accountId,\n * ]\n */\n private function prepareAssociationsForOpportunity(\n string $oppCrmId,\n array $companyAssociations,\n array $contactAssociations,\n array $associationsData\n ): array {\n $associations = [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n\n $oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];\n foreach ($oppCompanyIds as $companyCrmId) {\n if (isset($associationsData['company_id_mappings'][$companyCrmId])) {\n $associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];\n\n // Set primary account (first company becomes primary account)\n if ($associations['account_id'] === null) {\n $associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];\n }\n }\n }\n\n $oppContactIds = $contactAssociations[$oppCrmId] ?? [];\n foreach ($oppContactIds as $contactCrmId) {\n if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {\n $associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];\n }\n }\n\n return $associations;\n }\n\n /**\n * Update only associations for an opportunity\n */\n private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void\n {\n // Update contact associations\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n // Update company (account) associations\n $this->updateOpportunityAccount($opportunity, $associations['account_id']);\n }\n\n /**\n * Remove all contact associations from an opportunity\n */\n private function removeAllOpportunityContacts(Opportunity $opportunity): void\n {\n $currentCount = (int) $opportunity->contacts()->count();\n\n if ($currentCount > 0) {\n $opportunity->contacts()->detach();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_count' => $currentCount,\n ]);\n }\n }\n\n private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void\n {\n if ($accountId === null) {\n // No account ID provided - keep current account\n return;\n }\n\n $currentAccountId = $opportunity->getAccountId();\n\n // Only update if account has changed\n if ($currentAccountId !== $accountId) {\n $opportunity->account_id = $accountId;\n $opportunity->save();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [\n 'opportunity_id' => $opportunity->getId(),\n 'old_account_id' => $currentAccountId,\n 'new_account_id' => $accountId,\n ]);\n }\n }\n\n /**\n * Find existing opportunities by external IDs (OPTIMIZED VERSION)\n * Uses batch query for better performance\n */\n private function findExistingOpportunities(array $crmIds): Collection\n {\n return $this->crmEntityRepository\n ->findOpportunitiesByExternalIds($this->config, $crmIds);\n }\n\n private function processOpportunityBatch(array $opportunities): int\n {\n $syncedOpportunities = $this->importOpportunityBatch($opportunities);\n\n return count($syncedOpportunities['success'] ?? []);\n }\n\n /**\n * Convert single deal associations from HubSpot format to internal format\n * Handles both HubSpot SDK objects and array formats\n *\n * @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed\n *\n * @return array Processed associations with DB IDs\n */\n private function convertDealAssociations(array $opportunityAssociations): array\n {\n $associations = $this->initializeAssociationsStructure();\n\n if (empty($opportunityAssociations)) {\n return $associations;\n }\n\n $associationIds = $this->extractAssociationIds($opportunityAssociations);\n\n $this->processCompanyAssociations($associationIds, $associations);\n $this->processContactAssociations($associationIds, $associations);\n\n return $associations;\n }\n\n private function initializeAssociationsStructure(): array\n {\n return [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n }\n\n private function extractAssociationIds(array $opportunityAssociations): array\n {\n $associationIds = [];\n\n foreach ($opportunityAssociations as $type => $associationData) {\n if (! empty($associationData)) {\n $associationIds[$type] = $this->convertSingleDealAssociations($associationData);\n }\n }\n\n return $associationIds;\n }\n\n private function processCompanyAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['companies'])) {\n return;\n }\n\n $companyId = $associationIds['companies'][0];\n $account = $this->findOrSyncAccount($companyId);\n\n if ($account instanceof Account) {\n $associations['companies'][$companyId] = $account->getId();\n $associations['account_id'] = $account->getId();\n }\n }\n\n private function processContactAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['contacts'])) {\n return;\n }\n\n foreach ($associationIds['contacts'] as $contactId) {\n $contact = $this->findOrSyncContact($contactId);\n\n if ($contact instanceof Contact) {\n $associations['contacts'][$contactId] = $contact->getId();\n }\n }\n }\n\n private function findOrSyncAccount(string $companyId): ?Account\n {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);\n\n if (! $account instanceof Account) {\n $account = $this->syncAccount($companyId);\n }\n\n return $account;\n }\n\n private function findOrSyncContact(string $contactId): ?Contact\n {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);\n\n if (! $contact instanceof Contact) {\n $contact = $this->syncContact($contactId);\n }\n\n return $contact;\n }\n\n private function convertSingleDealAssociations($opportunityAssociations = null): array\n {\n $associationData = [];\n\n if ($opportunityAssociations === null) {\n return $associationData;\n }\n\n // Handle array input (from extractAssociationIds)\n if (is_array($opportunityAssociations)) {\n return $opportunityAssociations;\n }\n\n // Handle CollectionResponseAssociatedId object\n if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {\n foreach ($opportunityAssociations->getResults() as $association) {\n $associationData[] = $association->getId();\n }\n }\n\n return $associationData;\n }\n\n private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity\n {\n if (empty($crmData['properties'])) {\n return null;\n }\n\n $crmId = (string) $crmData['id'];\n $properties = $crmData['properties'];\n $associations = $crmData['associations'] ?? [];\n\n $opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(\n $this->config,\n $crmId\n );\n\n if ($opportunityExists) {\n return $this->updateOpportunity($crmId, $properties, $associations);\n } else {\n return $this->createOpportunity($crmId, $properties, $associations);\n }\n }\n\n /**\n * Create new opportunity\n */\n private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n if (! $accountId) {\n return null;\n }\n\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n if (! $businessProcess) {\n return null;\n }\n\n $stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);\n if (! $stage) {\n return null;\n }\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n if ($opportunity->wasRecentlyCreated) {\n MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());\n }\n\n return $opportunity;\n }\n\n /**\n * Update existing opportunity\n */\n private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n $stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->updateOpportunityAssociations($opportunity, $associations);\n\n return $opportunity;\n }\n\n private function resolveAccountId(array $associations): ?int\n {\n if (! empty($associations['accountId'])) {\n return $associations['accountId'];\n }\n\n if (empty($associations)) {\n return null;\n }\n\n // we can't resolve multiple account ids (currently SDK returns one company)\n foreach ($associations['companies'] as $accountId) {\n return $accountId;\n }\n\n return null;\n }\n\n private function buildOpportunityData(\n array $properties,\n ?int $accountId,\n ?BusinessProcess $businessProcess,\n ?Stage $stage\n ): array {\n $ownerId = null;\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $name = 'Unknown';\n if (isset($properties['dealname'])) {\n $name = mb_strimwidth($properties['dealname'], 0, 128);\n }\n\n $amount = $this->resolveAmount($properties);\n $currency = $properties['deal_currency_code'] ?? null;\n\n $closeDate = null;\n if (! empty($properties['closedate'])) {\n $closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');\n }\n\n $remotelyCreatedAt = null;\n if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {\n $date = $this->parseCleanDatetime($properties['createdate']);\n $remotelyCreatedAt = $date?->format('Y-m-d H:i:s');\n }\n\n $closedStages = $this->getClosedDealStages();\n $isWon = in_array($properties['dealstage'], $closedStages['won']);\n $isLost = in_array($properties['dealstage'], $closedStages['lost']);\n\n $data = [\n 'team_id' => $this->team->getId(),\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'value' => ! empty($amount) ? $amount : null,\n 'currency_code' => CurrencyFormatter::formatCode($currency),\n 'close_date' => $closeDate,\n 'is_closed' => $isWon || $isLost,\n 'is_won' => $isWon,\n 'remotely_created_at' => $remotelyCreatedAt,\n 'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),\n 'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),\n ];\n\n if ($accountId) {\n $data['account_id'] = $accountId;\n }\n\n if ($stage) {\n $data['stage_id'] = $stage->id;\n }\n\n if ($businessProcess) {\n $recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);\n if ($recordType) {\n $data['record_type_id'] = $recordType->id;\n }\n }\n\n return $data;\n }\n\n private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess\n {\n if ($pipelineId === null) {\n return null;\n }\n\n if (isset($this->cachedBusinessProcesses[$pipelineId])) {\n return $this->cachedBusinessProcesses[$pipelineId];\n }\n\n $businessProcess = $this->getBusinessProcess($pipelineId);\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->importStages();\n $businessProcess = $this->getBusinessProcess($pipelineId);\n }\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->logger->info(\n '[HubSpot] Deal is not attached to a pipeline',\n [\n 'pipeline' => $pipelineId]\n );\n }\n\n $this->cachedBusinessProcesses[$pipelineId] = $businessProcess;\n\n return $businessProcess;\n }\n\n private function getBusinessProcess(string $pipelineId): ?BusinessProcess\n {\n return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);\n }\n\n private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage\n {\n if (empty($stageId)) {\n return null;\n }\n\n $cacheKey = $businessProcess->getId() . ':' . $stageId;\n if (isset($this->cachedStages[$cacheKey])) {\n return $this->cachedStages[$cacheKey];\n }\n\n $stage = $this->crmEntityRepository->getPipelineStageByConditions(\n $businessProcess,\n [\n 'crm_provider_id' => $stageId,\n 'type' => Stage::TYPE_OPPORTUNITY,\n ]\n );\n\n if ($stage === null) {\n $this->importStages(null, $stageId);\n }\n\n if ($stage === null) {\n $this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);\n }\n\n $this->cachedStages[$cacheKey] = $stage;\n\n return $stage;\n }\n\n private function resolveAmount(array $properties): ?string\n {\n $amount = null;\n if (! empty($properties['amount'])) {\n $amount = str_replace(',', '', $properties['amount']);\n }\n\n if ($this->config->hasDefaultCurrencyFieldSet()) {\n $valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();\n $amount = $properties[$valueFieldName] ?? $amount;\n }\n\n return $amount;\n }\n\n private function parseCleanDatetime(string $datetime): ?Carbon\n {\n // Treat pre-1980 values as invalid\n $minValidDate = Carbon::parse('1980-01-01 00:00:00');\n\n try {\n $date = Carbon::parse($datetime);\n\n if ($minValidDate->gt($date)) {\n return null;\n }\n\n return $date;\n } catch (Exception) {\n return null; // On parse error, treat as null\n }\n }\n\n private function resolveDealProbability(?string $stageProbability): int\n {\n if ($stageProbability === null) {\n return 0;\n }\n\n $probability = (float) $stageProbability;\n\n return $probability > 1 ? 0 : (int) ($probability * 100);\n }\n\n private function resolveForecastCategory(?string $forecastCategory): string\n {\n if (! $forecastCategory) {\n return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;\n }\n\n $forecastCategory = str_replace('_', ' ', $forecastCategory);\n\n return ucwords(strtolower($forecastCategory));\n }\n\n private function importExternalFieldData(array $properties, int $opportunityId): void\n {\n $crmFields = $this->getOpportunitySyncableFields();\n $this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);\n }\n\n private function importOpportunityContacts(Opportunity $opportunity, array $associations): void\n {\n // Handle empty or missing contact associations\n if (empty($associations)) {\n // Remove all existing contact associations if none provided\n $this->removeAllOpportunityContacts($opportunity);\n\n return;\n }\n\n // Use differential sync approach for better performance and accuracy\n $this->syncOpportunityContactsDifferential($opportunity, $associations);\n }\n\n /**\n * Sync opportunity contacts using differential approach\n * This compares current vs new associations and only makes necessary changes\n */\n private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void\n {\n $currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);\n $contactAssociationIds = array_keys($contactAssociations);\n\n $contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);\n $contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);\n\n if (empty($contactsToAdd) && empty($contactsToRemove)) {\n return;\n }\n\n $this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);\n\n $this->removeContactAssociations($opportunity, $contactsToRemove);\n $this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);\n }\n\n private function getCurrentContactCrmIds(Opportunity $opportunity): array\n {\n return $opportunity->contacts()\n ->pluck('contacts.crm_provider_id')\n ->toArray();\n }\n\n private function logContactAssociationChanges(\n Opportunity $opportunity,\n array $currentContactCrmIds,\n array $contactAssociations,\n array $contactsToAdd,\n array $contactsToRemove\n ): void {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [\n 'opportunity_id' => $opportunity->getId(),\n 'current_contacts' => $currentContactCrmIds,\n 'new_contacts' => $contactAssociations,\n 'contacts_to_add' => $contactsToAdd,\n 'contacts_to_remove' => $contactsToRemove,\n ]);\n }\n\n private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void\n {\n if (empty($contactsToRemove)) {\n return;\n }\n\n $contactsToDetach = $opportunity->contacts()\n ->whereIn('contacts.crm_provider_id', $contactsToRemove)\n ->pluck('contacts.id')\n ->toArray();\n\n if (! empty($contactsToDetach)) {\n $opportunity->contacts()->detach($contactsToDetach);\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_contact_crm_ids' => $contactsToRemove,\n 'removed_contact_count' => count($contactsToDetach),\n ]);\n }\n }\n\n private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void\n {\n if (empty($contactsToAdd)) {\n return;\n }\n\n $contactsAdded = [];\n foreach ($contactsToAdd as $crmId) {\n $id = $contactAssociations[$crmId];\n\n if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {\n $contactsAdded[] = $crmId;\n }\n }\n\n $this->logAddedContacts($opportunity, $contactsAdded);\n }\n\n private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);\n\n if (! $contact) {\n return false;\n }\n\n return $this->performContactAttachment($opportunity, $contact, $crmId);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [\n 'opportunity_id' => $opportunity->getId(),\n 'contact_crm_id' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool\n {\n try {\n $opportunity->contacts()->attach($contact->getId(), [\n 'crm_provider_id' => $crmId,\n ]);\n\n return true;\n } catch (\\Illuminate\\Database\\QueryException $e) {\n if (str_contains($e->getMessage(), 'Duplicate entry')) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [\n 'contact_id' => $contact->getId(),\n 'contact_crm_id' => $crmId,\n 'opportunity_id' => $opportunity->getId(),\n ]);\n\n return false;\n }\n\n throw $e;\n }\n }\n\n private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void\n {\n if (! empty($contactsAdded)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'contacts_to_add_count' => count($contactsAdded),\n 'added_contact_crm_ids' => $contactsAdded,\n 'added_contacts_count' => count($contactsAdded),\n ]);\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.02111111},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<template>\n <WelcomeLayout\n title=\"Account disconnected\"\n textPosition=\"center\"\n :icon=\"faUnlink\"\n :class=\"$style.layout\"\n >\n <div :class=\"$style.container\" v-if=\"providersLoaded\">\n <p>\n <strong>\n It looks like your {{ localProvider.displayName }} account has become\n disconnected\n </strong>\n </p>\n <p :class=\"$style.small\">Please re-connect to continue</p>\n <p v-if=\"isInIframe\">\n We'll open the {{ localProvider.displayName }} authentication in a new\n tab. Please return here and refresh the page once complete\n </p>\n\n <GoogleLikeButton\n v-if=\"localProvider.viaIntegrationApp && crmTokenLoaded\"\n as=\"a\"\n :key=\"localProvider.name\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n @click=\"integrationAppOnClick\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n <GoogleLikeButton\n v-if=\"!localProvider.viaIntegrationApp\"\n as=\"a\"\n :key=\"localProvider.name\"\n :href=\"`/auth/redirect/${localProvider.name}`\"\n :target=\"target\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n </div>\n <BuildInfo />\n\n <KioskBanner />\n </WelcomeLayout>\n</template>\n\n<script>\nimport window from \"window\";\nimport axios from \"axios\";\nimport { faUnlink } from \"@fortawesome/pro-regular-svg-icons\";\nimport isInIframe from \"@/utils/isInIframe\";\nimport BuildInfo from \"@/components/layout/BuildInfo/BuildInfo.vue\";\nimport KioskBanner from \"@/components/shared/KioskBanner/KioskBanner.vue\";\nimport WelcomeLayout from \"@/components/layout/WelcomeLayout/WelcomeLayout.vue\";\nimport GoogleLikeButton from \"@/components/shared/Buttons/GoogleLikeButton.vue\";\nimport { showSnackbarError, normalizeError } from \"@/utils/index\";\nimport { IntegrationAppClient } from \"@integration-app/sdk\";\n\nexport default {\n name: \"ConnectPage\",\n components: {\n BuildInfo,\n KioskBanner,\n WelcomeLayout,\n GoogleLikeButton,\n },\n data() {\n return {\n ...window.connectData,\n crmToken: null,\n faUnlink,\n isInIframe,\n providers: [],\n providersLoaded: false,\n crmTokenLoaded: false,\n };\n },\n computed: {\n localProvider() {\n return this.providers.find((e) => e.name === this.provider);\n },\n target() {\n return this.isInIframe ? \"_blank\" : null;\n },\n },\n created() {\n this.getProviders();\n },\n mounted() {\n this.showErrors();\n },\n watch: {\n providersLoaded() {\n if (this.providersLoaded) {\n this.prepareIntegrationAppConnection();\n }\n },\n },\n methods: {\n showErrors() {\n if (!this.error) return;\n\n showSnackbarError(this.error, undefined, undefined, false);\n },\n unwrapEntityResponse({ data }) {\n return data.map(({ icon, name, displayName, viaIntegrationApp }) => {\n return { icon, name, displayName, viaIntegrationApp };\n });\n },\n async getProviders() {\n try {\n const response = await axios.get(\"/api/v1/connect-providers\");\n this.providers = this.unwrapEntityResponse(response);\n this.providersLoaded = true;\n } catch {\n showSnackbarError(\n \"An error occurred, while loading form data (connect providers).\",\n );\n }\n },\n async prepareIntegrationAppConnection() {\n if (this.localProvider.viaIntegrationApp) {\n try {\n const response = await axios.get(\"/api/v1/integration-app-token\");\n this.crmToken = response.data.token;\n this.crmTokenLoaded = true;\n } catch (error) {\n console.log(error);\n showSnackbarError(\n `An error occurred while preparing the page.\n Try refreshing, if the error persists get in touch with the Jiminny team.`,\n );\n }\n }\n },\n async integrationAppOnClick() {\n const integrationApp = new IntegrationAppClient({\n token: this.crmToken,\n });\n\n const connection = await integrationApp\n .integration(this.localProvider.name)\n .openNewConnection({\n showPoweredBy: false,\n allowMultipleConnections: false,\n });\n\n console.log('[IntegrationApp] openNewConnection resolved:', JSON.stringify(connection));\n\n // [IntegrationApp] openNewConnection resolved: {\n // \"id\":\"69e0b41a67d0068c2ca0b48e\",\n // \"name\":\"Zoho CRM\",\n // \"userId\":\"1ece66c8-feb1-4df1-b321-21607daf4623\",\n // \"tenantId\":\"69e0b3faef3e7b6248189289\",\n // \"isTest\":false,\n // \"connected\":true,\n // \"state\":\"READY\",\n // \"errors\":[],\n // \"integrationId\":\"66fe6c913202f3a165e3c14d\",\n // \"externalAppId\":\"6671653e7e2d642e4e41b0fa\",\n // \"authOptionKey\":\"\",\n // \"createdAt\":\"2026-04-16T10:04:10.420Z\",\n // \"updatedAt\":\"2026-04-16T10:04:10.575Z\",\n // \"retryAttempts\":0,\n // \"isDeactivated\":false\n // }\n\n if (connection && connection.disconnected !== true && connection.connected !== false) {\n console.log('[IntegrationApp] connection condition matched');\n try {\n const saveRequest = await axios.post(\n \"/api/v1/integration-app-connect\",\n );\n if (saveRequest.data && saveRequest.data.success === true) {\n /** If all is good refresh the page here */\n window.location = \"/dashboard\";\n return;\n }\n\n throw new Error(saveRequest.data.message);\n } catch (error) {\n console.log(error);\n showSnackbarError(normalizeError(error));\n }\n }\n },\n },\n};\n</script>\n\n<style module lang=\"less\" src=\"./connect.less\"></style>","depth":4,"value":"<template>\n <WelcomeLayout\n title=\"Account disconnected\"\n textPosition=\"center\"\n :icon=\"faUnlink\"\n :class=\"$style.layout\"\n >\n <div :class=\"$style.container\" v-if=\"providersLoaded\">\n <p>\n <strong>\n It looks like your {{ localProvider.displayName }} account has become\n disconnected\n </strong>\n </p>\n <p :class=\"$style.small\">Please re-connect to continue</p>\n <p v-if=\"isInIframe\">\n We'll open the {{ localProvider.displayName }} authentication in a new\n tab. Please return here and refresh the page once complete\n </p>\n\n <GoogleLikeButton\n v-if=\"localProvider.viaIntegrationApp && crmTokenLoaded\"\n as=\"a\"\n :key=\"localProvider.name\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n @click=\"integrationAppOnClick\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n <GoogleLikeButton\n v-if=\"!localProvider.viaIntegrationApp\"\n as=\"a\"\n :key=\"localProvider.name\"\n :href=\"`/auth/redirect/${localProvider.name}`\"\n :target=\"target\"\n :brand-logo=\"localProvider.name\"\n :class=\"$style.connectButton\"\n >\n Sign in with {{ localProvider.displayName }}\n </GoogleLikeButton>\n </div>\n <BuildInfo />\n\n <KioskBanner />\n </WelcomeLayout>\n</template>\n\n<script>\nimport window from \"window\";\nimport axios from \"axios\";\nimport { faUnlink } from \"@fortawesome/pro-regular-svg-icons\";\nimport isInIframe from \"@/utils/isInIframe\";\nimport BuildInfo from \"@/components/layout/BuildInfo/BuildInfo.vue\";\nimport KioskBanner from \"@/components/shared/KioskBanner/KioskBanner.vue\";\nimport WelcomeLayout from \"@/components/layout/WelcomeLayout/WelcomeLayout.vue\";\nimport GoogleLikeButton from \"@/components/shared/Buttons/GoogleLikeButton.vue\";\nimport { showSnackbarError, normalizeError } from \"@/utils/index\";\nimport { IntegrationAppClient } from \"@integration-app/sdk\";\n\nexport default {\n name: \"ConnectPage\",\n components: {\n BuildInfo,\n KioskBanner,\n WelcomeLayout,\n GoogleLikeButton,\n },\n data() {\n return {\n ...window.connectData,\n crmToken: null,\n faUnlink,\n isInIframe,\n providers: [],\n providersLoaded: false,\n crmTokenLoaded: false,\n };\n },\n computed: {\n localProvider() {\n return this.providers.find((e) => e.name === this.provider);\n },\n target() {\n return this.isInIframe ? \"_blank\" : null;\n },\n },\n created() {\n this.getProviders();\n },\n mounted() {\n this.showErrors();\n },\n watch: {\n providersLoaded() {\n if (this.providersLoaded) {\n this.prepareIntegrationAppConnection();\n }\n },\n },\n methods: {\n showErrors() {\n if (!this.error) return;\n\n showSnackbarError(this.error, undefined, undefined, false);\n },\n unwrapEntityResponse({ data }) {\n return data.map(({ icon, name, displayName, viaIntegrationApp }) => {\n return { icon, name, displayName, viaIntegrationApp };\n });\n },\n async getProviders() {\n try {\n const response = await axios.get(\"/api/v1/connect-providers\");\n this.providers = this.unwrapEntityResponse(response);\n this.providersLoaded = true;\n } catch {\n showSnackbarError(\n \"An error occurred, while loading form data (connect providers).\",\n );\n }\n },\n async prepareIntegrationAppConnection() {\n if (this.localProvider.viaIntegrationApp) {\n try {\n const response = await axios.get(\"/api/v1/integration-app-token\");\n this.crmToken = response.data.token;\n this.crmTokenLoaded = true;\n } catch (error) {\n console.log(error);\n showSnackbarError(\n `An error occurred while preparing the page.\n Try refreshing, if the error persists get in touch with the Jiminny team.`,\n );\n }\n }\n },\n async integrationAppOnClick() {\n const integrationApp = new IntegrationAppClient({\n token: this.crmToken,\n });\n\n const connection = await integrationApp\n .integration(this.localProvider.name)\n .openNewConnection({\n showPoweredBy: false,\n allowMultipleConnections: false,\n });\n\n console.log('[IntegrationApp] openNewConnection resolved:', JSON.stringify(connection));\n\n // [IntegrationApp] openNewConnection resolved: {\n // \"id\":\"69e0b41a67d0068c2ca0b48e\",\n // \"name\":\"Zoho CRM\",\n // \"userId\":\"1ece66c8-feb1-4df1-b321-21607daf4623\",\n // \"tenantId\":\"69e0b3faef3e7b6248189289\",\n // \"isTest\":false,\n // \"connected\":true,\n // \"state\":\"READY\",\n // \"errors\":[],\n // \"integrationId\":\"66fe6c913202f3a165e3c14d\",\n // \"externalAppId\":\"6671653e7e2d642e4e41b0fa\",\n // \"authOptionKey\":\"\",\n // \"createdAt\":\"2026-04-16T10:04:10.420Z\",\n // \"updatedAt\":\"2026-04-16T10:04:10.575Z\",\n // \"retryAttempts\":0,\n // \"isDeactivated\":false\n // }\n\n if (connection && connection.disconnected !== true && connection.connected !== false) {\n console.log('[IntegrationApp] connection condition matched');\n try {\n const saveRequest = await axios.post(\n \"/api/v1/integration-app-connect\",\n );\n if (saveRequest.data && saveRequest.data.success === true) {\n /** If all is good refresh the page here */\n window.location = \"/dashboard\";\n return;\n }\n\n throw new Error(saveRequest.data.message);\n } catch (error) {\n console.log(error);\n showSnackbarError(normalizeError(error));\n }\n }\n },\n },\n};\n</script>\n\n<style module lang=\"less\" src=\"./connect.less\"></style>","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
6089929419086115132
|
-8178086448081891036
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20692-fix-integration- Project: faVsco.js, menu
JY-20692-fix-integration-app-[API_KEY], menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
cachedStages
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
2/4
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Code changed:
Hide
Sync Changes
Hide This Notification
33
2
19
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use HubSpot\Client\Crm\Deals\Model\CollectionResponseAssociatedId;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Models\Account;
use Exception;
use Jiminny\Component\DealInsights\Forecast\Forecast;
use Jiminny\Jobs\Crm\MatchActivitiesToNewOpportunity;
use Jiminny\Models\Contact;
use Jiminny\Models\Crm\BusinessProcess;
use Jiminny\Exceptions\CrmException;
use Jiminny\Models\Opportunity;
use Illuminate\Support\Collection;
use Jiminny\Models\Stage;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Crm\Hubspot\DealFieldsService;
use Jiminny\Services\Crm\Hubspot\OpportunitySyncStrategy\HubspotSingleSyncStrategy;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Services\Crm\OpportunitySyncStrategyResolver;
use Jiminny\Utils\CurrencyFormatter;
/**
* Optimized sync methods for better performance
* These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains
*/
trait OpportunitySyncTrait
{
private const int BATCH_SIZE = 100;
private const int BATCH_PROCESS_SIZE = 800;
protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;
protected CrmEntityRepository $crmEntityRepository;
protected DealFieldsService $dealFieldsService;
private ?array $cachedClosedDealStages = null;
private array $cachedBusinessProcesses = [];
private array $cachedStages = [];
public function syncOpportunities(array $parameters, ?string $strategy = null): int
{
$strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);
$parameters['config'] = $this->config;
$syncCount = 0;
$reportedTotal = 0;
$lastSyncedId = [];
try {
foreach ($strategies as $strategyName => $syncStrategy) {
$this->logger->info(
'[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .
$strategyName
);
$total = 0;
$lastId = null;
$buffer = [];
// HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies
foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {
$buffer[] = $hsOpportunity;
// process every 800 rows (fits < 1 000 association limit)
if (\count($buffer) >= self::BATCH_PROCESS_SIZE) {
$syncCount += $this->processOpportunityBatch($buffer);
$buffer = [];
}
}
// leftovers
if ($buffer) {
$syncCount += $this->processOpportunityBatch($buffer);
}
$reportedTotal += $total;
$lastSyncedId = $lastId;
}
} catch (\HubSpot\Client\Crm\Deals\ApiException | CrmException $e) {
$this->handleSyncException($e, $parameters);
}
$this->logger->info(
'[HubSpot] Synced opportunities',
[
'team' => $this->team->getId(),
'sync_count' => $syncCount,
'total' => $reportedTotal,
'last_synced_id' => $lastSyncedId,
]
);
return $reportedTotal;
}
private function handleSyncException(\Throwable $e, array $parameters): void
{
if (($parameters['since'] ?? null) instanceof Carbon) {
$parameters['since'] = $parameters['since']->toDateTimeString();
}
$parameters['config'] = $this->config->getId();
$this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [
'teamId' => $this->team->getUuid(),
'parameters' => $parameters,
'reason' => $e->getMessage(),
]);
}
/**
* @inheritdoc
*/
public function syncOpportunity(string $crmId): ?Opportunity
{
$strategy = $this->opportunitySyncStrategyResolver->resolve(
$this->config,
OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,
);
$parameters = [
'config' => $this->config,
'crm_id' => $crmId,
];
try {
if (! $strategy instanceof HubspotSingleSyncStrategy) {
throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');
}
$hsOpportunity = $strategy->fetchOpportunity($parameters);
} catch (\HubSpot\Client\Crm\Deals\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
$hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);
return $this->importOrUpdateOpportunity($hsOpportunity);
}
/**
* Process webhook-collected opportunity batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportOpportunityBatch jobs for batch processing.
*
* @return int Number of opportunity IDs dispatched to jobs
*/
public function batchSyncOpportunities(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,
$configId
);
}
/**
* Import a batch of opportunities by their CRM IDs.
* Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().
*
* @param array<string> $crmIds HubSpot deal CRM IDs
*
* @return array{success: array, failed_ids: array, errors?: array<string, string>}
*/
public function importOpportunityBatchByIds(array $crmIds): array
{
$fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);
$allDeals = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
$deals = $this->client->getOpportunitiesByIds($chunk, $fields);
foreach ($deals as $deal) {
$allDeals[] = $deal;
}
}
// IDs not returned by HubSpot are likely deleted or inaccessible deals.
// These are not failures — retrying won't bring them back.
$fetchedIds = array_map('strval', array_column($allDeals, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allDeals),
]);
}
if (empty($allDeals)) {
return ['success' => [], 'failed_ids' => []];
}
return $this->importOpportunityBatch($allDeals);
}
private function getClosedDealStages(): array
{
if ($this->cachedClosedDealStages !== null) {
return $this->cachedClosedDealStages;
}
$stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);
$data = [
'lost' => [],
'won' => [],
];
foreach ($stages as $stage) {
if ($stage->probability == 0.00) {
$data['lost'][] = $stage->crm_provider_id;
}
if ($stage->probability == 100.00) {
$data['won'][] = $stage->crm_provider_id;
}
}
$this->cachedClosedDealStages = $data;
return $data;
}
/**
* Import deals into the database with pre-fetched associations.
*
* API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT
* caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()
* where Laravel retries the whole job with backoff. After all retries exhausted,
* failed() requeues all IDs to Redis.
*
* The per-deal loop catches exceptions individually. A deal can end up in three states:
* - success: imported/updated successfully
* - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)
* These are permanent issues — retrying won't fix them.
* - skipped (null): missing dependencies (no account, unknown pipeline/stage).
* This is acceptable — the deal cannot be imported until those exist.
*/
private function importOpportunityBatch(array $deals): array
{
$syncedOpportunities = [
'success' => [],
'failed_ids' => [],
];
$dealIds = array_column($deals, 'id');
// Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the
// queue job retries the whole batch and eventually requeues all deal IDs back to Redis.
try {
$companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');
$contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');
$associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);
$existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(
$this->config,
array_map('strval', $dealIds)
);
$existingCrmIdSet = array_flip($existingCrmIds);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [
'teamId' => $this->team->getId(),
'dealCount' => count($dealIds),
'error' => $e->getMessage(),
]);
throw $e;
}
foreach ($deals as $deal) {
try {
$deal['associations'] = $this->prepareAssociationsForOpportunity(
$deal['id'],
$companyAssociations,
$contactAssociations,
$associationsData
);
$syncedOpportunity = $this->importOrUpdateOpportunity(
$deal,
isset($existingCrmIdSet[(string) $deal['id']])
);
if ($syncedOpportunity) {
$syncedOpportunities['success'][] = $syncedOpportunity;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [
'teamId' => $this->team->getId(),
'crmId' => $deal['id'],
'error' => $e->getMessage(),
]);
$syncedOpportunities['failed_ids'][] = $deal['id'];
$syncedOpportunities['errors'][$deal['id']] = $e->getMessage();
}
}
return $syncedOpportunities;
}
/**
* Prepare associated entities for opportunities with optimized batch processing
* Returns structured data with CRM ID to DB ID mappings for each opportunity
*/
private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array
{
// Step 1: Collect all unique company and contact IDs from associations
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
// Step 2: Batch sync missing entities and get CRM ID to DB ID mappings
$companyIdMappings = [];
$contactIdMappings = [];
if (! empty($allCompanyIds)) {
$companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);
}
if (! empty($allContactIds)) {
$contactIdMappings = $this->prepareAssociatedContacts($allContactIds);
}
return [
'company_id_mappings' => $companyIdMappings,
'contact_id_mappings' => $contactIdMappings,
];
}
/**
* Flatten association data to get unique IDs
*/
private function flattenAssociationIds(array $associations): array
{
$ids = [];
foreach ($associations as $dealAssociations) {
if (is_array($dealAssociations)) {
foreach ($dealAssociations as $id) {
$ids[$id] = true;
}
}
}
return array_keys($ids);
}
/**
* Batch sync missing accounts
*/
private function prepareAssociatedAccounts(array $companyIds): array
{
// Find which accounts already exist
$existingAccounts = $this->crmEntityRepository
->findAccountsByExternalIds($this->config, $companyIds);
$existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();
$existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {
return [$account->getCrmProviderId() => $account->getId()];
})->toArray();
$missingCompanyIds = array_diff($companyIds, $existingCompanyIds);
if (empty($missingCompanyIds)) {
return $existingAccountsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [
'teamId' => $this->team->getUuid(),
'total_companies' => count($companyIds),
'existing_companies' => count($existingCompanyIds),
'missing_companies' => count($missingCompanyIds),
]);
// we already have limit on opportunity ids count
// Initialize variable before try block
$syncedAccountsData = [];
try {
$syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [
'size' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
$syncedAccountsData = [];
}
return $existingAccountsData + $syncedAccountsData;
}
/**
* Prepare associated contacts - find existing and sync missing ones
* Returns mapping of CRM ID to DB ID
*/
private function prepareAssociatedContacts(array $contactIds): array
{
// Find which contacts already exist
$existingContacts = $this->crmEntityRepository
->findContactsByExternalIds($this->config, $contactIds);
$existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();
// Create mapping for existing contacts
$existingContactsData = $existingContacts->mapWithKeys(function ($contact) {
return [$contact->getCrmProviderId() => $contact->getId()];
})->toArray();
$missingContactIds = array_diff($contactIds, $existingContactIds);
if (empty($missingContactIds)) {
return $existingContactsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [
'teamId' => $this->team->getUuid(),
'total_contacts' => count($contactIds),
'existing_contacts' => count($existingContactIds),
'missing_contacts' => count($missingContactIds),
]);
// Sync missing contacts using batch API
try {
$syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [
'size' => count($missingContactIds),
'error' => $e->getMessage(),
]);
$syncedContactsData = [];
}
return $existingContactsData + $syncedContactsData;
}
private function batchSyncCrmObjects(string $objectType, array $crmIds): array
{
$syncObjects = [];
$crmObjectIds = array_values($crmIds);
foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {
try {
$objects = $objectType === 'companies' ?
$this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :
$this->client->getContactsByIds($chunk, $this->getContactFields());
foreach ($objects as $objectId => $objectData) {
$this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [
'requested_count' => count($chunk),
'synced_count' => count($objects),
]);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [
'ids' => $chunk,
'error' => $e->getMessage(),
]);
}
}
return $syncObjects;
}
private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void
{
try {
$object = $objectType === 'companies' ?
$this->importAccount($objectData) :
$this->importContact($objectData);
if ($object) {
$syncObjects[$object->getCrmProviderId()] = $object->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [
'id' => $objectId,
'error' => $e->getMessage(),
]);
}
}
/**
* Prepare associations for a single opportunity
*
* The return value is an array with the following structure:
* [
* 'companies' => [
* $companyCrmId => $companyId,
* ...
* ],
* 'contacts' => [
* $contactCrmId => $contactId,
* ...
* ],
* 'account_id' => $accountId,
* ]
*/
private function prepareAssociationsForOpportunity(
string $oppCrmId,
array $companyAssociations,
array $contactAssociations,
array $associationsData
): array {
$associations = [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
$oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];
foreach ($oppCompanyIds as $companyCrmId) {
if (isset($associationsData['company_id_mappings'][$companyCrmId])) {
$associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];
// Set primary account (first company becomes primary account)
if ($associations['account_id'] === null) {
$associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];
}
}
}
$oppContactIds = $contactAssociations[$oppCrmId] ?? [];
foreach ($oppContactIds as $contactCrmId) {
if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {
$associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];
}
}
return $associations;
}
/**
* Update only associations for an opportunity
*/
private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void
{
// Update contact associations
$this->importOpportunityContacts($opportunity, $associations['contacts']);
// Update company (account) associations
$this->updateOpportunityAccount($opportunity, $associations['account_id']);
}
/**
* Remove all contact associations from an opportunity
*/
private function removeAllOpportunityContacts(Opportunity $opportunity): void
{
$currentCount = (int) $opportunity->contacts()->count();
if ($currentCount > 0) {
$opportunity->contacts()->detach();
$this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_count' => $currentCount,
]);
}
}
private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void
{
if ($accountId === null) {
// No account ID provided - keep current account
return;
}
$currentAccountId = $opportunity->getAccountId();
// Only update if account has changed
if ($currentAccountId !== $accountId) {
$opportunity->account_id = $accountId;
$opportunity->save();
$this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [
'opportunity_id' => $opportunity->getId(),
'old_account_id' => $currentAccountId,
'new_account_id' => $accountId,
]);
}
}
/**
* Find existing opportunities by external IDs (OPTIMIZED VERSION)
* Uses batch query for better performance
*/
private function findExistingOpportunities(array $crmIds): Collection
{
return $this->crmEntityRepository
->findOpportunitiesByExternalIds($this->config, $crmIds);
}
private function processOpportunityBatch(array $opportunities): int
{
$syncedOpportunities = $this->importOpportunityBatch($opportunities);
return count($syncedOpportunities['success'] ?? []);
}
/**
* Convert single deal associations from HubSpot format to internal format
* Handles both HubSpot SDK objects and array formats
*
* @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed
*
* @return array Processed associations with DB IDs
*/
private function convertDealAssociations(array $opportunityAssociations): array
{
$associations = $this->initializeAssociationsStructure();
if (empty($opportunityAssociations)) {
return $associations;
}
$associationIds = $this->extractAssociationIds($opportunityAssociations);
$this->processCompanyAssociations($associationIds, $associations);
$this->processContactAssociations($associationIds, $associations);
return $associations;
}
private function initializeAssociationsStructure(): array
{
return [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
}
private function extractAssociationIds(array $opportunityAssociations): array
{
$associationIds = [];
foreach ($opportunityAssociations as $type => $associationData) {
if (! empty($associationData)) {
$associationIds[$type] = $this->convertSingleDealAssociations($associationData);
}
}
return $associationIds;
}
private function processCompanyAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['companies'])) {
return;
}
$companyId = $associationIds['companies'][0];
$account = $this->findOrSyncAccount($companyId);
if ($account instanceof Account) {
$associations['companies'][$companyId] = $account->getId();
$associations['account_id'] = $account->getId();
}
}
private function processContactAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['contacts'])) {
return;
}
foreach ($associationIds['contacts'] as $contactId) {
$contact = $this->findOrSyncContact($contactId);
if ($contact instanceof Contact) {
$associations['contacts'][$contactId] = $contact->getId();
}
}
}
private function findOrSyncAccount(string $companyId): ?Account
{
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);
if (! $account instanceof Account) {
$account = $this->syncAccount($companyId);
}
return $account;
}
private function findOrSyncContact(string $contactId): ?Contact
{
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);
if (! $contact instanceof Contact) {
$contact = $this->syncContact($contactId);
}
return $contact;
}
private function convertSingleDealAssociations($opportunityAssociations = null): array
{
$associationData = [];
if ($opportunityAssociations === null) {
return $associationData;
}
// Handle array input (from extractAssociationIds)
if (is_array($opportunityAssociations)) {
return $opportunityAssociations;
}
// Handle CollectionResponseAssociatedId object
if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {
foreach ($opportunityAssociations->getResults() as $association) {
$associationData[] = $association->getId();
}
}
return $associationData;
}
private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity
{
if (empty($crmData['properties'])) {
return null;
}
$crmId = (string) $crmData['id'];
$properties = $crmData['properties'];
$associations = $crmData['associations'] ?? [];
$opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(
$this->config,
$crmId
);
if ($opportunityExists) {
return $this->updateOpportunity($crmId, $properties, $associations);
} else {
return $this->createOpportunity($crmId, $properties, $associations);
}
}
/**
* Create new opportunity
*/
private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity
{
$accountId = $this->resolveAccountId($associations);
if (! $accountId) {
return null;
}
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
if (! $businessProcess) {
return null;
}
$stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);
if (! $stage) {
return null;
}
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->importOpportunityContacts($opportunity, $associations['contacts']);
if ($opportunity->wasRecentlyCreated) {
MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());
}
return $opportunity;
}
/**
* Update existing opportunity
*/
private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity
{
$accountId = $this->resolveAccountId($associations);
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
$stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->updateOpportunityAssociations($opportunity, $associations);
return $opportunity;
}
private function resolveAccountId(array $associations): ?int
{
if (! empty($associations['accountId'])) {
return $associations['accountId'];
}
if (empty($associations)) {
return null;
}
// we can't resolve multiple account ids (currently SDK returns one company)
foreach ($associations['companies'] as $accountId) {
return $accountId;
}
return null;
}
private function buildOpportunityData(
array $properties,
?int $accountId,
?BusinessProcess $businessProcess,
?Stage $stage
): array {
$ownerId = null;
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$name = 'Unknown';
if (isset($properties['dealname'])) {
$name = mb_strimwidth($properties['dealname'], 0, 128);
}
$amount = $this->resolveAmount($properties);
$currency = $properties['deal_currency_code'] ?? null;
$closeDate = null;
if (! empty($properties['closedate'])) {
$closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');
}
$remotelyCreatedAt = null;
if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {
$date = $this->parseCleanDatetime($properties['createdate']);
$remotelyCreatedAt = $date?->format('Y-m-d H:i:s');
}
$closedStages = $this->getClosedDealStages();
$isWon = in_array($properties['dealstage'], $closedStages['won']);
$isLost = in_array($properties['dealstage'], $closedStages['lost']);
$data = [
'team_id' => $this->team->getId(),
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => $name,
'value' => ! empty($amount) ? $amount : null,
'currency_code' => CurrencyFormatter::formatCode($currency),
'close_date' => $closeDate,
'is_closed' => $isWon || $isLost,
'is_won' => $isWon,
'remotely_created_at' => $remotelyCreatedAt,
'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),
'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),
];
if ($accountId) {
$data['account_id'] = $accountId;
}
if ($stage) {
$data['stage_id'] = $stage->id;
}
if ($businessProcess) {
$recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);
if ($recordType) {
$data['record_type_id'] = $recordType->id;
}
}
return $data;
}
private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess
{
if ($pipelineId === null) {
return null;
}
if (isset($this->cachedBusinessProcesses[$pipelineId])) {
return $this->cachedBusinessProcesses[$pipelineId];
}
$businessProcess = $this->getBusinessProcess($pipelineId);
if (! $businessProcess instanceof BusinessProcess) {
$this->importStages();
$businessProcess = $this->getBusinessProcess($pipelineId);
}
if (! $businessProcess instanceof BusinessProcess) {
$this->logger->info(
'[HubSpot] Deal is not attached to a pipeline',
[
'pipeline' => $pipelineId]
);
}
$this->cachedBusinessProcesses[$pipelineId] = $businessProcess;
return $businessProcess;
}
private function getBusinessProcess(string $pipelineId): ?BusinessProcess
{
return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);
}
private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage
{
if (empty($stageId)) {
return null;
}
$cacheKey = $businessProcess->getId() . ':' . $stageId;
if (isset($this->cachedStages[$cacheKey])) {
return $this->cachedStages[$cacheKey];
}
$stage = $this->crmEntityRepository->getPipelineStageByConditions(
$businessProcess,
[
'crm_provider_id' => $stageId,
'type' => Stage::TYPE_OPPORTUNITY,
]
);
if ($stage === null) {
$this->importStages(null, $stageId);
}
if ($stage === null) {
$this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);
}
$this->cachedStages[$cacheKey] = $stage;
return $stage;
}
private function resolveAmount(array $properties): ?string
{
$amount = null;
if (! empty($properties['amount'])) {
$amount = str_replace(',', '', $properties['amount']);
}
if ($this->config->hasDefaultCurrencyFieldSet()) {
$valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();
$amount = $properties[$valueFieldName] ?? $amount;
}
return $amount;
}
private function parseCleanDatetime(string $datetime): ?Carbon
{
// Treat pre-1980 values as invalid
$minValidDate = Carbon::parse('1980-01-01 00:00:00');
try {
$date = Carbon::parse($datetime);
if ($minValidDate->gt($date)) {
return null;
}
return $date;
} catch (Exception) {
return null; // On parse error, treat as null
}
}
private function resolveDealProbability(?string $stageProbability): int
{
if ($stageProbability === null) {
return 0;
}
$probability = (float) $stageProbability;
return $probability > 1 ? 0 : (int) ($probability * 100);
}
private function resolveForecastCategory(?string $forecastCategory): string
{
if (! $forecastCategory) {
return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;
}
$forecastCategory = str_replace('_', ' ', $forecastCategory);
return ucwords(strtolower($forecastCategory));
}
private function importExternalFieldData(array $properties, int $opportunityId): void
{
$crmFields = $this->getOpportunitySyncableFields();
$this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);
}
private function importOpportunityContacts(Opportunity $opportunity, array $associations): void
{
// Handle empty or missing contact associations
if (empty($associations)) {
// Remove all existing contact associations if none provided
$this->removeAllOpportunityContacts($opportunity);
return;
}
// Use differential sync approach for better performance and accuracy
$this->syncOpportunityContactsDifferential($opportunity, $associations);
}
/**
* Sync opportunity contacts using differential approach
* This compares current vs new associations and only makes necessary changes
*/
private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void
{
$currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);
$contactAssociationIds = array_keys($contactAssociations);
$contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);
$contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);
if (empty($contactsToAdd) && empty($contactsToRemove)) {
return;
}
$this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);
$this->removeContactAssociations($opportunity, $contactsToRemove);
$this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);
}
private function getCurrentContactCrmIds(Opportunity $opportunity): array
{
return $opportunity->contacts()
->pluck('contacts.crm_provider_id')
->toArray();
}
private function logContactAssociationChanges(
Opportunity $opportunity,
array $currentContactCrmIds,
array $contactAssociations,
array $contactsToAdd,
array $contactsToRemove
): void {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [
'opportunity_id' => $opportunity->getId(),
'current_contacts' => $currentContactCrmIds,
'new_contacts' => $contactAssociations,
'contacts_to_add' => $contactsToAdd,
'contacts_to_remove' => $contactsToRemove,
]);
}
private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void
{
if (empty($contactsToRemove)) {
return;
}
$contactsToDetach = $opportunity->contacts()
->whereIn('contacts.crm_provider_id', $contactsToRemove)
->pluck('contacts.id')
->toArray();
if (! empty($contactsToDetach)) {
$opportunity->contacts()->detach($contactsToDetach);
$this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_contact_crm_ids' => $contactsToRemove,
'removed_contact_count' => count($contactsToDetach),
]);
}
}
private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void
{
if (empty($contactsToAdd)) {
return;
}
$contactsAdded = [];
foreach ($contactsToAdd as $crmId) {
$id = $contactAssociations[$crmId];
if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {
$contactsAdded[] = $crmId;
}
}
$this->logAddedContacts($opportunity, $contactsAdded);
}
private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool
{
try {
$contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);
if (! $contact) {
return false;
}
return $this->performContactAttachment($opportunity, $contact, $crmId);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [
'opportunity_id' => $opportunity->getId(),
'contact_crm_id' => $crmId,
'error' => $e->getMessage(),
]);
return false;
}
}
private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool
{
try {
$opportunity->contacts()->attach($contact->getId(), [
'crm_provider_id' => $crmId,
]);
return true;
} catch (\Illuminate\Database\QueryException $e) {
if (str_contains($e->getMessage(), 'Duplicate entry')) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [
'contact_id' => $contact->getId(),
'contact_crm_id' => $crmId,
'opportunity_id' => $opportunity->getId(),
]);
return false;
}
throw $e;
}
}
private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void
{
if (! empty($contactsAdded)) {
$this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [
'opportunity_id' => $opportunity->getId(),
'contacts_to_add_count' => count($contactsAdded),
'added_contact_crm_ids' => $contactsAdded,
'added_contacts_count' => count($contactsAdded),
]);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
1
Previous Highlighted Error
Next Highlighted Error
<template>
<WelcomeLayout
title="Account disconnected"
textPosition="center"
:icon="faUnlink"
:class="$style.layout"
>
<div :class="$style.container" v-if="providersLoaded">
<p>
<strong>
It looks like your {{ localProvider.displayName }} account has become
disconnected
</strong>
</p>
<p :class="$style.small">Please re-connect to continue</p>
<p v-if="isInIframe">
We'll open the {{ localProvider.displayName }} authentication in a new
tab. Please return here and refresh the page once complete
</p>
<GoogleLikeButton
v-if="localProvider.viaIntegrationApp && crmTokenLoaded"
as="a"
:key="localProvider.name"
:brand-logo="localProvider.name"
:class="$style.connectButton"
@click="integrationAppOnClick"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
<GoogleLikeButton
v-if="!localProvider.viaIntegrationApp"
as="a"
:key="localProvider.name"
:href="`/auth/redirect/${localProvider.name}`"
:target="target"
:brand-logo="localProvider.name"
:class="$style.connectButton"
>
Sign in with {{ localProvider.displayName }}
</GoogleLikeButton>
</div>
<BuildInfo />
<KioskBanner />
</WelcomeLayout>
</template>
<script>
import window from "window";
import axios from "axios";
import { faUnlink } from "@fortawesome/pro-regular-svg-icons";
import isInIframe from "@/utils/isInIframe";
import BuildInfo from "@/components/layout/BuildInfo/BuildInfo.vue";
import KioskBanner from "@/components/shared/KioskBanner/KioskBanner.vue";
import WelcomeLayout from "@/components/layout/WelcomeLayout/WelcomeLayout.vue";
import GoogleLikeButton from "@/components/shared/Buttons/GoogleLikeButton.vue";
import { showSnackbarError, normalizeError } from "@/utils/index";
import { IntegrationAppClient } from "@integration-app/sdk";
export default {
name: "ConnectPage",
components: {
BuildInfo,
KioskBanner,
WelcomeLayout,
GoogleLikeButton,
},
data() {
return {
...window.connectData,
crmToken: null,
faUnlink,
isInIframe,
providers: [],
providersLoaded: false,
crmTokenLoaded: false,
};
},
computed: {
localProvider() {
return this.providers.find((e) => e.name === this.provider);
},
target() {
return this.isInIframe ? "_blank" : null;
},
},
created() {
this.getProviders();
},
mounted() {
this.showErrors();
},
watch: {
providersLoaded() {
if (this.providersLoaded) {
this.prepareIntegrationAppConnection();
}
},
},
methods: {
showErrors() {
if (!this.error) return;
showSnackbarError(this.error, undefined, undefined, false);
},
unwrapEntityResponse({ data }) {
return data.map(({ icon, name, displayName, viaIntegrationApp }) => {
return { icon, name, displayName, viaIntegrationApp };
});
},
async getProviders() {
try {
const response = await axios.get("/api/v1/connect-providers");
this.providers = this.unwrapEntityResponse(response);
this.providersLoaded = true;
} catch {
showSnackbarError(
"An error occurred, while loading form data (connect providers).",
);
}
},
async prepareIntegrationAppConnection() {
if (this.localProvider.viaIntegrationApp) {
try {
const response = await axios.get("/api/v1/integration-app-token");
this.crmToken = response.data.token;
this.crmTokenLoaded = true;
} catch (error) {
console.log(error);
showSnackbarError(
`An error occurred while preparing the page.
Try refreshing, if the error persists get in touch with the Jiminny team.`,
);
}
}
},
async integrationAppOnClick() {
const integrationApp = new IntegrationAppClient({
token: this.crmToken,
});
const connection = await integrationApp
.integration(this.localProvider.name)
.openNewConnection({
showPoweredBy: false,
allowMultipleConnections: false,
});
console.log('[IntegrationApp] openNewConnection resolved:', JSON.stringify(connection));
// [IntegrationApp] openNewConnection resolved: {
// "id":"69e0b41a67d0068c2ca0b48e",
// "name":"Zoho CRM",
// "userId":"1ece66c8-feb1-4df1-b321-21607daf4623",
// "tenantId":"69e0b3faef3e7b6248189289",
// "isTest":false,
// "connected":true,
// "state":"READY",
// "errors":[],
// "integrationId":"66fe6c913202f3a165e3c14d",
// "externalAppId":"6671653e7e2d642e4e41b0fa",
// "authOptionKey":"",
// "createdAt":"2026-04-16T10:04:10.420Z",
// "updatedAt":"2026-04-16T10:04:10.575Z",
// "retryAttempts":0,
// "isDeactivated":false
// }
if (connection && connection.disconnected !== true && connection.connected !== false) {
console.log('[IntegrationApp] connection condition matched');
try {
const saveRequest = await axios.post(
"/api/v1/integration-app-connect",
);
if (saveRequest.data && saveRequest.data.success === true) {
/** If all is good refresh the page here */
window.location = "/dashboard";
return;
}
throw new Error(saveRequest.data.message);
} catch (error) {
console.log(error);
showSnackbarError(normalizeError(error));
}
}
},
},
};
</script>
<style module lang="less" src="./connect.less"></style>
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
46362
|
|
46240
|
NULL
|
0
|
2026-04-17T10:26:28.754267+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776421588754_m2.jpg...
|
NULL
|
NULL
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorToo PhpStormFileEditViewNavigateCodeLaravelRefactorToolsWindowHelpFV faVsco.s v99 JY-20692-fix-integration-app-[API_KEY] v© AutomatedReportsService.php© SendReportJob.php© SendReportMailJob.phpC ReportController.phpTokenBuilder.phpC TeamSetupController.phpphp api.php• Filesystem.php© Team.php© CreateHeldActivityEvent.php© TrackProviderInstalledEvent.php© RequestGenerateReportJob.phpOpportunitySyncTrait.phpC Opportunity.phpT InteractsWithPivotTable.phgC OpportunityUpdated.phpC OpportunityStageUpdated.php= custom.log= laravel.logValidationOVov D Repositoryv D OnDemandActivitySt© Criteria.php© TranscriptionKeywor> D TeamSettingspnp nelpers.ongInitialFrontendState.php©Jiminny.php© Plan.php© Serializer.php© TeamScimDetails.phpbootstrapD buildO config[M contrib> M database> M docsv _ front-end> O.vscode> D.yarn› D coverage› D node_modules› D publicO resourcesscriptssrcD_mocks_DJappsD assets_ components> D AiReportsv O connect> D_mocks_> D_tests_0 connect.lessV connect.vuecashboardDeallnsichts> MerrorPagesexoon-oonalextension-installedD Invitation• JoinConferencelayout• LiveCoach• LockedD loginMeetingConsentmobileMonboard> M mocks_› testsV MobileAppDownk0 Onboard.lessV Onboard.vuets useProvidersSync> D ondemand< Hs local liminnyalocalnostA SF [jiminny@localhost]C scratch_1json xconnect.vue xV Onboard.vue4 console [EUl© CrmEntityRepository.phpfi crm_configurations (EU]A console [PROD]A console [STAGING]<script>101methods: {138async integrationAppOnCLickO) €c) FventService?rovider.onn© OpportunityPendingAiAnalysisAfterStageChanged.php143const connection = await integrationAppoccomoceaprevurmeutduller taloe© RunOpportunityAiAnalysis.php© ProcessAiAutomationAnalysisResults.php© ImportOpportunityBatch.php3):( ImportBatchJobTrait.php(C) Service.phpconsole.Log(' [IntegrationApp] openNewConnection resolved:', JSON.stringify(connection));cachedStagesCcW151trait OpportunitySyncTraitA33 X2 X1911041531018private function resolveForecastCategory(?string $forecastCategory): stringf.. 15410281551029arsuate funetion AnponeExtenalFlez Data (onnay Soropertile, int Soppontunt ty ,59)1030ecrurtelas - echis >gecupporcunicyoyncab lertelaso).-158159$this->importOpportunityCrmFieldData($properties, $crmFields,Sopportunity] 160// [IntegrationApp] openNewConnection resolved: {"id": "69e0b41a67d0068c2ca0b48e","name":"Zoho CRM","userId":"1ece66c8-feb1-4df1-b321-21607daf4623","tenantId": "69e0b3faef3e7b6248189289","isTest" : false,"connected": true,"state": "READY","errors": [],1033=1611034162Lusaees— 1051035private function importOpportunityContacts(Opportunity Sopportunity, array $as: 16410481651049/**1661050* Sync opportunity contacts using differential approachеxtеrарро ое едоо ее"authOptionKey" : """createdAt":"2026-04-16T10:04:10.420Z","updatedAt": "2026-04-16T10:04:10.575Z","retryAttempts" :0,"isDeactivated" : false1051* This compares current vs new associations and only makes necessary changes16810521 usageprivate tunccion syncupporcunttyconcactsultterenclal upporcunicy фopportunitуn10/0private function getcurrentContactCrmIds(Opportunity $opportunity): arrayf...71701J0/110771078107910801721 usageprivate function logContactAssociationChanges(173Opportunity Sopportunity,174array $currentContactCrmIds,array $contactAssociations,array $contactsToAdd,array +concacustokemove): void {...}10931801811 usage1074private function removeContactAssociations(Opportunity Sopportunity, array $cor 1821115184Tusaue1116private function addContactAssociations (Opportunity $opportunity, array $contac 18511331 usage1134private function attachSingleContact (Opportunity Sopportunity, string Scrmid -18941/0It conneccson oxconneccson:.ursconnecteu === Talse comneccson:.connec Accent LRotech tif (connection d& connection.connected === true) {1if (connection a connection.disconnectedt oeeif (connection && connection.disconnected !== true && connection.connected !== false) €console.log('[IntegrationApp] connection condition matched');try{const saveRequest = await axios.post("Lapi/v1/integration-app-connect".if (saveRequest.data && saveRequest.data.success === true) <** If all is aood refresh the page here *window.Location = "/dashboard";return;|throw new Error(saveRequest.data.message);} catch (error) {console.log(error);showSnackbarError(normalizeError(error)):-190orivate Tunccion pertormconcactActachment upporcunity sopportunlcContact Sc,191</script>115011/11 usage«style module lang="less" src="./connect.less"></style>private function logAddedContacts(Opportunity $opportunity, array $contactsAddeX Reject File 1 % @+ 2 of 2 files →Shortcuts conflicts: Clone Caret Above and 1 more shortcut conflict with macOS shortcuts. Modify these shortcuts or change macOS system settings. // Modify Shortcuts // Don't Show Again (today 12:22))j Support Daily • in 1h 34 mA100% C•Fri 17 Apr 13:26:28AutomatedRenortsCommandTestCascadeAutomated Report RetiDebugging OpportunitUpdate Connection Loso why did youvue#L170 connection?.disconnected in cone place andconnection.disconnected on otherwNo functional reason — it's inconsistent. connect.vue already has a connection && guard so the optionalchalnine Is recuneanttnere. Ler me alien ootn tlles lo use plain dot notation since connection Isalwavs checked for existence first• connect.vue+1 -1Both files now consistently use plain dot notation — connection is always null-checked before accessingits properties.ta all**2 files +2-5>Ask anything (24L)+ ‹> CodeClaude Sonnet 4.6Reject allAccept allW Windsurf Teams179:20ulr-o2 spaces...
|
NULL
|
-3153063581759725570
|
NULL
|
click
|
ocr
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorToo PhpStormFileEditViewNavigateCodeLaravelRefactorToolsWindowHelpFV faVsco.s v99 JY-20692-fix-integration-app-[API_KEY] v© AutomatedReportsService.php© SendReportJob.php© SendReportMailJob.phpC ReportController.phpTokenBuilder.phpC TeamSetupController.phpphp api.php• Filesystem.php© Team.php© CreateHeldActivityEvent.php© TrackProviderInstalledEvent.php© RequestGenerateReportJob.phpOpportunitySyncTrait.phpC Opportunity.phpT InteractsWithPivotTable.phgC OpportunityUpdated.phpC OpportunityStageUpdated.php= custom.log= laravel.logValidationOVov D Repositoryv D OnDemandActivitySt© Criteria.php© TranscriptionKeywor> D TeamSettingspnp nelpers.ongInitialFrontendState.php©Jiminny.php© Plan.php© Serializer.php© TeamScimDetails.phpbootstrapD buildO config[M contrib> M database> M docsv _ front-end> O.vscode> D.yarn› D coverage› D node_modules› D publicO resourcesscriptssrcD_mocks_DJappsD assets_ components> D AiReportsv O connect> D_mocks_> D_tests_0 connect.lessV connect.vuecashboardDeallnsichts> MerrorPagesexoon-oonalextension-installedD Invitation• JoinConferencelayout• LiveCoach• LockedD loginMeetingConsentmobileMonboard> M mocks_› testsV MobileAppDownk0 Onboard.lessV Onboard.vuets useProvidersSync> D ondemand< Hs local liminnyalocalnostA SF [jiminny@localhost]C scratch_1json xconnect.vue xV Onboard.vue4 console [EUl© CrmEntityRepository.phpfi crm_configurations (EU]A console [PROD]A console [STAGING]<script>101methods: {138async integrationAppOnCLickO) €c) FventService?rovider.onn© OpportunityPendingAiAnalysisAfterStageChanged.php143const connection = await integrationAppoccomoceaprevurmeutduller taloe© RunOpportunityAiAnalysis.php© ProcessAiAutomationAnalysisResults.php© ImportOpportunityBatch.php3):( ImportBatchJobTrait.php(C) Service.phpconsole.Log(' [IntegrationApp] openNewConnection resolved:', JSON.stringify(connection));cachedStagesCcW151trait OpportunitySyncTraitA33 X2 X1911041531018private function resolveForecastCategory(?string $forecastCategory): stringf.. 15410281551029arsuate funetion AnponeExtenalFlez Data (onnay Soropertile, int Soppontunt ty ,59)1030ecrurtelas - echis >gecupporcunicyoyncab lertelaso).-158159$this->importOpportunityCrmFieldData($properties, $crmFields,Sopportunity] 160// [IntegrationApp] openNewConnection resolved: {"id": "69e0b41a67d0068c2ca0b48e","name":"Zoho CRM","userId":"1ece66c8-feb1-4df1-b321-21607daf4623","tenantId": "69e0b3faef3e7b6248189289","isTest" : false,"connected": true,"state": "READY","errors": [],1033=1611034162Lusaees— 1051035private function importOpportunityContacts(Opportunity Sopportunity, array $as: 16410481651049/**1661050* Sync opportunity contacts using differential approachеxtеrарро ое едоо ее"authOptionKey" : """createdAt":"2026-04-16T10:04:10.420Z","updatedAt": "2026-04-16T10:04:10.575Z","retryAttempts" :0,"isDeactivated" : false1051* This compares current vs new associations and only makes necessary changes16810521 usageprivate tunccion syncupporcunttyconcactsultterenclal upporcunicy фopportunitуn10/0private function getcurrentContactCrmIds(Opportunity $opportunity): arrayf...71701J0/110771078107910801721 usageprivate function logContactAssociationChanges(173Opportunity Sopportunity,174array $currentContactCrmIds,array $contactAssociations,array $contactsToAdd,array +concacustokemove): void {...}10931801811 usage1074private function removeContactAssociations(Opportunity Sopportunity, array $cor 1821115184Tusaue1116private function addContactAssociations (Opportunity $opportunity, array $contac 18511331 usage1134private function attachSingleContact (Opportunity Sopportunity, string Scrmid -18941/0It conneccson oxconneccson:.ursconnecteu === Talse comneccson:.connec Accent LRotech tif (connection d& connection.connected === true) {1if (connection a connection.disconnectedt oeeif (connection && connection.disconnected !== true && connection.connected !== false) €console.log('[IntegrationApp] connection condition matched');try{const saveRequest = await axios.post("Lapi/v1/integration-app-connect".if (saveRequest.data && saveRequest.data.success === true) <** If all is aood refresh the page here *window.Location = "/dashboard";return;|throw new Error(saveRequest.data.message);} catch (error) {console.log(error);showSnackbarError(normalizeError(error)):-190orivate Tunccion pertormconcactActachment upporcunity sopportunlcContact Sc,191</script>115011/11 usage«style module lang="less" src="./connect.less"></style>private function logAddedContacts(Opportunity $opportunity, array $contactsAddeX Reject File 1 % @+ 2 of 2 files →Shortcuts conflicts: Clone Caret Above and 1 more shortcut conflict with macOS shortcuts. Modify these shortcuts or change macOS system settings. // Modify Shortcuts // Don't Show Again (today 12:22))j Support Daily • in 1h 34 mA100% C•Fri 17 Apr 13:26:28AutomatedRenortsCommandTestCascadeAutomated Report RetiDebugging OpportunitUpdate Connection Loso why did youvue#L170 connection?.disconnected in cone place andconnection.disconnected on otherwNo functional reason — it's inconsistent. connect.vue already has a connection && guard so the optionalchalnine Is recuneanttnere. Ler me alien ootn tlles lo use plain dot notation since connection Isalwavs checked for existence first• connect.vue+1 -1Both files now consistently use plain dot notation — connection is always null-checked before accessingits properties.ta all**2 files +2-5>Ask anything (24L)+ ‹> CodeClaude Sonnet 4.6Reject allAccept allW Windsurf Teams179:20ulr-o2 spaces...
|
NULL
|
|
46239
|
NULL
|
0
|
2026-04-17T10:26:28.754261+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776421588754_m1.jpg...
|
PhpStorm
|
faVsco.js – SF [jiminny@localhost]
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
FirefoxFileEditViewHistoryBookmarksProfilesToolsWi FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelp(ahl= Support Daily • in 1h 34 m100% C47APP (-zsh)X4-zshDOCKER• ₴1DEV (-zsh)882APP (-zsh)|X3-zsh./public/vue-assets/assets/exports-D1lmea40.js:./public/vue-assets/assets/playlists-Yn3gKP2z.js./public/vue-assets/assets/callScoringTemplates-zeRn40ul.js./public/vue-assets/assets/_copy0bject-USkOnlaQ.js../public/vue-assets/assets/pusher-znYCfz7U.js:./public/vue-assets/assets/onboard-BqxRx4zw.js./public/vue-assets/assets/StatusBadge-C2H2xj9g.js:/public/vue-assets/assets/kiosk-BDlz4mpq.js:./public/vue-assets/assets/preload-helper-DCvhahzG.js../public/vue-assets/assets/deal-insights-BnVE19yc.js../public/vue-assets/assets/ListView-CN2ZtlC7.js../public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js../public/vue-assets/assets/WelcomeLayout-B6wd32HG.js./public/vue-assets/assets/dashboard-XCNXJG27.js./public/vue-assets/assets/emoji-input-CSq870Vy.js./public/vue-assets/assets/AppButton-D3qMd0Dr.js./public/vue-assets/assets/sentry-DMzthP2u.js../public/vue-assets/assets/OrgSettingsLayout-DYz0he6q.js../public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js../public/vue-assets/assets/playback-DRpCBZ09.js../public/vue-assets/assets/index.module-Bjlhgfd1.js../public/vue-assets/assets/intl-tel-input-BW4mv40Q.js:./public/vue-assets/assets/team-insights-CPpPTAKV.js../public/vue-assets/assets/popper-CQwVcrX4.js../public/vue-assets/assets/PhoneField-CwCIoAYm.js../public/vue-assets/assets/live-Cfb59XkF.js./public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BN0485xV.js../public/vue-assets/assets/index-1Grk9P2e.js../public/vue-assets/assets/logged-in-layout-DArs1VTa.jsО 88547.84kB48.28kB55.13kB61.28kB62.98kB63.11kB64.66kB79.60kB82.59kB94.84kB115.71kB117.59kB120.67kB128.71kB129.28kB133.44kB164.28kB176.33kB180.40kB198.79 kВ218.14kB264.94kB298.57kB307.13 kB343.99kB367.43kB689.63 kB825.23kB1,402.70 kB[PLUGIN_TIMINGS] Warning: Your build spent significant time in plugins. Here is a breakdown:- vite:css (61%)- vite-plugin-externals (12%)- vite:vue (9%)- sentry-vite-plugin (8%)- vite:worker (6%)See [URL_WITH_CREDENTIALS] ~/jiminny/app/front-end (JY-20692-fix-integration-app-[API_KEY]) $ O* Review screenp...gzip:16.46kBgz1p:15.07kBgzip:13.28kB9z1p:20.09kB921p:18.88kBgzip:21.85kBgzip:22.96kBgzip:22.65kBgz1p:27.46kBgzip:28.16kBgz1p:33.78kBgzip:38.71kBgzip:34.16kB9z1p:40.05kB9z1p:36.72kBgzip:43.03kBgzip:52.24kBgzip:gzip:56.15kB67.85 kBgzip:61.85 kBgz1p:64.16 kBgz1p:60.31 kBgzip:77.22 kBgz1p:103.86 kB9z1p:84.90 kB9z1p:97.05 kB921p:202.81 kBgzip:72.55 kВ921p: 438.08 kB• [EMAIL]:294.48kBmap:153..25kBmap:65.85kBmap:239.59kBmap:219.27 kBmap:201.38kBmap:244.72kBmap:300.68kBmap:map:3,452..35kB292.79kBmap:308..10kBmap:500.60kBmap:258.56kBmap:410.48kBmap:266.15kBmap:516.67kBmap:831.82kBmap:623.43kBmap:836.88 kBmap:684.87 kBmap: 1,108.20 kBmap:475.61kBmap:959.96kBmap: 1,245.28kBmap:849.05 kBmap:792.41kBmap: 3,016.64 kBmap:436.62 kBmap: 6,283.55kB• 878Fri 17 Apr 13:26:281₴81ec2-user@ip-10-...• 88APP...
|
NULL
|
-5596740301088824425
|
NULL
|
click
|
ocr
|
NULL
|
FirefoxFileEditViewHistoryBookmarksProfilesToolsWi FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelp(ahl= Support Daily • in 1h 34 m100% C47APP (-zsh)X4-zshDOCKER• ₴1DEV (-zsh)882APP (-zsh)|X3-zsh./public/vue-assets/assets/exports-D1lmea40.js:./public/vue-assets/assets/playlists-Yn3gKP2z.js./public/vue-assets/assets/callScoringTemplates-zeRn40ul.js./public/vue-assets/assets/_copy0bject-USkOnlaQ.js../public/vue-assets/assets/pusher-znYCfz7U.js:./public/vue-assets/assets/onboard-BqxRx4zw.js./public/vue-assets/assets/StatusBadge-C2H2xj9g.js:/public/vue-assets/assets/kiosk-BDlz4mpq.js:./public/vue-assets/assets/preload-helper-DCvhahzG.js../public/vue-assets/assets/deal-insights-BnVE19yc.js../public/vue-assets/assets/ListView-CN2ZtlC7.js../public/vue-assets/assets/_plugin-vue_export-helper-DD3s5456.js../public/vue-assets/assets/WelcomeLayout-B6wd32HG.js./public/vue-assets/assets/dashboard-XCNXJG27.js./public/vue-assets/assets/emoji-input-CSq870Vy.js./public/vue-assets/assets/AppButton-D3qMd0Dr.js./public/vue-assets/assets/sentry-DMzthP2u.js../public/vue-assets/assets/OrgSettingsLayout-DYz0he6q.js../public/vue-assets/assets/vuex.esm-bundler-DqfufJ2-.js../public/vue-assets/assets/playback-DRpCBZ09.js../public/vue-assets/assets/index.module-Bjlhgfd1.js../public/vue-assets/assets/intl-tel-input-BW4mv40Q.js:./public/vue-assets/assets/team-insights-CPpPTAKV.js../public/vue-assets/assets/popper-CQwVcrX4.js../public/vue-assets/assets/PhoneField-CwCIoAYm.js../public/vue-assets/assets/live-Cfb59XkF.js./public/vue-assets/assets/video-js-skin.less_vue_type_style_index_0_src_true_lang-BN0485xV.js../public/vue-assets/assets/index-1Grk9P2e.js../public/vue-assets/assets/logged-in-layout-DArs1VTa.jsО 88547.84kB48.28kB55.13kB61.28kB62.98kB63.11kB64.66kB79.60kB82.59kB94.84kB115.71kB117.59kB120.67kB128.71kB129.28kB133.44kB164.28kB176.33kB180.40kB198.79 kВ218.14kB264.94kB298.57kB307.13 kB343.99kB367.43kB689.63 kB825.23kB1,402.70 kB[PLUGIN_TIMINGS] Warning: Your build spent significant time in plugins. Here is a breakdown:- vite:css (61%)- vite-plugin-externals (12%)- vite:vue (9%)- sentry-vite-plugin (8%)- vite:worker (6%)See [URL_WITH_CREDENTIALS] ~/jiminny/app/front-end (JY-20692-fix-integration-app-[API_KEY]) $ O* Review screenp...gzip:16.46kBgz1p:15.07kBgzip:13.28kB9z1p:20.09kB921p:18.88kBgzip:21.85kBgzip:22.96kBgzip:22.65kBgz1p:27.46kBgzip:28.16kBgz1p:33.78kBgzip:38.71kBgzip:34.16kB9z1p:40.05kB9z1p:36.72kBgzip:43.03kBgzip:52.24kBgzip:gzip:56.15kB67.85 kBgzip:61.85 kBgz1p:64.16 kBgz1p:60.31 kBgzip:77.22 kBgz1p:103.86 kB9z1p:84.90 kB9z1p:97.05 kB921p:202.81 kBgzip:72.55 kВ921p: 438.08 kB• [EMAIL]:294.48kBmap:153..25kBmap:65.85kBmap:239.59kBmap:219.27 kBmap:201.38kBmap:244.72kBmap:300.68kBmap:map:3,452..35kB292.79kBmap:308..10kBmap:500.60kBmap:258.56kBmap:410.48kBmap:266.15kBmap:516.67kBmap:831.82kBmap:623.43kBmap:836.88 kBmap:684.87 kBmap: 1,108.20 kBmap:475.61kBmap:959.96kBmap: 1,245.28kBmap:849.05 kBmap:792.41kBmap: 3,016.64 kBmap:436.62 kBmap: 6,283.55kB• 878Fri 17 Apr 13:26:281₴81ec2-user@ip-10-...• 88APP...
|
NULL
|
|
46108
|
NULL
|
0
|
2026-04-17T10:21:19.669989+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776421279669_m2.jpg...
|
NULL
|
NULL
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorToo PhpStormFileEditViewNavigateCodeLaravelRefactorToolsWindowHelpFV faVsco.s v99 JY-20692-fix-integration-app-[API_KEY] v© AutomatedReportsService.php© SendReportJob.php© SendReportMailJob.phpC ReportController.phpTokenBuilder.phpC TeamSetupController.phpphp api.php• Filesystem.php© Team.php© CreateHeldActivityEvent.php© TrackProviderInstalledEvent.php© RequestGenerateReportJob.phpOpportunitySyncTrait.phpC Opportunity.phpT InteractsWithPivotTable.phgC OpportunityUpdated.phpC OpportunityStageUpdated.php= custom.log= laravel.logValidationOVov D Repositoryv D OnDemandActivitySt© Criteria.php© TranscriptionKeywor> D TeamSettingspnp nelpers.ongInitialFrontendState.php©Jiminny.php©Plan.php© Serializer.php© TeamScimDetails.phpbootstrapD buildO config[Mcontrib> M database> M docsv _ front-end> O.vscode> D.yarn› D coverage› D node_modules› D publicO resourcesscriptssrcD_mocks_DappsD assets_ components> D AiReportsv O connect> D_mocks_> D_tests_0 connect.lessV connect.vuecashboardDeallnsichts> MerrorPagesexoon-oonalextension-installedD Invitation• JoinConferencelayout• LiveCoach• LockedD loginMeetingConsentmobileMonboard> M mocks_› testsV MobileAppDownk0 Onboard.lessV Onboard.vuets useProvidersSync> D ondemand< Hs local liminnyalocalnostA SF [jiminny@localhost]CP scratch_1.json4 console [EUl© CrmEntityRepository.phpconnect.vue xV Onboard.vuefi crm_configurations (EU]A console [STAGING]c) FventServiceProvider.onn© OpportunityPendingAiAnalysisAfterStageChanged.php© RunOpportunityAiAnalysis.php© ProcessAiAutomationAnalysisResults.php© ImportOpportunityBatch.php101138143148A console [PROD]<script>methods: {async integrationAppOnClickO) €const connection = await integrationAppoccomoceaprevurmeutduller taloe3):( ImportBatchJobTrait.php(C) Service.phpconsole.Log(' [IntegrationApp] openNewConnection resolved:', JSON.stringify(connection));cachedStagesCcW151trait OpportunitySyncTraitA33 X2 X1911041531018102810291030private function resolveForecastCategory(?string $forecastCategory): stringf.. 154155arsuate funetion AnponeExtenalFlez Data (onnay Soropertie, int Sopontunt ty ,59)ecrurtelas - echis >gecupporcunttysyncab terte tastee-158159$this->importOpportunityCrmFieldData($properties, $crmFields,Sopportunity] 160// [IntegrationApp] openNewConnection resolved: {"id": "69e0b41a67d0068c2ca0b48e","name":"Zoho CRM","userId":"1ece66c8-feb1-4df1-b321-21607daf4623","tenantId": "69e0b3faef3e7b6248189289","isTest" : false,"connected": true,"state": "READY","errors": [],10331034103510481049105010511052=161162Lusaees/**1 usage— 105private function importOpportunityContacts(Opportunity Sopportunity, array $as: 164165166* Sync opportunity contacts using differential approachеxtеrарро ое едоо ее"authOptionKey" : """createdAt":"2026-04-16T10:04:10.420Z","updatedAt": "2026-04-16T10:04:10.575Z","retryAttempts" :0,"isDeactivated" : false* This compares current vs new associations and only makes necessary changes16810/01J0/1107710781079108010931074111511331134private tunccion syncupporcunttyconcactsultterenclal upporcunicy фopportunitуnprivate function getcurrentContactCrmIds(Opportunity $opportunity): array{...71701 usageprivate function logContactAssociationChanges(Opportunity Sopportunity,array $currentContactCrmIds,array $contactAssociations,array $contactsToAdd,array +concacustokemove): void {...}1721731741 usageprivate function removeContactAssociations(Opportunity Sopportunity, array $cor 182100184Tusaueprivate function addContactAssociations (Opportunity $opportunity, array $contac 1851 usageprivate function attachSingleContact (Opportunity Sopportunity, string Scrmid -189It conneccson oxconneccson:.ursconnecteu === Talse comneccson:.connec Accent LRotech tif (connection d& connection.connected === true) {1i (connection se connection.cosconnectedtrueseif (connection && connection.disconnected !== true && connection.connected !== false) €console.log('[IntegrationApp] connection condition matched'D;try{const saveRequest = await axios.post("Lapi/v1/integration-app-connect".if (saveRequest.data && saveRequest.data.success === true) <** If all is aood refresh the page here *window.Location = "/dashboard";recurnthrow new Error(saveRequest.data.message);f catch (error) (console.log(error);showSnackbarError(normalizeError(error)):-190private function performContactAttachment(Opportunity $opportunity, Contact Sc191</script>115011/11 usage«style module lang="less" src="./connect.less"></style>41/0private function logAddedContacts(Opportunity $opportunity, array $contactsAddeX Reject File 1 % @+ 2 of 2 files →Shortcuts conflicts: Clone Caret Above and 1 more shortcut conflict with macOS shortcuts. Modify these shortcuts or change macOS system settings. // Modify Shortcuts // Don't Show Again (58 minutes ago){ Support Daily • in 1h 39 mA100% C•Fri 17 Apr 13:21:19AutomatedRenortsCommandTestCascadeAutomated Report RetiDebugging OpportunitUpdate Connection Loso why did youvue#L170 connection?.disconnected in cone place andconnection.disconnected on otherwNo functional reason — it's inconsistent. conect.vue already has a connection && guard so the optionalchalnine Is recuneanttnere. Ler me alien ootn tlles lo use plain dot notation since connection Isalwavs checked for existence first• connect.vue+1 -1Both files now consistently use plain dot notation — connection is always null-checked before accessingits properties.ta all**2 files +3-5›Ask anything (24L)+ ‹> CodeClaude Sonnet 4.6Reject allAccept allwinasun leams171:67 UTF-8( 2 spaces...
|
NULL
|
-5817970843246167218
|
NULL
|
click
|
ocr
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorToo PhpStormFileEditViewNavigateCodeLaravelRefactorToolsWindowHelpFV faVsco.s v99 JY-20692-fix-integration-app-[API_KEY] v© AutomatedReportsService.php© SendReportJob.php© SendReportMailJob.phpC ReportController.phpTokenBuilder.phpC TeamSetupController.phpphp api.php• Filesystem.php© Team.php© CreateHeldActivityEvent.php© TrackProviderInstalledEvent.php© RequestGenerateReportJob.phpOpportunitySyncTrait.phpC Opportunity.phpT InteractsWithPivotTable.phgC OpportunityUpdated.phpC OpportunityStageUpdated.php= custom.log= laravel.logValidationOVov D Repositoryv D OnDemandActivitySt© Criteria.php© TranscriptionKeywor> D TeamSettingspnp nelpers.ongInitialFrontendState.php©Jiminny.php©Plan.php© Serializer.php© TeamScimDetails.phpbootstrapD buildO config[Mcontrib> M database> M docsv _ front-end> O.vscode> D.yarn› D coverage› D node_modules› D publicO resourcesscriptssrcD_mocks_DappsD assets_ components> D AiReportsv O connect> D_mocks_> D_tests_0 connect.lessV connect.vuecashboardDeallnsichts> MerrorPagesexoon-oonalextension-installedD Invitation• JoinConferencelayout• LiveCoach• LockedD loginMeetingConsentmobileMonboard> M mocks_› testsV MobileAppDownk0 Onboard.lessV Onboard.vuets useProvidersSync> D ondemand< Hs local liminnyalocalnostA SF [jiminny@localhost]CP scratch_1.json4 console [EUl© CrmEntityRepository.phpconnect.vue xV Onboard.vuefi crm_configurations (EU]A console [STAGING]c) FventServiceProvider.onn© OpportunityPendingAiAnalysisAfterStageChanged.php© RunOpportunityAiAnalysis.php© ProcessAiAutomationAnalysisResults.php© ImportOpportunityBatch.php101138143148A console [PROD]<script>methods: {async integrationAppOnClickO) €const connection = await integrationAppoccomoceaprevurmeutduller taloe3):( ImportBatchJobTrait.php(C) Service.phpconsole.Log(' [IntegrationApp] openNewConnection resolved:', JSON.stringify(connection));cachedStagesCcW151trait OpportunitySyncTraitA33 X2 X1911041531018102810291030private function resolveForecastCategory(?string $forecastCategory): stringf.. 154155arsuate funetion AnponeExtenalFlez Data (onnay Soropertie, int Sopontunt ty ,59)ecrurtelas - echis >gecupporcunttysyncab terte tastee-158159$this->importOpportunityCrmFieldData($properties, $crmFields,Sopportunity] 160// [IntegrationApp] openNewConnection resolved: {"id": "69e0b41a67d0068c2ca0b48e","name":"Zoho CRM","userId":"1ece66c8-feb1-4df1-b321-21607daf4623","tenantId": "69e0b3faef3e7b6248189289","isTest" : false,"connected": true,"state": "READY","errors": [],10331034103510481049105010511052=161162Lusaees/**1 usage— 105private function importOpportunityContacts(Opportunity Sopportunity, array $as: 164165166* Sync opportunity contacts using differential approachеxtеrарро ое едоо ее"authOptionKey" : """createdAt":"2026-04-16T10:04:10.420Z","updatedAt": "2026-04-16T10:04:10.575Z","retryAttempts" :0,"isDeactivated" : false* This compares current vs new associations and only makes necessary changes16810/01J0/1107710781079108010931074111511331134private tunccion syncupporcunttyconcactsultterenclal upporcunicy фopportunitуnprivate function getcurrentContactCrmIds(Opportunity $opportunity): array{...71701 usageprivate function logContactAssociationChanges(Opportunity Sopportunity,array $currentContactCrmIds,array $contactAssociations,array $contactsToAdd,array +concacustokemove): void {...}1721731741 usageprivate function removeContactAssociations(Opportunity Sopportunity, array $cor 182100184Tusaueprivate function addContactAssociations (Opportunity $opportunity, array $contac 1851 usageprivate function attachSingleContact (Opportunity Sopportunity, string Scrmid -189It conneccson oxconneccson:.ursconnecteu === Talse comneccson:.connec Accent LRotech tif (connection d& connection.connected === true) {1i (connection se connection.cosconnectedtrueseif (connection && connection.disconnected !== true && connection.connected !== false) €console.log('[IntegrationApp] connection condition matched'D;try{const saveRequest = await axios.post("Lapi/v1/integration-app-connect".if (saveRequest.data && saveRequest.data.success === true) <** If all is aood refresh the page here *window.Location = "/dashboard";recurnthrow new Error(saveRequest.data.message);f catch (error) (console.log(error);showSnackbarError(normalizeError(error)):-190private function performContactAttachment(Opportunity $opportunity, Contact Sc191</script>115011/11 usage«style module lang="less" src="./connect.less"></style>41/0private function logAddedContacts(Opportunity $opportunity, array $contactsAddeX Reject File 1 % @+ 2 of 2 files →Shortcuts conflicts: Clone Caret Above and 1 more shortcut conflict with macOS shortcuts. Modify these shortcuts or change macOS system settings. // Modify Shortcuts // Don't Show Again (58 minutes ago){ Support Daily • in 1h 39 mA100% C•Fri 17 Apr 13:21:19AutomatedRenortsCommandTestCascadeAutomated Report RetiDebugging OpportunitUpdate Connection Loso why did youvue#L170 connection?.disconnected in cone place andconnection.disconnected on otherwNo functional reason — it's inconsistent. conect.vue already has a connection && guard so the optionalchalnine Is recuneanttnere. Ler me alien ootn tlles lo use plain dot notation since connection Isalwavs checked for existence first• connect.vue+1 -1Both files now consistently use plain dot notation — connection is always null-checked before accessingits properties.ta all**2 files +3-5›Ask anything (24L)+ ‹> CodeClaude Sonnet 4.6Reject allAccept allwinasun leams171:67 UTF-8( 2 spaces...
|
NULL
|