|
35169
|
1316
|
19
|
2026-05-13T12:16:17.342973+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-13/1778 /Users/lukas/.screenpipe/data/data/2026-05-13/1778674577342_m2.jpg...
|
PhpStorm
|
faVsco.js – TeamInsightsRepository.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20891-improve-sms-text Project: faVsco.js, menu
JY-20891-improve-sms-text-relays, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
CONTEXT_TEAM_INSIGHTS_ACTIVITY
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
0 results
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Sync Changes
Hide This Notification
Code changed:
Hide
1
6
11
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Repositories;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use Elastica\Aggregation\AbstractAggregation;
use Elastica\Aggregation\AvgBucket;
use Elastica\Aggregation\Composite;
use Elastica\Aggregation\DateHistogram;
use Elastica\Aggregation\DateRange;
use Elastica\Aggregation\Filter;
use Elastica\Aggregation\Nested;
use Elastica\Aggregation\Sum;
use Elastica\Aggregation\Terms as AggregationTerms;
use Elastica\Aggregation\ValueCount;
use Elastica\Document;
use Elastica\Query;
use Elastica\Query\BoolQuery;
use Elastica\Query\Exists;
use Elastica\Query\Range;
use Elastica\Query\Term;
use Elastica\Query\Terms;
use Elastica\Result;
use Elastica\ResultSet;
use Generator;
use Illuminate\Support\Collection;
use Illuminate\Support\LazyCollection;
use Jiminny\Component\ActivitySearch\FilterDefinitionCollection;
use Jiminny\Component\ActivitySearch\FilterDefinitionQuery;
use Jiminny\Component\ElasticSearch\Service\Search;
use Jiminny\Component\Math\BitwiseOperations;
use Jiminny\Exceptions\OutOfBoundsException;
use Jiminny\Models\Activity;
use Jiminny\Models\Activity\Comment;
use Jiminny\Models\CoachingFeedback;
use Jiminny\Models\User;
class TeamInsightsRepository
{
use BitwiseOperations;
public const array CONVERSATION_DRILLDOWNS = [
self::CONVERSATION_DRILLDOWN_SCHEDULED,
self::CONVERSATION_DRILLDOWN_ATTEMPTED,
self::CONVERSATION_DRILLDOWN_CONNECTED,
self::CONVERSATION_DRILLDOWN_LOGGED,
];
public const string CONVERSATION_DRILLDOWN_CONNECTED = 'connected';
public const string CONVERSATION_DRILLDOWN_SCHEDULED = 'scheduled';
public const string CONVERSATION_DRILLDOWN_ATTEMPTED = 'attempted';
public const string CONVERSATION_DRILLDOWN_LOGGED = 'logged';
public const array DRILL_DOWN_MAP = [
'id_string' => 'id',
'title' => 'title',
'user.id_string' => null, // needed for indirect mapping
'user.name' => 'organizer.name',
'user.job.name' => 'organizer.job.name',
'user.photo_url' => 'organizer.photoUrl',
'type' => 'type',
'lead.name' => 'prospect.lead.name',
'lead.company' => 'prospect.lead.company',
'contact.name' => 'prospect.contact.name',
'contact.account.name' => 'prospect.contact.account.name',
'account.name' => 'prospect.account.account.name',
'participants.user.id_string' => null, // this would return an array and needs manual mapping
'participants.country_code' => null, // this would return an array and needs manual mapping
'participants.phone_number' => null, // this would return an array and needs manual mapping
'favorite_count' => 'stats.favorites',
'share_count' => 'stats.shares',
'comment_count' => 'stats.comments',
'play_count' => 'stats.plays',
'stats.talk_time_ratio' => 'stats.talkTimeRatio',
'stats.talking_speed' => 'stats.talkingSpeed',
'stats.user_questions' => 'stats.userQuestionsCount',
'stats.longest_user_monologue' => 'stats.longestUserMonologue',
'stats.longest_customer_monologue' => 'stats.longestCustomerMonologue',
'stats.patience_time' => 'stats.patienceTime',
'plays.user.id_string' => null, // this would return an array and needs manual mapping
'average_score' => 'averageScore',
'ai_call_score.score' => 'aiCallScore',
'category.name' => 'category.name',
'opportunity.value' => 'opportunity.value',
'opportunity.currency_code' => 'opportunity.currency_code',
'opportunity.stage.label' => 'opportunity.stage.label',
'duration' => 'duration',
'actual_end_time' => 'actualEndTime',
'tracks.telephony_provider_id' => null, // this would return an array and needs manual mapping
'coachingFeedbacks.coach.name' => null, // this would return an array and needs manual mapping
];
public const int FLAG_DRILLDOWN_COMMENT_POSITIVE = 1;
public const int FLAG_DRILLDOWN_COMMENT_NEGATIVE = 2;
private const int AGG_TERMS_MAX_SIZE = 9999;
private const int AGG_COMPOSITE_MAX_SIZE = 10000;
private const int AGGREGATE_VALUE_AVG = 0;
private const int AGGREGATE_VALUE_MAX = 1;
private const int AGGREGATE_VALUE_SUM = 2;
public function __construct(
private readonly Search $searchService,
private readonly Activity $model,
) {
}
/**
* Ensure prospect.name is present and non-empty by applying a type-based fallback
* from the original dot-notated data structure.
*
* - type lead => prospect.lead.name
* - type contact => prospect.contact.name
* - type account => prospect.account.account.name or prospect.account.name
*/
private static function ensureProspectNameFallback(array $data, array $originalDotData): array
{
if (! array_key_exists('prospect', $data) || ! is_array($data['prospect'])
|| ! array_key_exists('type', $data['prospect'])
) {
return $data;
}
$existingName = $data['prospect']['name'] ?? null;
$needsFallback = ! is_string($existingName) || trim($existingName) === '';
if (! $needsFallback) {
return $data;
}
$prospectName = null;
switch ($data['prospect']['type']) {
case 'lead':
$prospectName = $originalDotData['prospect.lead.name'] ?? null;
break;
case 'contact':
$prospectName = $originalDotData['prospect.contact.name'] ?? null;
break;
case 'account':
// Some indices nest account name under account.account.name
$prospectName = $originalDotData['prospect.account.account.name']
?? $originalDotData['prospect.account.name']
?? null;
break;
}
// Normalize whitespace-only fallback names to null (mirror $existingName check)
if (is_string($prospectName)) {
$prospectName = trim($prospectName);
if ($prospectName === '') {
$prospectName = null;
}
}
// set the name key no matter if empty
$data['prospect']['name'] = $prospectName;
return $data;
}
public function exportConversationsPerUser(FilterDefinitionCollection $filterSet, array $include): LazyCollection
{
$boolQuery = $filterSet->getBoolQuery(
$filterSet->extractElasticSearchQueries()
);
$query = (new Query($boolQuery))
->setSize(1000)
->setSource([
'includes' => $include,
])
->setSort([
'scheduled_start_time' => 'asc',
]);
return LazyCollection::make(function () use ($query): Generator {
$scroll = $this->searchService->scroll($query, $this->model);
foreach ($scroll as $resultSet) {
yield $resultSet;
}
})
->flatMap(static function (ResultSet $resultSet): array {
return $resultSet->getResults();
})
->map(static function (Result $result): array {
$hit = $result->getHit();
return $hit['_source'];
});
}
private function queryDrillDownResults(User $consumer, BoolQuery $boolQuery, int $page, int $limit = 25): Collection
{
if ($page <= 0) {
throw new OutOfBoundsException('The page number can only be greater or equal to one');
}
$map = Collection::make(self::DRILL_DOWN_MAP);
$query = (new Query($boolQuery))
->setSort(['scheduled_start_time'])
->setSource($map->keys()->all());
if ($limit === 0) {
$query->setSize(1000);
$documents = LazyCollection::make(function () use ($query): Generator {
$scroll = $this->searchService->scroll($query, $this->model);
foreach ($scroll as $resultSet) {
yield $resultSet;
}
})
->flatMap(static function (ResultSet $resultSet): array {
return $resultSet->getDocuments();
});
} else {
$query
->setSize($limit)
->setFrom(($page - 1) * $limit);
$documents = $this->searchService->search($query, $this->model, 'queryDrillDownResults')->getDocuments();
}
return Collection::make($documents)
->map(static function (Document $document) use ($map): array {
$documentDataRaw = $document->getData();
$documentData = array_dot($documentDataRaw);
$data = [];
foreach ($map as $fromKey => $toKey) {
if ($toKey === null) {
continue;
}
array_set($data, $toKey, $documentData[$fromKey] ?? null);
}
$roomOwnerId = $documentData['user.id_string'];
$data['played'] = Collection::make($documentDataRaw['plays'] ?? [])
->map(static function (array $data): string {
return $data['user']['id_string'];
})
->all();
$data['from'] = [
'national_phone_number' => Collection::make($documentDataRaw['participants'] ?? [])
->filter(static function (array $data) use ($roomOwnerId): bool {
return array_key_exists('user', $data) && $data['user']['id_string'] === $roomOwnerId;
})
->map(static function (array $data): ?string {
return $data['phone_number'];
})
->first(),
];
$data['isRecorded'] = Collection::make($documentDataRaw['tracks'] ?? [])
->filter(static function (array $track): bool {
return $track['telephony_provider_id'] !== null;
})
->isNotEmpty();
$data['coaches'] = Collection::make(
is_array($documentDataRaw)
&& array_key_exists('coachingFeedbacks', $documentDataRaw)
&& is_array($documentDataRaw['coachingFeedbacks'])
? $documentDataRaw['coachingFeedbacks']
: [],
);
return $data;
})
->map(static function (array $data) use ($consumer): array {
$dotData = array_dot($data);
// Keep a copy of the original dot data so we can derive names after reshaping
$originalDotData = $dotData;
// map prospect to lead/contact/account
if (array_key_exists('prospect.lead.name', $dotData) && $dotData['prospect.lead.name'] !== null) {
$data['prospect'] = array_get($data, 'prospect.lead');
$data['prospect']['type'] = 'lead';
} elseif (array_key_exists('prospect.contact.name', $dotData) && $dotData['prospect.contact.name'] !== null) {
$data['prospect'] = array_get($data, 'prospect.contact');
$data['prospect']['type'] = 'contact';
} elseif (
array_key_exists('prospect.account.account.name', $dotData)
&& $dotData['prospect.account.account.name'] !== null
) {
$data['prospect'] = array_get($data, 'prospect.account');
$data['prospect']['type'] = 'account';
} else {
unset($data['prospect']);
}
// Ensure prospect.name is present; if missing or empty, fill using type-based fallback
$data = self::ensureProspectNameFallback($data, $originalDotData);
$dotData = array_dot($data);
$nationalPhoneNumber = $dotData['from.national_phone_number'];
unset($data['from']);
$data['title'] = getActivityTitleAttribute(
$dotData['organizer.name'],
$dotData['type'],
$dotData['title'],
$dotData['prospect.name'] ?? null,
$nationalPhoneNumber
);
if (array_key_exists('category.name', $dotData) && $dotData['category.name'] === null) {
unset($data['category']['name']);
}
if (array_key_exists('category', $data) && empty($data['category'])) {
unset($data['category']);
}
// Include opportunity if either a value OR a stage label exists.
$hasOpportunityValue = array_key_exists('opportunity.value', $dotData)
&& $dotData['opportunity.value'] !== null;
$hasOpportunityStage = array_key_exists('opportunity.stage.label', $dotData)
&& $dotData['opportunity.stage.label'] !== null;
if ($hasOpportunityValue || $hasOpportunityStage) {
$data['opportunity'] = [];
if ($hasOpportunityValue) {
$data['opportunity']['formattedValue'] = formatOpportunityValue(
(float) $dotData['opportunity.value'],
$dotData['opportunity.currency_code'],
);
}
if ($hasOpportunityStage) {
$data['opportunity']['stage'] = [
'label' => $dotData['opportunity.stage.label'],
];
}
} else {
unset($data['opportunity']);
}
if (array_key_exists('played', $data) && is_array($data['played'])) {
$data['played'] = in_array($consumer->id_string, $data['played'], true);
}
if (array_key_exists('duration', $data) && $data['duration'] !== null) {
$data['durationForHumans'] = secondsToHuman((int) $dotData['duration']);
}
unset($data['duration']);
return $data;
})
// Convert database times to ISO 8601 or frontend will apply timezone conversions
->map(static function (array $data): array {
if (isset($data['actualEndTime'])) {
$data['actualEndTime'] = Carbon::createFromFormat('Y-m-d H:i:s', $data['actualEndTime'])->toIso8601String();
}
return $data;
});
}
public function getConversationsPerActivityChannelDrillDown(
User $consumer,
FilterDefinitionCollection $filterSet,
string $activityChannel,
string $drillDownType,
int $page,
int $limit = 25,
): Collection {
$boolQuery = $filterSet
->getBoolQuery($filterSet->extractElasticSearchQueries())
->addMust((new Term())->setTerm('type.keyword', $activityChannel));
switch ($drillDownType) {
case self::CONVERSATION_DRILLDOWN_SCHEDULED:
case self::CONVERSATION_DRILLDOWN_ATTEMPTED:
$boolQuery->addMust(new Exists('scheduled_start_time'));
break;
case self::CONVERSATION_DRILLDOWN_CONNECTED:
$boolQuery->addMust(new Exists('actual_start_time'));
break;
case self::CONVERSATION_DRILLDOWN_LOGGED:
$boolQuery->addMust(new Exists('crm_provider_id'));
break;
default:
throw new OutOfBoundsException('Unsupported drill down type');
}
return $this->queryDrillDownResults($consumer, $boolQuery, $page, $limit);
}
public function getCoachingActivitiesDrillDown(
User $consumer,
FilterDefinitionCollection $filterSet,
int $flags,
int $page,
int $limit = 25,
): Collection {
$extraFilterDefinitionQueries = [];
if ($this->isBitwiseFlagEnabled($flags, self::FLAG_DRILLDOWN_COMMENT_NEGATIVE)) {
$extraFilterDefinitionQueries[] = FilterDefinitionQuery::instance()
->setQuery(new Terms('comments.type', [Comment::TYPE_GAME_CHANGER]))
->setPath('comments', 'comments');
}
if ($this->isBitwiseFlagEnabled($flags, self::FLAG_DRILLDOWN_COMMENT_POSITIVE)) {
$extraFilterDefinitionQueries[] = FilterDefinitionQuery::instance()
->setQuery(new Terms('comments.type', [Comment::TYPE_POSITIVE]))
->setPath('comments', 'comments');
}
$boolQuery = $filterSet->getBoolQuery(
$filterSet->extractElasticSearchQueries($extraFilterDefinitionQueries)
);
return $this->queryDrillDownResults($consumer, $boolQuery, $page, $limit);
}
public function getCoachingActivitiesOverTimeDrillDown(
User $consumer,
FilterDefinitionCollection $filterSet,
User $coachee,
?string $sectionId,
int $page,
int $limit = 25
): Collection {
$extraFilterSetQueries = [
FilterDefinitionQuery::instance()
->setQuery((new Term())->setTerm('coachingFeedbacks.visibility', CoachingFeedback::VISIBLE_TO_ALL))
->setPath('coachingFeedbacks', 'coachingFeedbacks'),
FilterDefinitionQuery::instance()
->setQuery((new Term())->setTerm('coachingFeedbacks.coachee.id_string', $coachee->id_string))
->setPath('coachingFeedbacks.coachee', 'coachingFeedbacks'),
];
if (is_string($sectionId)) {
$extraFilterSetQueries[] = FilterDefinitionQuery::instance()
->setQuery((new Term())->setTerm('coachingFeedbacks.sectionFeedbacks.section.id_string', $sectionId))
->setPath('coachingFeedbacks.sectionFeedbacks.section', 'coachingFeedbacks.sectionFeedbacks');
}
$boolQuery = $filterSet->getBoolQuery(
$filterSet->extractElasticSearchQueries($extraFilterSetQueries)
);
return $this->queryDrillDownResults($consumer, $boolQuery, $page, $limit);
}
public function getEngagementActivitiesDrillDown(
User $consumer,
FilterDefinitionCollection $filterSet,
string $elasticsearchColumn,
int $page,
int $limit = 25,
): Collection {
$boolQuery = $filterSet->getBoolQuery(
$filterSet->extractElasticSearchQueries([
FilterDefinitionQuery::instance()
->setQuery(new Exists('stats.' . $elasticsearchColumn))
->setPath('stats', 'stats'),
])
);
return $this->queryDrillDownResults($consumer, $boolQuery, $page, $limit);
}
public function getConversationsActivityChannelPerUserAggregation(FilterDefinitionCollection $filterSet): Collection
{
$boolQuery = $filterSet->getBoolQuery(
$filterSet->extractElasticSearchQueries()
);
$query = (new Query())
->setSize(0)
->setSource(false)
->addAggregation(
(new Filter('activities', $boolQuery))
->addAggregation(
(new AggregationTerms('channel'))
->setField('type.keyword')
->setSize(self::AGG_TERMS_MAX_SIZE)
->addAggregation(
(new AggregationTerms('by_user'))
->setField('user.id_string.keyword')
->setSize(self::AGG_TERMS_MAX_SIZE)
->addAggregation(
(new Sum('total_duration'))->setField('duration')
)
->addAggregation(
new ValueCount('volume', 'id_string.keyword')
)
->addAggregation(
(new Filter('volume_connected', new Exists('actual_start_time')))
->addAggregation(
new ValueCount('volume', 'id_string.keyword')
)
)
->addAggregation(
(new Filter('logged_to_crm', new Exists('crm_provider_id')))
->addAggregation(
new ValueCount('volume', 'id_string.keyword')
)
)
)
->addAggregation(
new AvgBucket('avg_duration', 'by_user>total_duration')
)
->addAggregation(
new AvgBucket('avg_volume_all', 'by_user>volume')
)
->addAggregation(
new AvgBucket('avg_volume_logged', 'by_user>logged_to_crm>volume')
)
->addAggregation(
new AvgBucket('avg_volume_connected', 'by_user>volume_connected>volume')
)
)
);
$results = $this->searchService
->search($query, $this->model, 'getConversationsActivityChannelPerUserAggregation')
->getAggregations();
$connectableActivityChannels = [
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
Activity::TYPE_CONFERENCE,
];
return Collection::make(array_get($results, 'activities.channel.buckets', []))
->map(static function (array $bucket) use ($connectableActivityChannels): array {
$channel = $bucket['key'];
$avgVolume = (float) array_get($bucket, 'avg_volume_all.value', 0);
$avgVolumeLogged = (float) array_get($bucket, 'avg_volume_logged.value', 0);
$avgVolumeConnected = (float) array_get($bucket, 'avg_volume_connected.value', 0);
$avgDuration = (float) array_get($bucket, 'avg_duration.value', 0);
return [
'channel' => $channel,
'stats' => [
'avg_volume' => $avgVolume,
'avg_volume_logged' => $avgVolumeLogged,
'avg_volume_connected' => $avgVolumeConnected,
'avg_duration' => $avgDuration,
],
'per_user' => Collection::make(array_get($bucket, 'by_user.buckets', []))
->keyBy('key')
->map(static function (array $userBucket) use ($channel, $connectableActivityChannels): array {
$data = [
'volume_logged' => (int) array_get($userBucket, 'logged_to_crm.doc_count', 0),
'volume' => (int) $userBucket['doc_count'],
'duration' => (float) array_get($userBucket, 'total_duration.value', 0),
];
if (in_array($channel, $connectableActivityChannels, true)) {
$data['volume_connected'] = (int) array_get($userBucket, 'volume_connected.doc_count');
}
return $data;
})
->all(),
];
});
}
public function getDashboardActivityOverTime(
User $user,
FilterDefinitionCollection $filterSet,
string $histogramInterval = 'day'
): Collection {
$timezoneOffset = $user->getTimezoneOffset();
$boolQuery = $filterSet->getBoolQuery(
$filterSet->extractElasticSearchQueries()
);
$query = (new Query())
->setSize(0)
->setSource(false)
->addAggregation(
(new Filter(
'voice_activities',
(clone $boolQuery)
->addFilter(
(new BoolQuery())
->addShould((new Term())->setTerm('type.keyword', Activity::TYPE_CONFERENCE))
->addShould((new Term())->setTerm('type.keyword', Activity::TYPE_SOFTPHONE))
->addShould((new Term())->setTerm('type.keyword', Activity::TYPE_SOFTPHONE_INBOUND))
)
->addFilter(
new Exists('actual_start_time')
)
))
->addAggregation(
(new DateHistogram('over_time', 'actual_end_time', $histogramInterval))
->setFormat('8uuuu-MM-dd')
->setTimezone($timezoneOffset)
->setMinimumDocumentCount(0)
->addAggregation(
(new AggregationTerms('by_channel'))
->setField('type.keyword')
->setSize(self::AGG_TERMS_MAX_SIZE)
)
)
)
->addAggregation(
(new Filter(
'text_activities',
(clone $boolQuery)
->addFilter(
(new Term())->setTerm('type.keyword', Activity::TYPE_SMS_OUTBOUND)
)
))
->addAggregation(
(new DateHistogram('over_time', 'created_at', $histogramInterval))
->setFormat('8uuuu-MM-dd')
->setTimezone($timezoneOffset)
->setMinimumDocumentCount(0)
->addAggregation(
(new AggregationTerms('by_channel'))
->setField('type.keyword')
->setSize(self::AGG_TERMS_MAX_SIZE)
)
)
);
$aggregationData = $this->searchService->search($query, $this->model, 'getDashboardActivityOverTime')->getAggregations();
$activityData = Collection::make([
array_get($aggregationData, 'voice_activities.over_time.buckets'),
array_get($aggregationData, 'text_activities.over_time.buckets'),
])
->collapse()
->reduce(
static function (array $carry, array $bucketData): array {
$byChannel = Collection::make(array_get($bucketData, 'by_channel.buckets'))
->keyBy('key')
->map(static function (array $bucketData): int {
return $bucketData['doc_count'];
});
$date = $bucketData['key_as_string'];
if (array_key_exists($date, $carry)) {
$byChannel = $byChannel->merge($carry[$date]);
}
$carry[$date] = $byChannel->all();
return $carry;
},
[]
);
return Collection::make($activityData);
}
public function getDashboardCoachingOverTime(
User $user,
FilterDefinitionCollection $filterSet,
?CarbonImmutable $dateTimeRangeStartsAt,
?CarbonImmutable $dateTimeRangeEndsAt,
string $histogramInterval = 'day',
): Collection {
$filterDefinitionQueries = $filterSet->extractElasticSearchQueries();
$currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('plays');
$boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);
$timezone = $user->getTimezone();
$timezoneOffset = $user->getTimezoneOffset();
$hasDateRange = $dateTimeRangeStartsAt !== null && $dateTimeRangeEndsAt !== null;
if ($hasDateRange) {
$resultSetKey = 'from_as_string';
$dateTimeRangeStartsAt = $dateTimeRangeStartsAt->setTimezone($timezone)->toImmutable();
$dateTimeRangeEndsAt = $dateTimeRangeEndsAt->setTimezone($timezone)->toImmutable();
$dateRangeAggregation = (new DateRange('over_time'))
->setField('plays.created_at')
->setFormat('8uuuu-MM-dd')
->setParam('time_zone', $timezoneOffset);
if ($histogramInterval === 'day') {
$increment = static function (CarbonImmutable $dateTime): CarbonImmutable {
return $dateTime->addDay();
};
} elseif ($histogramInterval === 'hour') {
$increment = static function (CarbonImmutable $dateTime): CarbonImmutable {
return $dateTime->addHour();
};
} else {
throw new OutOfBoundsException('Unknown date time interval');
}
$periodStartsAt = $dateTimeRangeStartsAt;
while (true) {
$periodEndsAt = $increment($periodStartsAt);
$dateRangeAggregation->addRange(
$periodStartsAt->format('Y-m-d'),
$periodEndsAt->format('Y-m-d')
);
if ($periodEndsAt >= $dateTimeRangeEndsAt) {
break;
}
$periodStartsAt = clone $periodEndsAt;
}
} else {
$resultSetKey = 'key_as_string';
$dateRangeAggregation = (new DateHistogram('over_time', 'plays.created_at', $histogramInterval))
->setTimezone($timezoneOffset)
->setFormat('8uuuu-MM-dd')
->setMinimumDocumentCount(0);
}
$query = (new Query())
->setSize(0)
->setSource(false)
->addAggregation(
(new Filter('activities', $boolQuery))
->addAggregation(
(new Nested('playback', 'plays'))
->addAggregation(
(new Filter('filtered', $currentPriorityQuery))
->addAggregation($dateRangeAggregation)
)
)
);
$aggregationData = $this->searchService->search($query, $this->model, 'getDashboardCoachingOverTime')->getAggregations();
return Collection::make(array_get($aggregationData, 'activities.playback.filtered.over_time.buckets'))
->keyBy($resultSetKey)
->map(static function (array $bucketData): int {
return $bucketData['doc_count'];
});
}
public function getDashboardCoachingBreakdownListensByUserRole(FilterDefinitionCollection $filterSet): Collection
{
$filterDefinitionQueries = $filterSet->extractElasticSearchQueries();
$currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('plays');
$boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);
$query = (new Query())
->setSize(0)
->setSource(false)
->addAggregation(
(new Filter('activities', $boolQuery))
->addAggregation(
(new AggregationTerms('by_user'))
->setField('user.id_string.keyword')
->setSize(self::AGG_TERMS_MAX_SIZE)
->addAggregation(
(new Nested('played_by', 'plays'))
->addAggregation(
(new Filter('filtered', $currentPriorityQuery))
->addAggregation(
(new AggregationTerms('user'))
->setField('plays.user.id_string.keyword')
->setSize(self::AGG_TERMS_MAX_SIZE)
->addAggregation(
(new AggregationTerms('role'))
->setField('plays.user.roles.name')
->setSize(self::AGG_TERMS_MAX_SIZE)
)
)
)
)
)
);
$aggregationData = $this->searchService
->search($query, $this->model, 'getDashboardCoachingBreakdownListensByUserRole')
->getAggregations();
return Collection::make(array_get($aggregationData, 'activities.by_user.buckets'))
->keyBy('key')
->map(static function (array $roomOwnerBucket): array {
return Collection::make(array_get($roomOwnerBucket, 'played_by.filtered.user.buckets'))
->map(static function (array $playbackBucket): array {
$userId = $playbackBucket['key'];
$count = $playbackBucket['doc_count'];
$userRoles = Collection::make(array_get($playbackBucket, 'role.buckets'))
->keyBy('key')
->keys();
return [
'count' => $count,
'userId' => $userId,
'userRoles' => $userRoles->all(),
];
})
->all();
});
}
public function getDashboardCoachingBreakdownCoachingFocusFilledByUserRole(
FilterDefinitionCollection $filterSet
): Collection {
$filterDefinitionQueries = $filterSet->extractElasticSearchQueries();
$currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('comments');
$boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);
$query = (new Query($boolQuery))
->setSize(0)
->setSource(false)
->addAggregation(
(new AggregationTerms('by_user'))
->setField('user.id_string.keyword')
->setSize(self::AGG_TERMS_MAX_SIZE)
->addAggregation(
(new Nested('commented_by', 'comments'))
->addAggregation(
(new Filter('filtered', $currentPriorityQuery))
->addAggregation(
(new AggregationTerms('user'))
->setField('comments.user.id_string.keyword')
->setSize(self::AGG_TERMS_MAX_SIZE)
->addAggregation(
(new AggregationTerms('role'))
->setField('comments.user.roles.name')
->setSize(self::AGG_TERMS_MAX_SIZE)
)
)
)
)
);
$aggregationData = $this->searchService
->search($query, $this->model, 'getDashboardCoachingBreakdownCoachingFocusByUserRole')
->getAggregations();
return Collection::make(array_get($aggregationData, 'by_user.buckets'))
->keyBy('key')
->map(static function (array $roomOwnerBucket): array {
return Collection::make(array_get($roomOwnerBucket, 'commented_by.filtered.user.buckets'))
->map(static function (array $commentBucket): array {
$userId = $commentBucket['key'];
$count = $commentBucket['doc_count'];
$userRoles = Collection::make(array_get($commentBucket, 'role.buckets'))
->keyBy('key')
->keys();
return [
'count' => $count,
'userId' => $userId,
'userRoles' => $userRoles->all(),
];
})
->all();
});
}
private function getCompositeAggregationBy(
BoolQuery $boolQuery,
AbstractAggregation $sourceAggregation,
?array $customAggregations = null,
?AbstractAggregation $aggregationParent = null,
?callable $compositeAggregationExtractor = null,
?AbstractAggregation $immediateAggregationParent = null
): LazyCollection {
if ($compositeAggregationExtractor === null) {
$compositeAggregationExtractor = static fn (ResultSet $aggregationData): array => $aggregationData
->getAggregation('composite')['buckets'];
}
return LazyCollection::make(
function () use (
$sourceAggregation,
$customAggregations,
$aggregationParent,
$immediateAggregationParent,
$boolQuery,
$compositeAggregationExtractor,
): Generator {
$compositeAggregation = (new Composite('composite'))
->setSize(self::AGG_COMPOSITE_MAX_SIZE)
->addSource($sourceAggregation);
if (is_array($customAggregations)) {
foreach ($customAggregations as $customAggregation) {
$compositeAggregation->addAggregation($customAggregation);
}
}
$aggregation = $compositeAggregation;
if ($aggregationParent instanceof AbstractAggregation) {
$aggregation = $aggregationParent;
if ($immediateAggregationParent instanceof AbstractAggregation) {
$immediateAggregationParent->addAggregation($compositeAggregation);
} else {
$aggregationParent->addAggregation($compositeAggregation);
}
}
while (true) {
$query = (new Query($boolQuery))
->setSource(false)
->addAggregation($aggregation);
$aggregationData = $this->searchService
->search($query, $this->model, 'getCompositeAggregationBy');
foreach ($compositeAggregationExtractor($aggregationData) as $bucket) {
yield $bucket;
}
$cursor = array_get($aggregationData, 'composite.after_key', null);
if (! is_array($cursor)) {
break;
}
$compositeAggregation->addAfter($cursor);
}
},
);
}
private function getCompositeAggregationByUser(
BoolQuery $boolQuery,
?array $customAggregations = null,
?callable $callback = null,
): LazyCollection {
if ($callback === null) {
$callback = static fn (array $bucket): int => $bucket['doc_count'];
}
$sourceAggregation = (new AggregationTerms('by_user'))
->setField('user.id_string.keyword');
return $this
->getCompositeAggregationBy(
boolQuery: $boolQuery,
sourceAggregation: $sourceAggregation,
customAggregations: $customAggregations,
)
->mapWithKeys(static fn (array $bucket): array => [
$bucket['key']['by_user'] => $bucket,
])
->map($callback);
}
private function getCompositeAggregationByCoachingFeedbackCoach(
BoolQuery $boolQuery,
?array $customAggregations = null
): LazyCollection {
return $this
->getCompositeAggregationBy(
$boolQuery,
(new AggregationTerms('by_feedback_id'))
->setField('coachingFeedbacks.id_string'),
$customAggregations,
new Nested('coachingFeedbacks', 'coachingFeedbacks'),
static fn (ResultSet $aggregationData): array => $aggregationData
->getAggregation('coachingFeedbacks')['composite']['buckets']
);
}
/**
* @param Collection|string[] $statsOfInterest
*
* @return Collection|AbstractAggregation[]
*/
private function getEngagementStatsAggregation(Collection $statsOfInterest): Collection
{
$getAggregation = static function (string $propertyName): Filter {
$totalAmountProperties = [
'talk_time_ratio',
'longest_user_monologue',
'longest_customer_monologue',
'talking_speed',
'user_questions',
];
$propertyPath = 'stats.' . $propertyName;
$filterQuery = (new BoolQuery())
->addMust(new Exists($propertyPath));
if (in_array($propertyName, $totalAmountProperties, true)) {
$filterQuery->addMust(
new Range(
$propertyPath,
[
'gt' => 0,
]
)
);
}
return (new Filter($propertyName, $filterQuery))
->addAggregation(
(new Sum('data'))->setField($propertyPath)
);
};
return Collection::make()
->push(
$statsOfInterest
->reduce(
static function (Nested $carry, string $propertyName) use ($getAggregation): Nested {
return $carry->addAggregation($getAggregation($propertyName));
},
new Nested('stats', 'stats')
)
);
}
public function getDashboardEngagementStats(FilterDefinitionCollection $filterSet): Collection
{
$statsOfInterest = Collection::make([
'talkTimeRatio' => 'talk_time_ratio',
'longestMonologue' => 'longest_user_monologue',
'longestCustomerStory' => 'longest_customer_monologue',
'talkingSpeed' => 'talking_speed',
'patience' => 'patience_time',
'questionRate' => 'user_questions',
]);
$boolQuery = $filterSet->getBoolQuery(
$filterSet->extractElasticSearchQueries()
);
$aggregationData = $this
->getCompositeAggregationByUser(
$boolQuery,
$this->getEngagementStatsAggregation($statsOfInterest)->toArray(),
static function (array $bucket): array {
$statsBucket = $bucket['stats'];
unset($statsBucket['doc_count']);
return Collection::make($statsBucket)
->map(static function (array $stat): array {
return [
'count' => $stat['doc_count'],
'value' => $stat['data']['value'],
];
})
->toArray();
}
)
->collect();
return $statsOfInterest
->map(static function (string $propertyName) use ($aggregationData): ?float {
[$count, $value] = $aggregationData->reduce(
static function (array $accumulator, array $bucket) use ($propertyName): array {
$accumulator[0] += (int) $bucket[$propertyName]['count'];
$accumulator[1] += (float) $bucket[$propertyName]['value'];
return $accumulator;
},
[0, 0]
);
if ($count === 0) {
return null;
}
return (float) ($value / $count);
});
}
public function getCoachingFeedbacksFilledPerUserAggregation(FilterDefinitionCollection $filterSet): Collection
{
$filterDefinitionQueries = $filterSet->extractElasticSearchQueries([
FilterDefinitionQuery::instance()
->setQuery((new Term())->setTerm('coachingFeedbacks.visibility', CoachingFeedback::VISIBLE_TO_ALL))
->setPath('coachingFeedbacks', 'coachingFeedbacks'),
FilterDefinitionQuery::instance()
->setQuery((new Term())->setTerm('coachingFeedbacks.coach.status', User::STATUS_ACTIVE))
->setPath('coachingFeedbacks', 'coachingFeedbacks'),
]);
$currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('coachingFeedbacks');
$boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);
$data = $this
->getCompositeAggregationByUser(
$boolQuery,
[
(new Nested('feedbacks', 'coachingFeedbacks'))
->addAggregation(
(new Filter('filtered', $currentPriorityQuery))
->addAggregation(
(new AggregationTerms('by_user'))
->setField('coachingFeedbacks.coach.id_string')
->setSize(self::AGG_TERMS_MAX_SIZE)
)
),
],
static function (array $bucket): array {
return array_get($bucket, 'feedbacks.filtered.by_user.buckets', []);
}
)
->reduce(
static function (array $carry, array $buckets): array {
foreach ($buckets as $bucket) {
$userId = $bucket['key'];
if (! array_key_exists($userId, $carry)) {
$carry[$userId] = 0;
}
$carry[$userId] += $bucket['doc_count'];
}
return $carry;
},
[]
);
return Collection::make($data);
}
public function getCoachingFeedbacksReceivedPerUserAggregation(FilterDefinitionCollection $filterSet): Collection
{
$filterDefinitionQueries = $filterSet->extractElasticSearchQueries([
FilterDefinitionQuery::instance()
->setQuery((new Term())->setTerm('coachingFeedbacks.visibility', CoachingFeedback::VISIBLE_TO_ALL))
->setPath('coachingFeedbacks', 'coachingFeedbacks'),
FilterDefinitionQuery::instance()
->setQuery((new Term())->setTerm('coachingFeedbacks.coach.status', User::STATUS_ACTIVE))
->setPath('coachingFeedbacks', 'coachingFeedbacks'),
]);
$currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('coachingFeedbacks');
$boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);
$data = $this
->getCompositeAggregationByUser(
$boolQuery,
[
(new Nested('feedbacks', 'coachingFeedbacks'))
->addAggregation(
(new Filter('filtered', $currentPriorityQuery))
->addAggregation(
(new AggregationTerms('by_user'))
->setField('coachingFeedbacks.coachee.id_string')
->setSize(self::AGG_TERMS_MAX_SIZE)
->addAggregation(
new ValueCount('volume', 'coachingFeedbacks.id_string'),
...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20891-improve-sms-text-relays, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.08843085,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20891-improve-sms-text-relays","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.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"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.10472074,"top":0.17956904,"width":0.008643617,"height":0.01915403},"on_screen":true,"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.11735372,"top":0.17877094,"width":0.00731383,"height":0.017557861},"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"CONTEXT_TEAM_INSIGHTS_ACTIVITY","depth":4,"bounds":{"left":0.12832446,"top":0.17877094,"width":0.078457445,"height":0.015961692},"on_screen":true,"value":"CONTEXT_TEAM_INSIGHTS_ACTIVITY","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.21575798,"top":0.17877094,"width":0.00731383,"height":0.017557861},"on_screen":true,"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.22573139,"top":0.17877094,"width":0.00731383,"height":0.017557861},"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Words","depth":3,"bounds":{"left":0.234375,"top":0.17877094,"width":0.00731383,"height":0.017557861},"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Regex","depth":3,"bounds":{"left":0.24301861,"top":0.17877094,"width":0.00731383,"height":0.017557861},"on_screen":true,"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.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Replace","depth":4,"on_screen":false,"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.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"0 results","depth":4,"bounds":{"left":0.25664893,"top":0.17797287,"width":0.025598405,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Occurrence","depth":4,"bounds":{"left":0.28224733,"top":0.17717478,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Occurrence","depth":4,"bounds":{"left":0.29089096,"top":0.17717478,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Filter Search Results","depth":4,"bounds":{"left":0.2995346,"top":0.17717478,"width":0.008643617,"height":0.01915403},"on_screen":true,"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.3081782,"top":0.17717478,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Click to highlight","depth":4,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":4,"bounds":{"left":0.3949468,"top":0.17717478,"width":0.008643617,"height":0.01915403},"on_screen":true,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.3636968,"top":0.20830008,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.37300533,"top":0.20830008,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"11","depth":4,"bounds":{"left":0.38297874,"top":0.20830008,"width":0.008976064,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39361703,"top":0.20670392,"width":0.00731383,"height":0.018355945},"on_screen":true,"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.40093085,"top":0.20670392,"width":0.006981383,"height":0.018355945},"on_screen":true,"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\\Repositories;\n\nuse Carbon\\Carbon;\nuse Carbon\\CarbonImmutable;\nuse Elastica\\Aggregation\\AbstractAggregation;\nuse Elastica\\Aggregation\\AvgBucket;\nuse Elastica\\Aggregation\\Composite;\nuse Elastica\\Aggregation\\DateHistogram;\nuse Elastica\\Aggregation\\DateRange;\nuse Elastica\\Aggregation\\Filter;\nuse Elastica\\Aggregation\\Nested;\nuse Elastica\\Aggregation\\Sum;\nuse Elastica\\Aggregation\\Terms as AggregationTerms;\nuse Elastica\\Aggregation\\ValueCount;\nuse Elastica\\Document;\nuse Elastica\\Query;\nuse Elastica\\Query\\BoolQuery;\nuse Elastica\\Query\\Exists;\nuse Elastica\\Query\\Range;\nuse Elastica\\Query\\Term;\nuse Elastica\\Query\\Terms;\nuse Elastica\\Result;\nuse Elastica\\ResultSet;\nuse Generator;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\LazyCollection;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinitionCollection;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinitionQuery;\nuse Jiminny\\Component\\ElasticSearch\\Service\\Search;\nuse Jiminny\\Component\\Math\\BitwiseOperations;\nuse Jiminny\\Exceptions\\OutOfBoundsException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Activity\\Comment;\nuse Jiminny\\Models\\CoachingFeedback;\nuse Jiminny\\Models\\User;\n\nclass TeamInsightsRepository\n{\n use BitwiseOperations;\n\n public const array CONVERSATION_DRILLDOWNS = [\n self::CONVERSATION_DRILLDOWN_SCHEDULED,\n self::CONVERSATION_DRILLDOWN_ATTEMPTED,\n self::CONVERSATION_DRILLDOWN_CONNECTED,\n self::CONVERSATION_DRILLDOWN_LOGGED,\n ];\n\n public const string CONVERSATION_DRILLDOWN_CONNECTED = 'connected';\n public const string CONVERSATION_DRILLDOWN_SCHEDULED = 'scheduled';\n public const string CONVERSATION_DRILLDOWN_ATTEMPTED = 'attempted';\n public const string CONVERSATION_DRILLDOWN_LOGGED = 'logged';\n public const array DRILL_DOWN_MAP = [\n 'id_string' => 'id',\n 'title' => 'title',\n 'user.id_string' => null, // needed for indirect mapping\n 'user.name' => 'organizer.name',\n 'user.job.name' => 'organizer.job.name',\n 'user.photo_url' => 'organizer.photoUrl',\n 'type' => 'type',\n\n 'lead.name' => 'prospect.lead.name',\n 'lead.company' => 'prospect.lead.company',\n\n 'contact.name' => 'prospect.contact.name',\n 'contact.account.name' => 'prospect.contact.account.name',\n\n 'account.name' => 'prospect.account.account.name',\n\n 'participants.user.id_string' => null, // this would return an array and needs manual mapping\n 'participants.country_code' => null, // this would return an array and needs manual mapping\n 'participants.phone_number' => null, // this would return an array and needs manual mapping\n\n 'favorite_count' => 'stats.favorites',\n 'share_count' => 'stats.shares',\n 'comment_count' => 'stats.comments',\n 'play_count' => 'stats.plays',\n\n 'stats.talk_time_ratio' => 'stats.talkTimeRatio',\n 'stats.talking_speed' => 'stats.talkingSpeed',\n 'stats.user_questions' => 'stats.userQuestionsCount',\n 'stats.longest_user_monologue' => 'stats.longestUserMonologue',\n 'stats.longest_customer_monologue' => 'stats.longestCustomerMonologue',\n 'stats.patience_time' => 'stats.patienceTime',\n\n 'plays.user.id_string' => null, // this would return an array and needs manual mapping\n 'average_score' => 'averageScore',\n 'ai_call_score.score' => 'aiCallScore',\n 'category.name' => 'category.name',\n 'opportunity.value' => 'opportunity.value',\n 'opportunity.currency_code' => 'opportunity.currency_code',\n 'opportunity.stage.label' => 'opportunity.stage.label',\n 'duration' => 'duration',\n 'actual_end_time' => 'actualEndTime',\n 'tracks.telephony_provider_id' => null, // this would return an array and needs manual mapping\n 'coachingFeedbacks.coach.name' => null, // this would return an array and needs manual mapping\n ];\n\n public const int FLAG_DRILLDOWN_COMMENT_POSITIVE = 1;\n public const int FLAG_DRILLDOWN_COMMENT_NEGATIVE = 2;\n\n private const int AGG_TERMS_MAX_SIZE = 9999;\n private const int AGG_COMPOSITE_MAX_SIZE = 10000;\n\n private const int AGGREGATE_VALUE_AVG = 0;\n private const int AGGREGATE_VALUE_MAX = 1;\n private const int AGGREGATE_VALUE_SUM = 2;\n\n public function __construct(\n private readonly Search $searchService,\n private readonly Activity $model,\n ) {\n }\n\n /**\n * Ensure prospect.name is present and non-empty by applying a type-based fallback\n * from the original dot-notated data structure.\n *\n * - type lead => prospect.lead.name\n * - type contact => prospect.contact.name\n * - type account => prospect.account.account.name or prospect.account.name\n */\n private static function ensureProspectNameFallback(array $data, array $originalDotData): array\n {\n if (! array_key_exists('prospect', $data) || ! is_array($data['prospect'])\n || ! array_key_exists('type', $data['prospect'])\n ) {\n return $data;\n }\n\n $existingName = $data['prospect']['name'] ?? null;\n $needsFallback = ! is_string($existingName) || trim($existingName) === '';\n\n if (! $needsFallback) {\n return $data;\n }\n\n $prospectName = null;\n switch ($data['prospect']['type']) {\n case 'lead':\n $prospectName = $originalDotData['prospect.lead.name'] ?? null;\n\n break;\n case 'contact':\n $prospectName = $originalDotData['prospect.contact.name'] ?? null;\n\n break;\n case 'account':\n // Some indices nest account name under account.account.name\n $prospectName = $originalDotData['prospect.account.account.name']\n ?? $originalDotData['prospect.account.name']\n ?? null;\n\n break;\n }\n\n // Normalize whitespace-only fallback names to null (mirror $existingName check)\n if (is_string($prospectName)) {\n $prospectName = trim($prospectName);\n if ($prospectName === '') {\n $prospectName = null;\n }\n }\n\n // set the name key no matter if empty\n $data['prospect']['name'] = $prospectName;\n\n return $data;\n }\n\n public function exportConversationsPerUser(FilterDefinitionCollection $filterSet, array $include): LazyCollection\n {\n $boolQuery = $filterSet->getBoolQuery(\n $filterSet->extractElasticSearchQueries()\n );\n\n $query = (new Query($boolQuery))\n ->setSize(1000)\n ->setSource([\n 'includes' => $include,\n ])\n ->setSort([\n 'scheduled_start_time' => 'asc',\n ]);\n\n return LazyCollection::make(function () use ($query): Generator {\n $scroll = $this->searchService->scroll($query, $this->model);\n\n foreach ($scroll as $resultSet) {\n yield $resultSet;\n }\n })\n ->flatMap(static function (ResultSet $resultSet): array {\n return $resultSet->getResults();\n })\n ->map(static function (Result $result): array {\n $hit = $result->getHit();\n\n return $hit['_source'];\n });\n }\n\n private function queryDrillDownResults(User $consumer, BoolQuery $boolQuery, int $page, int $limit = 25): Collection\n {\n if ($page <= 0) {\n throw new OutOfBoundsException('The page number can only be greater or equal to one');\n }\n\n $map = Collection::make(self::DRILL_DOWN_MAP);\n\n $query = (new Query($boolQuery))\n ->setSort(['scheduled_start_time'])\n ->setSource($map->keys()->all());\n\n if ($limit === 0) {\n $query->setSize(1000);\n $documents = LazyCollection::make(function () use ($query): Generator {\n $scroll = $this->searchService->scroll($query, $this->model);\n\n foreach ($scroll as $resultSet) {\n yield $resultSet;\n }\n })\n ->flatMap(static function (ResultSet $resultSet): array {\n return $resultSet->getDocuments();\n });\n } else {\n $query\n ->setSize($limit)\n ->setFrom(($page - 1) * $limit);\n\n $documents = $this->searchService->search($query, $this->model, 'queryDrillDownResults')->getDocuments();\n }\n\n return Collection::make($documents)\n ->map(static function (Document $document) use ($map): array {\n $documentDataRaw = $document->getData();\n $documentData = array_dot($documentDataRaw);\n\n $data = [];\n foreach ($map as $fromKey => $toKey) {\n if ($toKey === null) {\n continue;\n }\n\n array_set($data, $toKey, $documentData[$fromKey] ?? null);\n }\n $roomOwnerId = $documentData['user.id_string'];\n\n $data['played'] = Collection::make($documentDataRaw['plays'] ?? [])\n ->map(static function (array $data): string {\n return $data['user']['id_string'];\n })\n ->all();\n\n $data['from'] = [\n 'national_phone_number' => Collection::make($documentDataRaw['participants'] ?? [])\n ->filter(static function (array $data) use ($roomOwnerId): bool {\n return array_key_exists('user', $data) && $data['user']['id_string'] === $roomOwnerId;\n })\n ->map(static function (array $data): ?string {\n return $data['phone_number'];\n })\n ->first(),\n ];\n\n $data['isRecorded'] = Collection::make($documentDataRaw['tracks'] ?? [])\n ->filter(static function (array $track): bool {\n return $track['telephony_provider_id'] !== null;\n })\n ->isNotEmpty();\n\n $data['coaches'] = Collection::make(\n is_array($documentDataRaw)\n && array_key_exists('coachingFeedbacks', $documentDataRaw)\n && is_array($documentDataRaw['coachingFeedbacks'])\n ? $documentDataRaw['coachingFeedbacks']\n : [],\n );\n\n return $data;\n })\n ->map(static function (array $data) use ($consumer): array {\n $dotData = array_dot($data);\n // Keep a copy of the original dot data so we can derive names after reshaping\n $originalDotData = $dotData;\n\n // map prospect to lead/contact/account\n if (array_key_exists('prospect.lead.name', $dotData) && $dotData['prospect.lead.name'] !== null) {\n $data['prospect'] = array_get($data, 'prospect.lead');\n $data['prospect']['type'] = 'lead';\n } elseif (array_key_exists('prospect.contact.name', $dotData) && $dotData['prospect.contact.name'] !== null) {\n $data['prospect'] = array_get($data, 'prospect.contact');\n $data['prospect']['type'] = 'contact';\n } elseif (\n array_key_exists('prospect.account.account.name', $dotData)\n && $dotData['prospect.account.account.name'] !== null\n ) {\n $data['prospect'] = array_get($data, 'prospect.account');\n $data['prospect']['type'] = 'account';\n } else {\n unset($data['prospect']);\n }\n\n // Ensure prospect.name is present; if missing or empty, fill using type-based fallback\n $data = self::ensureProspectNameFallback($data, $originalDotData);\n\n $dotData = array_dot($data);\n $nationalPhoneNumber = $dotData['from.national_phone_number'];\n unset($data['from']);\n\n $data['title'] = getActivityTitleAttribute(\n $dotData['organizer.name'],\n $dotData['type'],\n $dotData['title'],\n $dotData['prospect.name'] ?? null,\n $nationalPhoneNumber\n );\n\n if (array_key_exists('category.name', $dotData) && $dotData['category.name'] === null) {\n unset($data['category']['name']);\n }\n\n if (array_key_exists('category', $data) && empty($data['category'])) {\n unset($data['category']);\n }\n\n // Include opportunity if either a value OR a stage label exists.\n $hasOpportunityValue = array_key_exists('opportunity.value', $dotData)\n && $dotData['opportunity.value'] !== null;\n $hasOpportunityStage = array_key_exists('opportunity.stage.label', $dotData)\n && $dotData['opportunity.stage.label'] !== null;\n\n if ($hasOpportunityValue || $hasOpportunityStage) {\n $data['opportunity'] = [];\n\n if ($hasOpportunityValue) {\n $data['opportunity']['formattedValue'] = formatOpportunityValue(\n (float) $dotData['opportunity.value'],\n $dotData['opportunity.currency_code'],\n );\n }\n\n if ($hasOpportunityStage) {\n $data['opportunity']['stage'] = [\n 'label' => $dotData['opportunity.stage.label'],\n ];\n }\n } else {\n unset($data['opportunity']);\n }\n\n if (array_key_exists('played', $data) && is_array($data['played'])) {\n $data['played'] = in_array($consumer->id_string, $data['played'], true);\n }\n\n if (array_key_exists('duration', $data) && $data['duration'] !== null) {\n $data['durationForHumans'] = secondsToHuman((int) $dotData['duration']);\n }\n unset($data['duration']);\n\n return $data;\n })\n // Convert database times to ISO 8601 or frontend will apply timezone conversions\n ->map(static function (array $data): array {\n if (isset($data['actualEndTime'])) {\n $data['actualEndTime'] = Carbon::createFromFormat('Y-m-d H:i:s', $data['actualEndTime'])->toIso8601String();\n }\n\n return $data;\n });\n }\n\n public function getConversationsPerActivityChannelDrillDown(\n User $consumer,\n FilterDefinitionCollection $filterSet,\n string $activityChannel,\n string $drillDownType,\n int $page,\n int $limit = 25,\n ): Collection {\n $boolQuery = $filterSet\n ->getBoolQuery($filterSet->extractElasticSearchQueries())\n ->addMust((new Term())->setTerm('type.keyword', $activityChannel));\n\n switch ($drillDownType) {\n case self::CONVERSATION_DRILLDOWN_SCHEDULED:\n case self::CONVERSATION_DRILLDOWN_ATTEMPTED:\n $boolQuery->addMust(new Exists('scheduled_start_time'));\n\n break;\n case self::CONVERSATION_DRILLDOWN_CONNECTED:\n $boolQuery->addMust(new Exists('actual_start_time'));\n\n break;\n case self::CONVERSATION_DRILLDOWN_LOGGED:\n $boolQuery->addMust(new Exists('crm_provider_id'));\n\n break;\n default:\n throw new OutOfBoundsException('Unsupported drill down type');\n }\n\n return $this->queryDrillDownResults($consumer, $boolQuery, $page, $limit);\n }\n\n public function getCoachingActivitiesDrillDown(\n User $consumer,\n FilterDefinitionCollection $filterSet,\n int $flags,\n int $page,\n int $limit = 25,\n ): Collection {\n $extraFilterDefinitionQueries = [];\n\n if ($this->isBitwiseFlagEnabled($flags, self::FLAG_DRILLDOWN_COMMENT_NEGATIVE)) {\n $extraFilterDefinitionQueries[] = FilterDefinitionQuery::instance()\n ->setQuery(new Terms('comments.type', [Comment::TYPE_GAME_CHANGER]))\n ->setPath('comments', 'comments');\n }\n\n if ($this->isBitwiseFlagEnabled($flags, self::FLAG_DRILLDOWN_COMMENT_POSITIVE)) {\n $extraFilterDefinitionQueries[] = FilterDefinitionQuery::instance()\n ->setQuery(new Terms('comments.type', [Comment::TYPE_POSITIVE]))\n ->setPath('comments', 'comments');\n }\n\n $boolQuery = $filterSet->getBoolQuery(\n $filterSet->extractElasticSearchQueries($extraFilterDefinitionQueries)\n );\n\n return $this->queryDrillDownResults($consumer, $boolQuery, $page, $limit);\n }\n\n public function getCoachingActivitiesOverTimeDrillDown(\n User $consumer,\n FilterDefinitionCollection $filterSet,\n User $coachee,\n ?string $sectionId,\n int $page,\n int $limit = 25\n ): Collection {\n $extraFilterSetQueries = [\n FilterDefinitionQuery::instance()\n ->setQuery((new Term())->setTerm('coachingFeedbacks.visibility', CoachingFeedback::VISIBLE_TO_ALL))\n ->setPath('coachingFeedbacks', 'coachingFeedbacks'),\n FilterDefinitionQuery::instance()\n ->setQuery((new Term())->setTerm('coachingFeedbacks.coachee.id_string', $coachee->id_string))\n ->setPath('coachingFeedbacks.coachee', 'coachingFeedbacks'),\n ];\n\n if (is_string($sectionId)) {\n $extraFilterSetQueries[] = FilterDefinitionQuery::instance()\n ->setQuery((new Term())->setTerm('coachingFeedbacks.sectionFeedbacks.section.id_string', $sectionId))\n ->setPath('coachingFeedbacks.sectionFeedbacks.section', 'coachingFeedbacks.sectionFeedbacks');\n }\n\n $boolQuery = $filterSet->getBoolQuery(\n $filterSet->extractElasticSearchQueries($extraFilterSetQueries)\n );\n\n return $this->queryDrillDownResults($consumer, $boolQuery, $page, $limit);\n }\n\n public function getEngagementActivitiesDrillDown(\n User $consumer,\n FilterDefinitionCollection $filterSet,\n string $elasticsearchColumn,\n int $page,\n int $limit = 25,\n ): Collection {\n $boolQuery = $filterSet->getBoolQuery(\n $filterSet->extractElasticSearchQueries([\n FilterDefinitionQuery::instance()\n ->setQuery(new Exists('stats.' . $elasticsearchColumn))\n ->setPath('stats', 'stats'),\n ])\n );\n\n return $this->queryDrillDownResults($consumer, $boolQuery, $page, $limit);\n }\n\n public function getConversationsActivityChannelPerUserAggregation(FilterDefinitionCollection $filterSet): Collection\n {\n $boolQuery = $filterSet->getBoolQuery(\n $filterSet->extractElasticSearchQueries()\n );\n\n $query = (new Query())\n ->setSize(0)\n ->setSource(false)\n ->addAggregation(\n (new Filter('activities', $boolQuery))\n ->addAggregation(\n (new AggregationTerms('channel'))\n ->setField('type.keyword')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n ->addAggregation(\n (new AggregationTerms('by_user'))\n ->setField('user.id_string.keyword')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n ->addAggregation(\n (new Sum('total_duration'))->setField('duration')\n )\n ->addAggregation(\n new ValueCount('volume', 'id_string.keyword')\n )\n ->addAggregation(\n (new Filter('volume_connected', new Exists('actual_start_time')))\n ->addAggregation(\n new ValueCount('volume', 'id_string.keyword')\n )\n )\n ->addAggregation(\n (new Filter('logged_to_crm', new Exists('crm_provider_id')))\n ->addAggregation(\n new ValueCount('volume', 'id_string.keyword')\n )\n )\n )\n ->addAggregation(\n new AvgBucket('avg_duration', 'by_user>total_duration')\n )\n ->addAggregation(\n new AvgBucket('avg_volume_all', 'by_user>volume')\n )\n ->addAggregation(\n new AvgBucket('avg_volume_logged', 'by_user>logged_to_crm>volume')\n )\n ->addAggregation(\n new AvgBucket('avg_volume_connected', 'by_user>volume_connected>volume')\n )\n )\n );\n\n $results = $this->searchService\n ->search($query, $this->model, 'getConversationsActivityChannelPerUserAggregation')\n ->getAggregations();\n\n $connectableActivityChannels = [\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n Activity::TYPE_CONFERENCE,\n ];\n\n return Collection::make(array_get($results, 'activities.channel.buckets', []))\n ->map(static function (array $bucket) use ($connectableActivityChannels): array {\n $channel = $bucket['key'];\n\n $avgVolume = (float) array_get($bucket, 'avg_volume_all.value', 0);\n $avgVolumeLogged = (float) array_get($bucket, 'avg_volume_logged.value', 0);\n $avgVolumeConnected = (float) array_get($bucket, 'avg_volume_connected.value', 0);\n $avgDuration = (float) array_get($bucket, 'avg_duration.value', 0);\n\n return [\n 'channel' => $channel,\n 'stats' => [\n 'avg_volume' => $avgVolume,\n 'avg_volume_logged' => $avgVolumeLogged,\n 'avg_volume_connected' => $avgVolumeConnected,\n 'avg_duration' => $avgDuration,\n ],\n 'per_user' => Collection::make(array_get($bucket, 'by_user.buckets', []))\n ->keyBy('key')\n ->map(static function (array $userBucket) use ($channel, $connectableActivityChannels): array {\n $data = [\n 'volume_logged' => (int) array_get($userBucket, 'logged_to_crm.doc_count', 0),\n 'volume' => (int) $userBucket['doc_count'],\n 'duration' => (float) array_get($userBucket, 'total_duration.value', 0),\n ];\n\n if (in_array($channel, $connectableActivityChannels, true)) {\n $data['volume_connected'] = (int) array_get($userBucket, 'volume_connected.doc_count');\n }\n\n return $data;\n })\n ->all(),\n ];\n });\n }\n\n public function getDashboardActivityOverTime(\n User $user,\n FilterDefinitionCollection $filterSet,\n string $histogramInterval = 'day'\n ): Collection {\n $timezoneOffset = $user->getTimezoneOffset();\n\n $boolQuery = $filterSet->getBoolQuery(\n $filterSet->extractElasticSearchQueries()\n );\n\n $query = (new Query())\n ->setSize(0)\n ->setSource(false)\n ->addAggregation(\n (new Filter(\n 'voice_activities',\n (clone $boolQuery)\n ->addFilter(\n (new BoolQuery())\n ->addShould((new Term())->setTerm('type.keyword', Activity::TYPE_CONFERENCE))\n ->addShould((new Term())->setTerm('type.keyword', Activity::TYPE_SOFTPHONE))\n ->addShould((new Term())->setTerm('type.keyword', Activity::TYPE_SOFTPHONE_INBOUND))\n )\n ->addFilter(\n new Exists('actual_start_time')\n )\n ))\n ->addAggregation(\n (new DateHistogram('over_time', 'actual_end_time', $histogramInterval))\n ->setFormat('8uuuu-MM-dd')\n ->setTimezone($timezoneOffset)\n ->setMinimumDocumentCount(0)\n ->addAggregation(\n (new AggregationTerms('by_channel'))\n ->setField('type.keyword')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n )\n )\n )\n ->addAggregation(\n (new Filter(\n 'text_activities',\n (clone $boolQuery)\n ->addFilter(\n (new Term())->setTerm('type.keyword', Activity::TYPE_SMS_OUTBOUND)\n )\n ))\n ->addAggregation(\n (new DateHistogram('over_time', 'created_at', $histogramInterval))\n ->setFormat('8uuuu-MM-dd')\n ->setTimezone($timezoneOffset)\n ->setMinimumDocumentCount(0)\n ->addAggregation(\n (new AggregationTerms('by_channel'))\n ->setField('type.keyword')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n )\n )\n );\n\n $aggregationData = $this->searchService->search($query, $this->model, 'getDashboardActivityOverTime')->getAggregations();\n\n $activityData = Collection::make([\n array_get($aggregationData, 'voice_activities.over_time.buckets'),\n array_get($aggregationData, 'text_activities.over_time.buckets'),\n ])\n ->collapse()\n ->reduce(\n static function (array $carry, array $bucketData): array {\n $byChannel = Collection::make(array_get($bucketData, 'by_channel.buckets'))\n ->keyBy('key')\n ->map(static function (array $bucketData): int {\n return $bucketData['doc_count'];\n });\n\n $date = $bucketData['key_as_string'];\n\n if (array_key_exists($date, $carry)) {\n $byChannel = $byChannel->merge($carry[$date]);\n }\n\n $carry[$date] = $byChannel->all();\n\n return $carry;\n },\n []\n );\n\n return Collection::make($activityData);\n }\n\n public function getDashboardCoachingOverTime(\n User $user,\n FilterDefinitionCollection $filterSet,\n ?CarbonImmutable $dateTimeRangeStartsAt,\n ?CarbonImmutable $dateTimeRangeEndsAt,\n string $histogramInterval = 'day',\n ): Collection {\n $filterDefinitionQueries = $filterSet->extractElasticSearchQueries();\n\n $currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('plays');\n\n $boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);\n\n $timezone = $user->getTimezone();\n $timezoneOffset = $user->getTimezoneOffset();\n\n $hasDateRange = $dateTimeRangeStartsAt !== null && $dateTimeRangeEndsAt !== null;\n\n if ($hasDateRange) {\n $resultSetKey = 'from_as_string';\n\n $dateTimeRangeStartsAt = $dateTimeRangeStartsAt->setTimezone($timezone)->toImmutable();\n $dateTimeRangeEndsAt = $dateTimeRangeEndsAt->setTimezone($timezone)->toImmutable();\n\n $dateRangeAggregation = (new DateRange('over_time'))\n ->setField('plays.created_at')\n ->setFormat('8uuuu-MM-dd')\n ->setParam('time_zone', $timezoneOffset);\n\n if ($histogramInterval === 'day') {\n $increment = static function (CarbonImmutable $dateTime): CarbonImmutable {\n return $dateTime->addDay();\n };\n } elseif ($histogramInterval === 'hour') {\n $increment = static function (CarbonImmutable $dateTime): CarbonImmutable {\n return $dateTime->addHour();\n };\n } else {\n throw new OutOfBoundsException('Unknown date time interval');\n }\n\n $periodStartsAt = $dateTimeRangeStartsAt;\n\n while (true) {\n $periodEndsAt = $increment($periodStartsAt);\n\n $dateRangeAggregation->addRange(\n $periodStartsAt->format('Y-m-d'),\n $periodEndsAt->format('Y-m-d')\n );\n\n if ($periodEndsAt >= $dateTimeRangeEndsAt) {\n break;\n }\n\n $periodStartsAt = clone $periodEndsAt;\n }\n } else {\n $resultSetKey = 'key_as_string';\n\n $dateRangeAggregation = (new DateHistogram('over_time', 'plays.created_at', $histogramInterval))\n ->setTimezone($timezoneOffset)\n ->setFormat('8uuuu-MM-dd')\n ->setMinimumDocumentCount(0);\n }\n\n $query = (new Query())\n ->setSize(0)\n ->setSource(false)\n ->addAggregation(\n (new Filter('activities', $boolQuery))\n ->addAggregation(\n (new Nested('playback', 'plays'))\n ->addAggregation(\n (new Filter('filtered', $currentPriorityQuery))\n ->addAggregation($dateRangeAggregation)\n )\n )\n );\n\n $aggregationData = $this->searchService->search($query, $this->model, 'getDashboardCoachingOverTime')->getAggregations();\n\n return Collection::make(array_get($aggregationData, 'activities.playback.filtered.over_time.buckets'))\n ->keyBy($resultSetKey)\n ->map(static function (array $bucketData): int {\n return $bucketData['doc_count'];\n });\n }\n\n public function getDashboardCoachingBreakdownListensByUserRole(FilterDefinitionCollection $filterSet): Collection\n {\n $filterDefinitionQueries = $filterSet->extractElasticSearchQueries();\n\n $currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('plays');\n\n $boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);\n\n $query = (new Query())\n ->setSize(0)\n ->setSource(false)\n ->addAggregation(\n (new Filter('activities', $boolQuery))\n ->addAggregation(\n (new AggregationTerms('by_user'))\n ->setField('user.id_string.keyword')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n ->addAggregation(\n (new Nested('played_by', 'plays'))\n ->addAggregation(\n (new Filter('filtered', $currentPriorityQuery))\n ->addAggregation(\n (new AggregationTerms('user'))\n ->setField('plays.user.id_string.keyword')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n ->addAggregation(\n (new AggregationTerms('role'))\n ->setField('plays.user.roles.name')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n )\n )\n )\n )\n )\n );\n\n $aggregationData = $this->searchService\n ->search($query, $this->model, 'getDashboardCoachingBreakdownListensByUserRole')\n ->getAggregations();\n\n return Collection::make(array_get($aggregationData, 'activities.by_user.buckets'))\n ->keyBy('key')\n ->map(static function (array $roomOwnerBucket): array {\n return Collection::make(array_get($roomOwnerBucket, 'played_by.filtered.user.buckets'))\n ->map(static function (array $playbackBucket): array {\n $userId = $playbackBucket['key'];\n $count = $playbackBucket['doc_count'];\n\n $userRoles = Collection::make(array_get($playbackBucket, 'role.buckets'))\n ->keyBy('key')\n ->keys();\n\n return [\n 'count' => $count,\n 'userId' => $userId,\n 'userRoles' => $userRoles->all(),\n ];\n })\n ->all();\n });\n }\n\n public function getDashboardCoachingBreakdownCoachingFocusFilledByUserRole(\n FilterDefinitionCollection $filterSet\n ): Collection {\n $filterDefinitionQueries = $filterSet->extractElasticSearchQueries();\n\n $currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('comments');\n\n $boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);\n\n $query = (new Query($boolQuery))\n ->setSize(0)\n ->setSource(false)\n ->addAggregation(\n (new AggregationTerms('by_user'))\n ->setField('user.id_string.keyword')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n ->addAggregation(\n (new Nested('commented_by', 'comments'))\n ->addAggregation(\n (new Filter('filtered', $currentPriorityQuery))\n ->addAggregation(\n (new AggregationTerms('user'))\n ->setField('comments.user.id_string.keyword')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n ->addAggregation(\n (new AggregationTerms('role'))\n ->setField('comments.user.roles.name')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n )\n )\n )\n )\n );\n\n $aggregationData = $this->searchService\n ->search($query, $this->model, 'getDashboardCoachingBreakdownCoachingFocusByUserRole')\n ->getAggregations();\n\n return Collection::make(array_get($aggregationData, 'by_user.buckets'))\n ->keyBy('key')\n ->map(static function (array $roomOwnerBucket): array {\n return Collection::make(array_get($roomOwnerBucket, 'commented_by.filtered.user.buckets'))\n ->map(static function (array $commentBucket): array {\n $userId = $commentBucket['key'];\n $count = $commentBucket['doc_count'];\n\n $userRoles = Collection::make(array_get($commentBucket, 'role.buckets'))\n ->keyBy('key')\n ->keys();\n\n return [\n 'count' => $count,\n 'userId' => $userId,\n 'userRoles' => $userRoles->all(),\n ];\n })\n ->all();\n });\n }\n\n private function getCompositeAggregationBy(\n BoolQuery $boolQuery,\n AbstractAggregation $sourceAggregation,\n ?array $customAggregations = null,\n ?AbstractAggregation $aggregationParent = null,\n ?callable $compositeAggregationExtractor = null,\n ?AbstractAggregation $immediateAggregationParent = null\n ): LazyCollection {\n if ($compositeAggregationExtractor === null) {\n $compositeAggregationExtractor = static fn (ResultSet $aggregationData): array => $aggregationData\n ->getAggregation('composite')['buckets'];\n }\n\n return LazyCollection::make(\n function () use (\n $sourceAggregation,\n $customAggregations,\n $aggregationParent,\n $immediateAggregationParent,\n $boolQuery,\n $compositeAggregationExtractor,\n ): Generator {\n $compositeAggregation = (new Composite('composite'))\n ->setSize(self::AGG_COMPOSITE_MAX_SIZE)\n ->addSource($sourceAggregation);\n\n if (is_array($customAggregations)) {\n foreach ($customAggregations as $customAggregation) {\n $compositeAggregation->addAggregation($customAggregation);\n }\n }\n\n $aggregation = $compositeAggregation;\n\n if ($aggregationParent instanceof AbstractAggregation) {\n $aggregation = $aggregationParent;\n\n if ($immediateAggregationParent instanceof AbstractAggregation) {\n $immediateAggregationParent->addAggregation($compositeAggregation);\n } else {\n $aggregationParent->addAggregation($compositeAggregation);\n }\n }\n\n while (true) {\n $query = (new Query($boolQuery))\n ->setSource(false)\n ->addAggregation($aggregation);\n\n $aggregationData = $this->searchService\n ->search($query, $this->model, 'getCompositeAggregationBy');\n\n foreach ($compositeAggregationExtractor($aggregationData) as $bucket) {\n yield $bucket;\n }\n\n $cursor = array_get($aggregationData, 'composite.after_key', null);\n\n if (! is_array($cursor)) {\n break;\n }\n\n $compositeAggregation->addAfter($cursor);\n }\n },\n );\n }\n\n private function getCompositeAggregationByUser(\n BoolQuery $boolQuery,\n ?array $customAggregations = null,\n ?callable $callback = null,\n ): LazyCollection {\n if ($callback === null) {\n $callback = static fn (array $bucket): int => $bucket['doc_count'];\n }\n\n $sourceAggregation = (new AggregationTerms('by_user'))\n ->setField('user.id_string.keyword');\n\n return $this\n ->getCompositeAggregationBy(\n boolQuery: $boolQuery,\n sourceAggregation: $sourceAggregation,\n customAggregations: $customAggregations,\n )\n ->mapWithKeys(static fn (array $bucket): array => [\n $bucket['key']['by_user'] => $bucket,\n ])\n ->map($callback);\n }\n\n private function getCompositeAggregationByCoachingFeedbackCoach(\n BoolQuery $boolQuery,\n ?array $customAggregations = null\n ): LazyCollection {\n return $this\n ->getCompositeAggregationBy(\n $boolQuery,\n (new AggregationTerms('by_feedback_id'))\n ->setField('coachingFeedbacks.id_string'),\n $customAggregations,\n new Nested('coachingFeedbacks', 'coachingFeedbacks'),\n static fn (ResultSet $aggregationData): array => $aggregationData\n ->getAggregation('coachingFeedbacks')['composite']['buckets']\n );\n }\n\n /**\n * @param Collection|string[] $statsOfInterest\n *\n * @return Collection|AbstractAggregation[]\n */\n private function getEngagementStatsAggregation(Collection $statsOfInterest): Collection\n {\n $getAggregation = static function (string $propertyName): Filter {\n $totalAmountProperties = [\n 'talk_time_ratio',\n 'longest_user_monologue',\n 'longest_customer_monologue',\n 'talking_speed',\n 'user_questions',\n ];\n\n $propertyPath = 'stats.' . $propertyName;\n\n $filterQuery = (new BoolQuery())\n ->addMust(new Exists($propertyPath));\n\n if (in_array($propertyName, $totalAmountProperties, true)) {\n $filterQuery->addMust(\n new Range(\n $propertyPath,\n [\n 'gt' => 0,\n ]\n )\n );\n }\n\n return (new Filter($propertyName, $filterQuery))\n ->addAggregation(\n (new Sum('data'))->setField($propertyPath)\n );\n };\n\n return Collection::make()\n ->push(\n $statsOfInterest\n ->reduce(\n static function (Nested $carry, string $propertyName) use ($getAggregation): Nested {\n return $carry->addAggregation($getAggregation($propertyName));\n },\n new Nested('stats', 'stats')\n )\n );\n }\n\n public function getDashboardEngagementStats(FilterDefinitionCollection $filterSet): Collection\n {\n $statsOfInterest = Collection::make([\n 'talkTimeRatio' => 'talk_time_ratio',\n 'longestMonologue' => 'longest_user_monologue',\n 'longestCustomerStory' => 'longest_customer_monologue',\n 'talkingSpeed' => 'talking_speed',\n 'patience' => 'patience_time',\n 'questionRate' => 'user_questions',\n ]);\n\n $boolQuery = $filterSet->getBoolQuery(\n $filterSet->extractElasticSearchQueries()\n );\n\n $aggregationData = $this\n ->getCompositeAggregationByUser(\n $boolQuery,\n $this->getEngagementStatsAggregation($statsOfInterest)->toArray(),\n static function (array $bucket): array {\n $statsBucket = $bucket['stats'];\n unset($statsBucket['doc_count']);\n\n return Collection::make($statsBucket)\n ->map(static function (array $stat): array {\n return [\n 'count' => $stat['doc_count'],\n 'value' => $stat['data']['value'],\n ];\n })\n ->toArray();\n }\n )\n ->collect();\n\n return $statsOfInterest\n ->map(static function (string $propertyName) use ($aggregationData): ?float {\n [$count, $value] = $aggregationData->reduce(\n static function (array $accumulator, array $bucket) use ($propertyName): array {\n $accumulator[0] += (int) $bucket[$propertyName]['count'];\n $accumulator[1] += (float) $bucket[$propertyName]['value'];\n\n return $accumulator;\n },\n [0, 0]\n );\n\n if ($count === 0) {\n return null;\n }\n\n return (float) ($value / $count);\n });\n }\n\n public function getCoachingFeedbacksFilledPerUserAggregation(FilterDefinitionCollection $filterSet): Collection\n {\n $filterDefinitionQueries = $filterSet->extractElasticSearchQueries([\n FilterDefinitionQuery::instance()\n ->setQuery((new Term())->setTerm('coachingFeedbacks.visibility', CoachingFeedback::VISIBLE_TO_ALL))\n ->setPath('coachingFeedbacks', 'coachingFeedbacks'),\n FilterDefinitionQuery::instance()\n ->setQuery((new Term())->setTerm('coachingFeedbacks.coach.status', User::STATUS_ACTIVE))\n ->setPath('coachingFeedbacks', 'coachingFeedbacks'),\n ]);\n\n $currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('coachingFeedbacks');\n $boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);\n\n $data = $this\n ->getCompositeAggregationByUser(\n $boolQuery,\n [\n (new Nested('feedbacks', 'coachingFeedbacks'))\n ->addAggregation(\n (new Filter('filtered', $currentPriorityQuery))\n ->addAggregation(\n (new AggregationTerms('by_user'))\n ->setField('coachingFeedbacks.coach.id_string')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n )\n ),\n ],\n static function (array $bucket): array {\n return array_get($bucket, 'feedbacks.filtered.by_user.buckets', []);\n }\n )\n ->reduce(\n static function (array $carry, array $buckets): array {\n foreach ($buckets as $bucket) {\n $userId = $bucket['key'];\n\n if (! array_key_exists($userId, $carry)) {\n $carry[$userId] = 0;\n }\n\n $carry[$userId] += $bucket['doc_count'];\n }\n\n return $carry;\n },\n []\n );\n\n return Collection::make($data);\n }\n\n public function getCoachingFeedbacksReceivedPerUserAggregation(FilterDefinitionCollection $filterSet): Collection\n {\n $filterDefinitionQueries = $filterSet->extractElasticSearchQueries([\n FilterDefinitionQuery::instance()\n ->setQuery((new Term())->setTerm('coachingFeedbacks.visibility', CoachingFeedback::VISIBLE_TO_ALL))\n ->setPath('coachingFeedbacks', 'coachingFeedbacks'),\n FilterDefinitionQuery::instance()\n ->setQuery((new Term())->setTerm('coachingFeedbacks.coach.status', User::STATUS_ACTIVE))\n ->setPath('coachingFeedbacks', 'coachingFeedbacks'),\n ]);\n\n $currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('coachingFeedbacks');\n $boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);\n\n $data = $this\n ->getCompositeAggregationByUser(\n $boolQuery,\n [\n (new Nested('feedbacks', 'coachingFeedbacks'))\n ->addAggregation(\n (new Filter('filtered', $currentPriorityQuery))\n ->addAggregation(\n (new AggregationTerms('by_user'))\n ->setField('coachingFeedbacks.coachee.id_string')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n ->addAggregation(\n new ValueCount('volume', 'coachingFeedbacks.id_string'),\n )\n )\n ),\n ],\n static function (array $bucket): array {\n return array_get($bucket, 'feedbacks.filtered.by_user.buckets', []);\n }\n )\n ->reduce(\n static function (array $carry, array $buckets): array {\n foreach ($buckets as $bucket) {\n $userId = $bucket['key'];\n\n if (! array_key_exists($userId, $carry)) {\n $carry[$userId] = 0;\n }\n\n $carry[$userId] += $bucket['doc_count'];\n }\n\n return $carry;\n },\n []\n );\n\n return Collection::make($data);\n }\n\n public function getCoachingFeedbacksOverTimeAggregation(\n User $user,\n FilterDefinitionCollection $filterSet,\n ?CarbonImmutable $dateTimeRangeStartsAt,\n ?CarbonImmutable $dateTimeRangeEndsAt,\n string $histogramInterval = 'day',\n ): LazyCollection {\n $timezone = $user->getTimezone();\n $timezoneOffset = $user->getTimezoneOffset();\n\n $filterDefinitionQueries = $filterSet->extractElasticSearchQueries([\n FilterDefinitionQuery::instance()\n ->setQuery((new Term())->setTerm('coachingFeedbacks.visibility', CoachingFeedback::VISIBLE_TO_ALL))\n ->setPath('coachingFeedbacks', 'coachingFeedbacks'),\n ]);\n\n $currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('coachingFeedbacks');\n $boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);\n\n $hasDateRange = $dateTimeRangeStartsAt !== null && $dateTimeRangeEndsAt !== null;\n\n if ($hasDateRange) {\n $resultSetKey = 'from_as_string';\n\n $dateTimeRangeStartsAt = $dateTimeRangeStartsAt->setTimezone($timezone)->toImmutable();\n $dateTimeRangeEndsAt = $dateTimeRangeEndsAt->setTimezone($timezone)->toImmutable();\n\n $dateRangeAggregation = (new DateRange('over_time'))\n ->setField('coachingFeedbacks.updated_at')\n ->setFormat('8uuuu-MM-dd')\n ->setParam('time_zone', $timezoneOffset);\n\n if ($histogramInterval === 'day') {\n $increment = static function (CarbonImmutable $dateTime): CarbonImmutable {\n return $dateTime->addDay();\n };\n } elseif ($histogramInterval === 'hour') {\n $increment = static function (CarbonImmutable $dateTime): CarbonImmutable {\n return $dateTime->addHour();\n };\n } else {\n throw new OutOfBoundsException('Unknown date time interval');\n }\n\n $periodStartsAt = $dateTimeRangeStartsAt;\n\n while (true) {\n $periodEndsAt = $increment($periodStartsAt);\n\n $dateRangeAggregation->addRange(\n $periodStartsAt->format('Y-m-d'),\n $periodEndsAt->format('Y-m-d')\n );\n\n if ($periodEndsAt >= $dateTimeRangeEndsAt) {\n break;\n }\n\n $periodStartsAt = clone $periodEndsAt;\n }\n } else {\n $resultSetKey = 'key_as_string';\n\n $dateRangeAggregation = (new DateHistogram(\n name: 'over_time',\n field: 'coachingFeedbacks.updated_at',\n interval: $histogramInterval,\n ))\n ->setTimezone($timezoneOffset)\n ->setFormat('8uuuu-MM-dd')\n ->setMinimumDocumentCount(0);\n }\n\n $aggregationResult = $this\n ->getCompositeAggregationByCoachingFeedbackCoach(\n $boolQuery,\n [\n (new Filter('filtered', $currentPriorityQuery))\n ->addAggregation($dateRangeAggregation),\n ]\n )\n ->filter(static fn (array $bucket): bool => $bucket['filtered']['doc_count'] > 0)\n ->reduce(\n static function (array $carry, array $bucket) use ($resultSetKey): array {\n $feedbackId = $bucket['key']['by_feedback_id'];\n $date = null;\n\n foreach (array_get($bucket, 'filtered.over_time.buckets') as $dateTime) {\n if ($dateTime['doc_count'] > 0) {\n $date = $dateTime[$resultSetKey];\n\n break;\n }\n }\n\n if ($date === null) {\n return $carry;\n }\n\n $carry[$feedbackId] = $date;\n\n return $carry;\n },\n []\n );\n\n $query = (new Query($boolQuery))\n ->setSource([\n 'id_string',\n 'coachingFeedbacks.id_string',\n 'coachingFeedbacks.coach.id_string',\n 'coachingFeedbacks.coachee.id_string',\n 'coachingFeedbacks.framework.id_string',\n 'coachingFeedbacks.framework.name',\n 'coachingFeedbacks.sectionFeedbacks.section.id_string',\n 'coachingFeedbacks.sectionFeedbacks.section.name',\n 'coachingFeedbacks.sectionFeedbacks.score',\n ]);\n\n return $this->searchService->scrollOverDocuments($query, $this->model)\n ->flatMap(static function (Document $document) use ($aggregationResult): array {\n $data = $document->toArray();\n $activityId = $data['_source']['id_string'];\n\n return Collection::make($data['_source']['coachingFeedbacks'])\n ->filter(\n static fn (array $feedback): bool\n => array_key_exists($feedback['id_string'], $aggregationResult),\n )\n ->flatMap(static function (array $feedback) use ($activityId, $aggregationResult): array {\n if (! array_key_exists('sectionFeedbacks', $feedback)) {\n return [];\n }\n\n $feedbackId = $feedback['id_string'];\n $framework = $feedback['framework'];\n $sectionFeedbacks = $feedback['sectionFeedbacks'];\n\n $coachId = $feedback['coach']['id_string'];\n $coacheeId = $feedback['coachee']['id_string'];\n\n return Collection::make($sectionFeedbacks)\n ->map(static fn (array $section): array => [\n 'activityId' => $activityId,\n 'feedbackUUID' => $feedbackId,\n 'activityTypeName' => $framework['name'],\n 'activityTypeId' => $framework['id_string'],\n 'sectionId' => $section['section']['id_string'],\n 'sectionName' => $section['section']['name'],\n 'score' => $section['score'],\n\n 'date' => $aggregationResult[$feedbackId],\n 'coachId' => $coachId,\n 'coacheeId' => $coacheeId,\n ])\n ->toArray();\n })\n ->toArray();\n })\n ->filter(static fn (array $bucket): bool => ! empty($bucket));\n }\n\n public function getCoachingListensPerUserAggregation(FilterDefinitionCollection $filterSet): Collection\n {\n $filterDefinitionQueries = $filterSet->extractElasticSearchQueries();\n $boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);\n\n $data = $this\n ->getCompositeAggregationByUser(\n $boolQuery,\n [\n (new Nested('playback', 'plays'))\n ->addAggregation(\n (new Filter('filtered', $filterDefinitionQueries->getNestedQuery('plays')))\n ->addAggregation(\n (new AggregationTerms('by_user'))\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n ->setField('plays.user.id_string.keyword')\n )\n ),\n ],\n static function (array $bucket): array {\n return array_get($bucket, 'playback.filtered.by_user.buckets', []);\n }\n )\n ->reduce(\n static function (array $carry, array $buckets): array {\n foreach ($buckets as $bucket) {\n $userId = $bucket['key'];\n\n if (! array_key_exists($userId, $carry)) {\n $carry[$userId] = 0;\n }\n\n $carry[$userId] += $bucket['doc_count'];\n }\n\n return $carry;\n },\n []\n );\n\n return Collection::make($data);\n }\n\n public function getCoachingSharesPerUserAggregation(FilterDefinitionCollection $filterSet): Collection\n {\n $filterDefinitionQueries = $filterSet->extractElasticSearchQueries();\n\n $currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('shares')\n ->addMustNot(new Exists('shares.parent_share_id'));\n\n $boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);\n\n $data = $this\n ->getCompositeAggregationByUser(\n $boolQuery,\n [\n (new Nested('shares', 'shares'))\n ->addAggregation(\n (new Filter('filtered', $currentPriorityQuery))\n ->addAggregation(\n (new AggregationTerms('by_user'))\n ->setField('shares.user.id_string.keyword')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n )\n ),\n ],\n static function (array $bucket): array {\n return array_get($bucket, 'shares.filtered.by_user.buckets', []);\n }\n )\n ->reduce(\n static function (array $carry, array $buckets): array {\n foreach ($buckets as $bucket) {\n $userId = $bucket['key'];\n\n if (! array_key_exists($userId, $carry)) {\n $carry[$userId] = 0;\n }\n\n $carry[$userId] += $bucket['doc_count'];\n }\n\n return $carry;\n },\n []\n );\n\n return Collection::make($data);\n }\n\n /**\n * @param int[] $commentTypes\n */\n public function getCoachingCommentsReceivedUserAggregation(\n FilterDefinitionCollection $filterSet,\n array $commentTypes\n ): array {\n if (empty($commentTypes)) {\n throw new OutOfBoundsException('At least one comment type must be specified');\n }\n\n $filterDefinitionQueries = $filterSet->extractElasticSearchQueries([\n FilterDefinitionQuery::instance()\n ->setQuery(new Terms('comments.type', $commentTypes))\n ->setPath('comments', 'comments'),\n ]);\n\n $boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);\n\n return $this\n ->getCompositeAggregationByUser(\n $boolQuery,\n [\n (new Nested('comments', 'comments'))\n ->addAggregation(\n new Filter('filtered', $filterDefinitionQueries->getNestedQuery('comments'))\n ),\n ],\n static function (array $bucket): int {\n return array_get($bucket, 'comments.filtered.doc_count');\n }\n )\n ->collect()\n ->toArray();\n }\n\n /**\n * @param int[] $commentTypes\n */\n public function getCoachingCommentsFilledUserAggregation(\n FilterDefinitionCollection $filterSet,\n array $commentTypes\n ): array {\n if (empty($commentTypes)) {\n throw new OutOfBoundsException('At least one comment type must be specified');\n }\n\n $filterDefinitionQueries = $filterSet->extractElasticSearchQueries([\n FilterDefinitionQuery::instance()\n ->setQuery(new Terms('comments.type', $commentTypes))\n ->setPath('comments', 'comments'),\n ]);\n\n $currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('comments');\n\n $boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);\n\n return $this\n ->getCompositeAggregationByUser(\n $boolQuery,\n [\n (new Nested('comments', 'comments'))\n ->addAggregation(\n (new Filter('filtered', $currentPriorityQuery))\n ->addAggregation(\n (new AggregationTerms('by_user'))\n ->setField('comments.user.id_string.keyword')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n )\n ),\n ],\n static function (array $bucket): array {\n return array_get($bucket, 'comments.filtered.by_user.buckets', []);\n }\n )\n ->reduce(\n static function (array $carry, array $buckets): array {\n foreach ($buckets as $bucket) {\n $userId = $bucket['key'];\n\n if (! array_key_exists($userId, $carry)) {\n $carry[$userId] = 0;\n }\n\n $carry[$userId] += $bucket['doc_count'];\n }\n\n return $carry;\n },\n []\n );\n }\n\n public function getCoachingPlaylistContributionsPerUserAggregation(\n FilterDefinitionCollection $filterSet,\n ): Collection {\n $filterDefinitionQueries = $filterSet->extractElasticSearchQueries();\n $currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('playlists');\n $boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);\n\n $data = $this\n ->getCompositeAggregationByUser(\n $boolQuery,\n [\n (new Nested('playlists', 'playlists'))\n ->addAggregation(\n (new Filter('filtered', $currentPriorityQuery))\n ->addAggregation(\n (new AggregationTerms('by_user'))\n ->setField('playlists.pivot.user.id_string')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n )\n ),\n ],\n static function (array $bucket): array {\n return array_get($bucket, 'playlists.filtered.by_user.buckets', []);\n }\n )\n ->reduce(\n static function (array $carry, array $buckets): array {\n foreach ($buckets as $bucket) {\n $userId = $bucket['key'];\n\n if (! array_key_exists($userId, $carry)) {\n $carry[$userId] = 0;\n }\n\n $carry[$userId] += $bucket['doc_count'];\n }\n\n return $carry;\n },\n []\n );\n\n return Collection::make($data);\n }\n\n public function getCoachingLiveCoachingPerUserAggregation(FilterDefinitionCollection $filterSet): Collection\n {\n $filterDefinitionQueries = $filterSet->extractElasticSearchQueries();\n $currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('participants');\n $boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);\n\n $data = $this\n ->getCompositeAggregationByUser(\n $boolQuery,\n [\n (new Nested('participants', 'participants'))\n ->addAggregation(\n (new Filter('filtered', $currentPriorityQuery))\n ->addAggregation(\n (new AggregationTerms('by_user'))\n ->setField('participants.user.id_string.keyword')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n )\n ),\n ],\n static function (array $bucket): array {\n return array_get($bucket, 'participants.filtered.by_user.buckets', []);\n }\n )\n ->reduce(\n static function (array $carry, array $buckets): array {\n foreach ($buckets as $bucket) {\n $userId = $bucket['key'];\n\n if (! array_key_exists($userId, $carry)) {\n $carry[$userId] = 0;\n }\n\n $carry[$userId] += $bucket['doc_count'];\n }\n\n return $carry;\n },\n []\n );\n\n return Collection::make($data);\n }\n\n private function getEngagementActivityStatsAggregationResults(\n FilterDefinitionCollection $filterSet,\n string $propertyName,\n ): Collection {\n $statsOfInterest = Collection::make([\n $propertyName,\n ]);\n\n $boolQuery = $filterSet->getBoolQuery(\n $filterSet->extractElasticSearchQueries()\n );\n\n $aggregationData = $this->getCompositeAggregationByUser(\n $boolQuery,\n $this->getEngagementStatsAggregation($statsOfInterest)->toArray(),\n static function (array $bucket): array {\n $statsBucket = $bucket['stats'];\n unset($statsBucket['doc_count']);\n\n return Collection::make($statsBucket)\n ->map(static function (array $stat): array {\n return [\n 'count' => $stat['doc_count'],\n 'value' => $stat['data']['value'],\n ];\n })\n ->toArray();\n }\n );\n\n return $aggregationData\n ->map(static function (array $bucket) use ($propertyName): float {\n $value = $bucket[$propertyName]['value'];\n $count = $bucket[$propertyName]['count'];\n\n if ($count === 0) {\n return 0;\n }\n\n return $value / $count;\n })\n ->collect();\n }\n\n public function getEngagementTalkTimeRatioPerUser(FilterDefinitionCollection $filterSet): Collection\n {\n return $this->getEngagementActivityStatsAggregationResults($filterSet, 'talk_time_ratio');\n }\n\n public function getEngagementLongestMonologuePerUser(FilterDefinitionCollection $filterSet): Collection\n {\n return $this->getEngagementActivityStatsAggregationResults($filterSet, 'longest_user_monologue');\n }\n\n public function getEngagementLongestCustomerStoryPerUser(FilterDefinitionCollection $filterSet): Collection\n {\n return $this->getEngagementActivityStatsAggregationResults($filterSet, 'longest_customer_monologue');\n }\n\n public function getEngagementTalkingSpeedPerUser(FilterDefinitionCollection $filterSet): Collection\n {\n return $this->getEngagementActivityStatsAggregationResults($filterSet, 'talking_speed');\n }\n\n public function getEngagementInteractivityPerUser(FilterDefinitionCollection $filterSet): Collection\n {\n return $this->getEngagementActivityStatsAggregationResults($filterSet, 'interactions');\n }\n\n public function getEngagementPatiencePerUser(FilterDefinitionCollection $filterSet): Collection\n {\n return $this->getEngagementActivityStatsAggregationResults($filterSet, 'patience_time');\n }\n\n public function getEngagementQuestionRatePerUser(FilterDefinitionCollection $filterSet): Collection\n {\n return $this->getEngagementActivityStatsAggregationResults($filterSet, 'user_questions');\n }\n\n public function getCommonTopics(FilterDefinitionCollection $filterSet, int $size): Collection\n {\n $boolQuery = $filterSet->getBoolQuery(\n $filterSet->extractElasticSearchQueries()\n );\n\n $query = (new Query())\n ->setSize(0)\n ->setSource(false)\n ->addAggregation(\n (new Filter('activities', $boolQuery))\n ->addAggregation(\n (new Nested('playback_topic', 'topic_triggers.playback_topic_trigger.playback_topic'))\n ->addAggregation(\n (new AggregationTerms('by_topic'))\n ->setField('topic_triggers.playback_topic_trigger.playback_topic.id_string')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n ->setOrder('_count', 'desc')\n ->setSize($size)\n )\n )\n );\n\n $aggregationData = $this->searchService->search($query, $this->model, 'getCommonTopics')->getAggregations();\n\n return Collection::make(array_get($aggregationData, 'activities.playback_topic.by_topic.buckets', []))\n ->keyBy('key')\n ->map(static function (array $topicBucket): float {\n return $topicBucket['doc_count'];\n });\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Repositories;\n\nuse Carbon\\Carbon;\nuse Carbon\\CarbonImmutable;\nuse Elastica\\Aggregation\\AbstractAggregation;\nuse Elastica\\Aggregation\\AvgBucket;\nuse Elastica\\Aggregation\\Composite;\nuse Elastica\\Aggregation\\DateHistogram;\nuse Elastica\\Aggregation\\DateRange;\nuse Elastica\\Aggregation\\Filter;\nuse Elastica\\Aggregation\\Nested;\nuse Elastica\\Aggregation\\Sum;\nuse Elastica\\Aggregation\\Terms as AggregationTerms;\nuse Elastica\\Aggregation\\ValueCount;\nuse Elastica\\Document;\nuse Elastica\\Query;\nuse Elastica\\Query\\BoolQuery;\nuse Elastica\\Query\\Exists;\nuse Elastica\\Query\\Range;\nuse Elastica\\Query\\Term;\nuse Elastica\\Query\\Terms;\nuse Elastica\\Result;\nuse Elastica\\ResultSet;\nuse Generator;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\LazyCollection;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinitionCollection;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinitionQuery;\nuse Jiminny\\Component\\ElasticSearch\\Service\\Search;\nuse Jiminny\\Component\\Math\\BitwiseOperations;\nuse Jiminny\\Exceptions\\OutOfBoundsException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Activity\\Comment;\nuse Jiminny\\Models\\CoachingFeedback;\nuse Jiminny\\Models\\User;\n\nclass TeamInsightsRepository\n{\n use BitwiseOperations;\n\n public const array CONVERSATION_DRILLDOWNS = [\n self::CONVERSATION_DRILLDOWN_SCHEDULED,\n self::CONVERSATION_DRILLDOWN_ATTEMPTED,\n self::CONVERSATION_DRILLDOWN_CONNECTED,\n self::CONVERSATION_DRILLDOWN_LOGGED,\n ];\n\n public const string CONVERSATION_DRILLDOWN_CONNECTED = 'connected';\n public const string CONVERSATION_DRILLDOWN_SCHEDULED = 'scheduled';\n public const string CONVERSATION_DRILLDOWN_ATTEMPTED = 'attempted';\n public const string CONVERSATION_DRILLDOWN_LOGGED = 'logged';\n public const array DRILL_DOWN_MAP = [\n 'id_string' => 'id',\n 'title' => 'title',\n 'user.id_string' => null, // needed for indirect mapping\n 'user.name' => 'organizer.name',\n 'user.job.name' => 'organizer.job.name',\n 'user.photo_url' => 'organizer.photoUrl',\n 'type' => 'type',\n\n 'lead.name' => 'prospect.lead.name',\n 'lead.company' => 'prospect.lead.company',\n\n 'contact.name' => 'prospect.contact.name',\n 'contact.account.name' => 'prospect.contact.account.name',\n\n 'account.name' => 'prospect.account.account.name',\n\n 'participants.user.id_string' => null, // this would return an array and needs manual mapping\n 'participants.country_code' => null, // this would return an array and needs manual mapping\n 'participants.phone_number' => null, // this would return an array and needs manual mapping\n\n 'favorite_count' => 'stats.favorites',\n 'share_count' => 'stats.shares',\n 'comment_count' => 'stats.comments',\n 'play_count' => 'stats.plays',\n\n 'stats.talk_time_ratio' => 'stats.talkTimeRatio',\n 'stats.talking_speed' => 'stats.talkingSpeed',\n 'stats.user_questions' => 'stats.userQuestionsCount',\n 'stats.longest_user_monologue' => 'stats.longestUserMonologue',\n 'stats.longest_customer_monologue' => 'stats.longestCustomerMonologue',\n 'stats.patience_time' => 'stats.patienceTime',\n\n 'plays.user.id_string' => null, // this would return an array and needs manual mapping\n 'average_score' => 'averageScore',\n 'ai_call_score.score' => 'aiCallScore',\n 'category.name' => 'category.name',\n 'opportunity.value' => 'opportunity.value',\n 'opportunity.currency_code' => 'opportunity.currency_code',\n 'opportunity.stage.label' => 'opportunity.stage.label',\n 'duration' => 'duration',\n 'actual_end_time' => 'actualEndTime',\n 'tracks.telephony_provider_id' => null, // this would return an array and needs manual mapping\n 'coachingFeedbacks.coach.name' => null, // this would return an array and needs manual mapping\n ];\n\n public const int FLAG_DRILLDOWN_COMMENT_POSITIVE = 1;\n public const int FLAG_DRILLDOWN_COMMENT_NEGATIVE = 2;\n\n private const int AGG_TERMS_MAX_SIZE = 9999;\n private const int AGG_COMPOSITE_MAX_SIZE = 10000;\n\n private const int AGGREGATE_VALUE_AVG = 0;\n private const int AGGREGATE_VALUE_MAX = 1;\n private const int AGGREGATE_VALUE_SUM = 2;\n\n public function __construct(\n private readonly Search $searchService,\n private readonly Activity $model,\n ) {\n }\n\n /**\n * Ensure prospect.name is present and non-empty by applying a type-based fallback\n * from the original dot-notated data structure.\n *\n * - type lead => prospect.lead.name\n * - type contact => prospect.contact.name\n * - type account => prospect.account.account.name or prospect.account.name\n */\n private static function ensureProspectNameFallback(array $data, array $originalDotData): array\n {\n if (! array_key_exists('prospect', $data) || ! is_array($data['prospect'])\n || ! array_key_exists('type', $data['prospect'])\n ) {\n return $data;\n }\n\n $existingName = $data['prospect']['name'] ?? null;\n $needsFallback = ! is_string($existingName) || trim($existingName) === '';\n\n if (! $needsFallback) {\n return $data;\n }\n\n $prospectName = null;\n switch ($data['prospect']['type']) {\n case 'lead':\n $prospectName = $originalDotData['prospect.lead.name'] ?? null;\n\n break;\n case 'contact':\n $prospectName = $originalDotData['prospect.contact.name'] ?? null;\n\n break;\n case 'account':\n // Some indices nest account name under account.account.name\n $prospectName = $originalDotData['prospect.account.account.name']\n ?? $originalDotData['prospect.account.name']\n ?? null;\n\n break;\n }\n\n // Normalize whitespace-only fallback names to null (mirror $existingName check)\n if (is_string($prospectName)) {\n $prospectName = trim($prospectName);\n if ($prospectName === '') {\n $prospectName = null;\n }\n }\n\n // set the name key no matter if empty\n $data['prospect']['name'] = $prospectName;\n\n return $data;\n }\n\n public function exportConversationsPerUser(FilterDefinitionCollection $filterSet, array $include): LazyCollection\n {\n $boolQuery = $filterSet->getBoolQuery(\n $filterSet->extractElasticSearchQueries()\n );\n\n $query = (new Query($boolQuery))\n ->setSize(1000)\n ->setSource([\n 'includes' => $include,\n ])\n ->setSort([\n 'scheduled_start_time' => 'asc',\n ]);\n\n return LazyCollection::make(function () use ($query): Generator {\n $scroll = $this->searchService->scroll($query, $this->model);\n\n foreach ($scroll as $resultSet) {\n yield $resultSet;\n }\n })\n ->flatMap(static function (ResultSet $resultSet): array {\n return $resultSet->getResults();\n })\n ->map(static function (Result $result): array {\n $hit = $result->getHit();\n\n return $hit['_source'];\n });\n }\n\n private function queryDrillDownResults(User $consumer, BoolQuery $boolQuery, int $page, int $limit = 25): Collection\n {\n if ($page <= 0) {\n throw new OutOfBoundsException('The page number can only be greater or equal to one');\n }\n\n $map = Collection::make(self::DRILL_DOWN_MAP);\n\n $query = (new Query($boolQuery))\n ->setSort(['scheduled_start_time'])\n ->setSource($map->keys()->all());\n\n if ($limit === 0) {\n $query->setSize(1000);\n $documents = LazyCollection::make(function () use ($query): Generator {\n $scroll = $this->searchService->scroll($query, $this->model);\n\n foreach ($scroll as $resultSet) {\n yield $resultSet;\n }\n })\n ->flatMap(static function (ResultSet $resultSet): array {\n return $resultSet->getDocuments();\n });\n } else {\n $query\n ->setSize($limit)\n ->setFrom(($page - 1) * $limit);\n\n $documents = $this->searchService->search($query, $this->model, 'queryDrillDownResults')->getDocuments();\n }\n\n return Collection::make($documents)\n ->map(static function (Document $document) use ($map): array {\n $documentDataRaw = $document->getData();\n $documentData = array_dot($documentDataRaw);\n\n $data = [];\n foreach ($map as $fromKey => $toKey) {\n if ($toKey === null) {\n continue;\n }\n\n array_set($data, $toKey, $documentData[$fromKey] ?? null);\n }\n $roomOwnerId = $documentData['user.id_string'];\n\n $data['played'] = Collection::make($documentDataRaw['plays'] ?? [])\n ->map(static function (array $data): string {\n return $data['user']['id_string'];\n })\n ->all();\n\n $data['from'] = [\n 'national_phone_number' => Collection::make($documentDataRaw['participants'] ?? [])\n ->filter(static function (array $data) use ($roomOwnerId): bool {\n return array_key_exists('user', $data) && $data['user']['id_string'] === $roomOwnerId;\n })\n ->map(static function (array $data): ?string {\n return $data['phone_number'];\n })\n ->first(),\n ];\n\n $data['isRecorded'] = Collection::make($documentDataRaw['tracks'] ?? [])\n ->filter(static function (array $track): bool {\n return $track['telephony_provider_id'] !== null;\n })\n ->isNotEmpty();\n\n $data['coaches'] = Collection::make(\n is_array($documentDataRaw)\n && array_key_exists('coachingFeedbacks', $documentDataRaw)\n && is_array($documentDataRaw['coachingFeedbacks'])\n ? $documentDataRaw['coachingFeedbacks']\n : [],\n );\n\n return $data;\n })\n ->map(static function (array $data) use ($consumer): array {\n $dotData = array_dot($data);\n // Keep a copy of the original dot data so we can derive names after reshaping\n $originalDotData = $dotData;\n\n // map prospect to lead/contact/account\n if (array_key_exists('prospect.lead.name', $dotData) && $dotData['prospect.lead.name'] !== null) {\n $data['prospect'] = array_get($data, 'prospect.lead');\n $data['prospect']['type'] = 'lead';\n } elseif (array_key_exists('prospect.contact.name', $dotData) && $dotData['prospect.contact.name'] !== null) {\n $data['prospect'] = array_get($data, 'prospect.contact');\n $data['prospect']['type'] = 'contact';\n } elseif (\n array_key_exists('prospect.account.account.name', $dotData)\n && $dotData['prospect.account.account.name'] !== null\n ) {\n $data['prospect'] = array_get($data, 'prospect.account');\n $data['prospect']['type'] = 'account';\n } else {\n unset($data['prospect']);\n }\n\n // Ensure prospect.name is present; if missing or empty, fill using type-based fallback\n $data = self::ensureProspectNameFallback($data, $originalDotData);\n\n $dotData = array_dot($data);\n $nationalPhoneNumber = $dotData['from.national_phone_number'];\n unset($data['from']);\n\n $data['title'] = getActivityTitleAttribute(\n $dotData['organizer.name'],\n $dotData['type'],\n $dotData['title'],\n $dotData['prospect.name'] ?? null,\n $nationalPhoneNumber\n );\n\n if (array_key_exists('category.name', $dotData) && $dotData['category.name'] === null) {\n unset($data['category']['name']);\n }\n\n if (array_key_exists('category', $data) && empty($data['category'])) {\n unset($data['category']);\n }\n\n // Include opportunity if either a value OR a stage label exists.\n $hasOpportunityValue = array_key_exists('opportunity.value', $dotData)\n && $dotData['opportunity.value'] !== null;\n $hasOpportunityStage = array_key_exists('opportunity.stage.label', $dotData)\n && $dotData['opportunity.stage.label'] !== null;\n\n if ($hasOpportunityValue || $hasOpportunityStage) {\n $data['opportunity'] = [];\n\n if ($hasOpportunityValue) {\n $data['opportunity']['formattedValue'] = formatOpportunityValue(\n (float) $dotData['opportunity.value'],\n $dotData['opportunity.currency_code'],\n );\n }\n\n if ($hasOpportunityStage) {\n $data['opportunity']['stage'] = [\n 'label' => $dotData['opportunity.stage.label'],\n ];\n }\n } else {\n unset($data['opportunity']);\n }\n\n if (array_key_exists('played', $data) && is_array($data['played'])) {\n $data['played'] = in_array($consumer->id_string, $data['played'], true);\n }\n\n if (array_key_exists('duration', $data) && $data['duration'] !== null) {\n $data['durationForHumans'] = secondsToHuman((int) $dotData['duration']);\n }\n unset($data['duration']);\n\n return $data;\n })\n // Convert database times to ISO 8601 or frontend will apply timezone conversions\n ->map(static function (array $data): array {\n if (isset($data['actualEndTime'])) {\n $data['actualEndTime'] = Carbon::createFromFormat('Y-m-d H:i:s', $data['actualEndTime'])->toIso8601String();\n }\n\n return $data;\n });\n }\n\n public function getConversationsPerActivityChannelDrillDown(\n User $consumer,\n FilterDefinitionCollection $filterSet,\n string $activityChannel,\n string $drillDownType,\n int $page,\n int $limit = 25,\n ): Collection {\n $boolQuery = $filterSet\n ->getBoolQuery($filterSet->extractElasticSearchQueries())\n ->addMust((new Term())->setTerm('type.keyword', $activityChannel));\n\n switch ($drillDownType) {\n case self::CONVERSATION_DRILLDOWN_SCHEDULED:\n case self::CONVERSATION_DRILLDOWN_ATTEMPTED:\n $boolQuery->addMust(new Exists('scheduled_start_time'));\n\n break;\n case self::CONVERSATION_DRILLDOWN_CONNECTED:\n $boolQuery->addMust(new Exists('actual_start_time'));\n\n break;\n case self::CONVERSATION_DRILLDOWN_LOGGED:\n $boolQuery->addMust(new Exists('crm_provider_id'));\n\n break;\n default:\n throw new OutOfBoundsException('Unsupported drill down type');\n }\n\n return $this->queryDrillDownResults($consumer, $boolQuery, $page, $limit);\n }\n\n public function getCoachingActivitiesDrillDown(\n User $consumer,\n FilterDefinitionCollection $filterSet,\n int $flags,\n int $page,\n int $limit = 25,\n ): Collection {\n $extraFilterDefinitionQueries = [];\n\n if ($this->isBitwiseFlagEnabled($flags, self::FLAG_DRILLDOWN_COMMENT_NEGATIVE)) {\n $extraFilterDefinitionQueries[] = FilterDefinitionQuery::instance()\n ->setQuery(new Terms('comments.type', [Comment::TYPE_GAME_CHANGER]))\n ->setPath('comments', 'comments');\n }\n\n if ($this->isBitwiseFlagEnabled($flags, self::FLAG_DRILLDOWN_COMMENT_POSITIVE)) {\n $extraFilterDefinitionQueries[] = FilterDefinitionQuery::instance()\n ->setQuery(new Terms('comments.type', [Comment::TYPE_POSITIVE]))\n ->setPath('comments', 'comments');\n }\n\n $boolQuery = $filterSet->getBoolQuery(\n $filterSet->extractElasticSearchQueries($extraFilterDefinitionQueries)\n );\n\n return $this->queryDrillDownResults($consumer, $boolQuery, $page, $limit);\n }\n\n public function getCoachingActivitiesOverTimeDrillDown(\n User $consumer,\n FilterDefinitionCollection $filterSet,\n User $coachee,\n ?string $sectionId,\n int $page,\n int $limit = 25\n ): Collection {\n $extraFilterSetQueries = [\n FilterDefinitionQuery::instance()\n ->setQuery((new Term())->setTerm('coachingFeedbacks.visibility', CoachingFeedback::VISIBLE_TO_ALL))\n ->setPath('coachingFeedbacks', 'coachingFeedbacks'),\n FilterDefinitionQuery::instance()\n ->setQuery((new Term())->setTerm('coachingFeedbacks.coachee.id_string', $coachee->id_string))\n ->setPath('coachingFeedbacks.coachee', 'coachingFeedbacks'),\n ];\n\n if (is_string($sectionId)) {\n $extraFilterSetQueries[] = FilterDefinitionQuery::instance()\n ->setQuery((new Term())->setTerm('coachingFeedbacks.sectionFeedbacks.section.id_string', $sectionId))\n ->setPath('coachingFeedbacks.sectionFeedbacks.section', 'coachingFeedbacks.sectionFeedbacks');\n }\n\n $boolQuery = $filterSet->getBoolQuery(\n $filterSet->extractElasticSearchQueries($extraFilterSetQueries)\n );\n\n return $this->queryDrillDownResults($consumer, $boolQuery, $page, $limit);\n }\n\n public function getEngagementActivitiesDrillDown(\n User $consumer,\n FilterDefinitionCollection $filterSet,\n string $elasticsearchColumn,\n int $page,\n int $limit = 25,\n ): Collection {\n $boolQuery = $filterSet->getBoolQuery(\n $filterSet->extractElasticSearchQueries([\n FilterDefinitionQuery::instance()\n ->setQuery(new Exists('stats.' . $elasticsearchColumn))\n ->setPath('stats', 'stats'),\n ])\n );\n\n return $this->queryDrillDownResults($consumer, $boolQuery, $page, $limit);\n }\n\n public function getConversationsActivityChannelPerUserAggregation(FilterDefinitionCollection $filterSet): Collection\n {\n $boolQuery = $filterSet->getBoolQuery(\n $filterSet->extractElasticSearchQueries()\n );\n\n $query = (new Query())\n ->setSize(0)\n ->setSource(false)\n ->addAggregation(\n (new Filter('activities', $boolQuery))\n ->addAggregation(\n (new AggregationTerms('channel'))\n ->setField('type.keyword')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n ->addAggregation(\n (new AggregationTerms('by_user'))\n ->setField('user.id_string.keyword')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n ->addAggregation(\n (new Sum('total_duration'))->setField('duration')\n )\n ->addAggregation(\n new ValueCount('volume', 'id_string.keyword')\n )\n ->addAggregation(\n (new Filter('volume_connected', new Exists('actual_start_time')))\n ->addAggregation(\n new ValueCount('volume', 'id_string.keyword')\n )\n )\n ->addAggregation(\n (new Filter('logged_to_crm', new Exists('crm_provider_id')))\n ->addAggregation(\n new ValueCount('volume', 'id_string.keyword')\n )\n )\n )\n ->addAggregation(\n new AvgBucket('avg_duration', 'by_user>total_duration')\n )\n ->addAggregation(\n new AvgBucket('avg_volume_all', 'by_user>volume')\n )\n ->addAggregation(\n new AvgBucket('avg_volume_logged', 'by_user>logged_to_crm>volume')\n )\n ->addAggregation(\n new AvgBucket('avg_volume_connected', 'by_user>volume_connected>volume')\n )\n )\n );\n\n $results = $this->searchService\n ->search($query, $this->model, 'getConversationsActivityChannelPerUserAggregation')\n ->getAggregations();\n\n $connectableActivityChannels = [\n Activity::TYPE_SOFTPHONE,\n Activity::TYPE_SOFTPHONE_INBOUND,\n Activity::TYPE_CONFERENCE,\n ];\n\n return Collection::make(array_get($results, 'activities.channel.buckets', []))\n ->map(static function (array $bucket) use ($connectableActivityChannels): array {\n $channel = $bucket['key'];\n\n $avgVolume = (float) array_get($bucket, 'avg_volume_all.value', 0);\n $avgVolumeLogged = (float) array_get($bucket, 'avg_volume_logged.value', 0);\n $avgVolumeConnected = (float) array_get($bucket, 'avg_volume_connected.value', 0);\n $avgDuration = (float) array_get($bucket, 'avg_duration.value', 0);\n\n return [\n 'channel' => $channel,\n 'stats' => [\n 'avg_volume' => $avgVolume,\n 'avg_volume_logged' => $avgVolumeLogged,\n 'avg_volume_connected' => $avgVolumeConnected,\n 'avg_duration' => $avgDuration,\n ],\n 'per_user' => Collection::make(array_get($bucket, 'by_user.buckets', []))\n ->keyBy('key')\n ->map(static function (array $userBucket) use ($channel, $connectableActivityChannels): array {\n $data = [\n 'volume_logged' => (int) array_get($userBucket, 'logged_to_crm.doc_count', 0),\n 'volume' => (int) $userBucket['doc_count'],\n 'duration' => (float) array_get($userBucket, 'total_duration.value', 0),\n ];\n\n if (in_array($channel, $connectableActivityChannels, true)) {\n $data['volume_connected'] = (int) array_get($userBucket, 'volume_connected.doc_count');\n }\n\n return $data;\n })\n ->all(),\n ];\n });\n }\n\n public function getDashboardActivityOverTime(\n User $user,\n FilterDefinitionCollection $filterSet,\n string $histogramInterval = 'day'\n ): Collection {\n $timezoneOffset = $user->getTimezoneOffset();\n\n $boolQuery = $filterSet->getBoolQuery(\n $filterSet->extractElasticSearchQueries()\n );\n\n $query = (new Query())\n ->setSize(0)\n ->setSource(false)\n ->addAggregation(\n (new Filter(\n 'voice_activities',\n (clone $boolQuery)\n ->addFilter(\n (new BoolQuery())\n ->addShould((new Term())->setTerm('type.keyword', Activity::TYPE_CONFERENCE))\n ->addShould((new Term())->setTerm('type.keyword', Activity::TYPE_SOFTPHONE))\n ->addShould((new Term())->setTerm('type.keyword', Activity::TYPE_SOFTPHONE_INBOUND))\n )\n ->addFilter(\n new Exists('actual_start_time')\n )\n ))\n ->addAggregation(\n (new DateHistogram('over_time', 'actual_end_time', $histogramInterval))\n ->setFormat('8uuuu-MM-dd')\n ->setTimezone($timezoneOffset)\n ->setMinimumDocumentCount(0)\n ->addAggregation(\n (new AggregationTerms('by_channel'))\n ->setField('type.keyword')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n )\n )\n )\n ->addAggregation(\n (new Filter(\n 'text_activities',\n (clone $boolQuery)\n ->addFilter(\n (new Term())->setTerm('type.keyword', Activity::TYPE_SMS_OUTBOUND)\n )\n ))\n ->addAggregation(\n (new DateHistogram('over_time', 'created_at', $histogramInterval))\n ->setFormat('8uuuu-MM-dd')\n ->setTimezone($timezoneOffset)\n ->setMinimumDocumentCount(0)\n ->addAggregation(\n (new AggregationTerms('by_channel'))\n ->setField('type.keyword')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n )\n )\n );\n\n $aggregationData = $this->searchService->search($query, $this->model, 'getDashboardActivityOverTime')->getAggregations();\n\n $activityData = Collection::make([\n array_get($aggregationData, 'voice_activities.over_time.buckets'),\n array_get($aggregationData, 'text_activities.over_time.buckets'),\n ])\n ->collapse()\n ->reduce(\n static function (array $carry, array $bucketData): array {\n $byChannel = Collection::make(array_get($bucketData, 'by_channel.buckets'))\n ->keyBy('key')\n ->map(static function (array $bucketData): int {\n return $bucketData['doc_count'];\n });\n\n $date = $bucketData['key_as_string'];\n\n if (array_key_exists($date, $carry)) {\n $byChannel = $byChannel->merge($carry[$date]);\n }\n\n $carry[$date] = $byChannel->all();\n\n return $carry;\n },\n []\n );\n\n return Collection::make($activityData);\n }\n\n public function getDashboardCoachingOverTime(\n User $user,\n FilterDefinitionCollection $filterSet,\n ?CarbonImmutable $dateTimeRangeStartsAt,\n ?CarbonImmutable $dateTimeRangeEndsAt,\n string $histogramInterval = 'day',\n ): Collection {\n $filterDefinitionQueries = $filterSet->extractElasticSearchQueries();\n\n $currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('plays');\n\n $boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);\n\n $timezone = $user->getTimezone();\n $timezoneOffset = $user->getTimezoneOffset();\n\n $hasDateRange = $dateTimeRangeStartsAt !== null && $dateTimeRangeEndsAt !== null;\n\n if ($hasDateRange) {\n $resultSetKey = 'from_as_string';\n\n $dateTimeRangeStartsAt = $dateTimeRangeStartsAt->setTimezone($timezone)->toImmutable();\n $dateTimeRangeEndsAt = $dateTimeRangeEndsAt->setTimezone($timezone)->toImmutable();\n\n $dateRangeAggregation = (new DateRange('over_time'))\n ->setField('plays.created_at')\n ->setFormat('8uuuu-MM-dd')\n ->setParam('time_zone', $timezoneOffset);\n\n if ($histogramInterval === 'day') {\n $increment = static function (CarbonImmutable $dateTime): CarbonImmutable {\n return $dateTime->addDay();\n };\n } elseif ($histogramInterval === 'hour') {\n $increment = static function (CarbonImmutable $dateTime): CarbonImmutable {\n return $dateTime->addHour();\n };\n } else {\n throw new OutOfBoundsException('Unknown date time interval');\n }\n\n $periodStartsAt = $dateTimeRangeStartsAt;\n\n while (true) {\n $periodEndsAt = $increment($periodStartsAt);\n\n $dateRangeAggregation->addRange(\n $periodStartsAt->format('Y-m-d'),\n $periodEndsAt->format('Y-m-d')\n );\n\n if ($periodEndsAt >= $dateTimeRangeEndsAt) {\n break;\n }\n\n $periodStartsAt = clone $periodEndsAt;\n }\n } else {\n $resultSetKey = 'key_as_string';\n\n $dateRangeAggregation = (new DateHistogram('over_time', 'plays.created_at', $histogramInterval))\n ->setTimezone($timezoneOffset)\n ->setFormat('8uuuu-MM-dd')\n ->setMinimumDocumentCount(0);\n }\n\n $query = (new Query())\n ->setSize(0)\n ->setSource(false)\n ->addAggregation(\n (new Filter('activities', $boolQuery))\n ->addAggregation(\n (new Nested('playback', 'plays'))\n ->addAggregation(\n (new Filter('filtered', $currentPriorityQuery))\n ->addAggregation($dateRangeAggregation)\n )\n )\n );\n\n $aggregationData = $this->searchService->search($query, $this->model, 'getDashboardCoachingOverTime')->getAggregations();\n\n return Collection::make(array_get($aggregationData, 'activities.playback.filtered.over_time.buckets'))\n ->keyBy($resultSetKey)\n ->map(static function (array $bucketData): int {\n return $bucketData['doc_count'];\n });\n }\n\n public function getDashboardCoachingBreakdownListensByUserRole(FilterDefinitionCollection $filterSet): Collection\n {\n $filterDefinitionQueries = $filterSet->extractElasticSearchQueries();\n\n $currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('plays');\n\n $boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);\n\n $query = (new Query())\n ->setSize(0)\n ->setSource(false)\n ->addAggregation(\n (new Filter('activities', $boolQuery))\n ->addAggregation(\n (new AggregationTerms('by_user'))\n ->setField('user.id_string.keyword')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n ->addAggregation(\n (new Nested('played_by', 'plays'))\n ->addAggregation(\n (new Filter('filtered', $currentPriorityQuery))\n ->addAggregation(\n (new AggregationTerms('user'))\n ->setField('plays.user.id_string.keyword')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n ->addAggregation(\n (new AggregationTerms('role'))\n ->setField('plays.user.roles.name')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n )\n )\n )\n )\n )\n );\n\n $aggregationData = $this->searchService\n ->search($query, $this->model, 'getDashboardCoachingBreakdownListensByUserRole')\n ->getAggregations();\n\n return Collection::make(array_get($aggregationData, 'activities.by_user.buckets'))\n ->keyBy('key')\n ->map(static function (array $roomOwnerBucket): array {\n return Collection::make(array_get($roomOwnerBucket, 'played_by.filtered.user.buckets'))\n ->map(static function (array $playbackBucket): array {\n $userId = $playbackBucket['key'];\n $count = $playbackBucket['doc_count'];\n\n $userRoles = Collection::make(array_get($playbackBucket, 'role.buckets'))\n ->keyBy('key')\n ->keys();\n\n return [\n 'count' => $count,\n 'userId' => $userId,\n 'userRoles' => $userRoles->all(),\n ];\n })\n ->all();\n });\n }\n\n public function getDashboardCoachingBreakdownCoachingFocusFilledByUserRole(\n FilterDefinitionCollection $filterSet\n ): Collection {\n $filterDefinitionQueries = $filterSet->extractElasticSearchQueries();\n\n $currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('comments');\n\n $boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);\n\n $query = (new Query($boolQuery))\n ->setSize(0)\n ->setSource(false)\n ->addAggregation(\n (new AggregationTerms('by_user'))\n ->setField('user.id_string.keyword')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n ->addAggregation(\n (new Nested('commented_by', 'comments'))\n ->addAggregation(\n (new Filter('filtered', $currentPriorityQuery))\n ->addAggregation(\n (new AggregationTerms('user'))\n ->setField('comments.user.id_string.keyword')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n ->addAggregation(\n (new AggregationTerms('role'))\n ->setField('comments.user.roles.name')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n )\n )\n )\n )\n );\n\n $aggregationData = $this->searchService\n ->search($query, $this->model, 'getDashboardCoachingBreakdownCoachingFocusByUserRole')\n ->getAggregations();\n\n return Collection::make(array_get($aggregationData, 'by_user.buckets'))\n ->keyBy('key')\n ->map(static function (array $roomOwnerBucket): array {\n return Collection::make(array_get($roomOwnerBucket, 'commented_by.filtered.user.buckets'))\n ->map(static function (array $commentBucket): array {\n $userId = $commentBucket['key'];\n $count = $commentBucket['doc_count'];\n\n $userRoles = Collection::make(array_get($commentBucket, 'role.buckets'))\n ->keyBy('key')\n ->keys();\n\n return [\n 'count' => $count,\n 'userId' => $userId,\n 'userRoles' => $userRoles->all(),\n ];\n })\n ->all();\n });\n }\n\n private function getCompositeAggregationBy(\n BoolQuery $boolQuery,\n AbstractAggregation $sourceAggregation,\n ?array $customAggregations = null,\n ?AbstractAggregation $aggregationParent = null,\n ?callable $compositeAggregationExtractor = null,\n ?AbstractAggregation $immediateAggregationParent = null\n ): LazyCollection {\n if ($compositeAggregationExtractor === null) {\n $compositeAggregationExtractor = static fn (ResultSet $aggregationData): array => $aggregationData\n ->getAggregation('composite')['buckets'];\n }\n\n return LazyCollection::make(\n function () use (\n $sourceAggregation,\n $customAggregations,\n $aggregationParent,\n $immediateAggregationParent,\n $boolQuery,\n $compositeAggregationExtractor,\n ): Generator {\n $compositeAggregation = (new Composite('composite'))\n ->setSize(self::AGG_COMPOSITE_MAX_SIZE)\n ->addSource($sourceAggregation);\n\n if (is_array($customAggregations)) {\n foreach ($customAggregations as $customAggregation) {\n $compositeAggregation->addAggregation($customAggregation);\n }\n }\n\n $aggregation = $compositeAggregation;\n\n if ($aggregationParent instanceof AbstractAggregation) {\n $aggregation = $aggregationParent;\n\n if ($immediateAggregationParent instanceof AbstractAggregation) {\n $immediateAggregationParent->addAggregation($compositeAggregation);\n } else {\n $aggregationParent->addAggregation($compositeAggregation);\n }\n }\n\n while (true) {\n $query = (new Query($boolQuery))\n ->setSource(false)\n ->addAggregation($aggregation);\n\n $aggregationData = $this->searchService\n ->search($query, $this->model, 'getCompositeAggregationBy');\n\n foreach ($compositeAggregationExtractor($aggregationData) as $bucket) {\n yield $bucket;\n }\n\n $cursor = array_get($aggregationData, 'composite.after_key', null);\n\n if (! is_array($cursor)) {\n break;\n }\n\n $compositeAggregation->addAfter($cursor);\n }\n },\n );\n }\n\n private function getCompositeAggregationByUser(\n BoolQuery $boolQuery,\n ?array $customAggregations = null,\n ?callable $callback = null,\n ): LazyCollection {\n if ($callback === null) {\n $callback = static fn (array $bucket): int => $bucket['doc_count'];\n }\n\n $sourceAggregation = (new AggregationTerms('by_user'))\n ->setField('user.id_string.keyword');\n\n return $this\n ->getCompositeAggregationBy(\n boolQuery: $boolQuery,\n sourceAggregation: $sourceAggregation,\n customAggregations: $customAggregations,\n )\n ->mapWithKeys(static fn (array $bucket): array => [\n $bucket['key']['by_user'] => $bucket,\n ])\n ->map($callback);\n }\n\n private function getCompositeAggregationByCoachingFeedbackCoach(\n BoolQuery $boolQuery,\n ?array $customAggregations = null\n ): LazyCollection {\n return $this\n ->getCompositeAggregationBy(\n $boolQuery,\n (new AggregationTerms('by_feedback_id'))\n ->setField('coachingFeedbacks.id_string'),\n $customAggregations,\n new Nested('coachingFeedbacks', 'coachingFeedbacks'),\n static fn (ResultSet $aggregationData): array => $aggregationData\n ->getAggregation('coachingFeedbacks')['composite']['buckets']\n );\n }\n\n /**\n * @param Collection|string[] $statsOfInterest\n *\n * @return Collection|AbstractAggregation[]\n */\n private function getEngagementStatsAggregation(Collection $statsOfInterest): Collection\n {\n $getAggregation = static function (string $propertyName): Filter {\n $totalAmountProperties = [\n 'talk_time_ratio',\n 'longest_user_monologue',\n 'longest_customer_monologue',\n 'talking_speed',\n 'user_questions',\n ];\n\n $propertyPath = 'stats.' . $propertyName;\n\n $filterQuery = (new BoolQuery())\n ->addMust(new Exists($propertyPath));\n\n if (in_array($propertyName, $totalAmountProperties, true)) {\n $filterQuery->addMust(\n new Range(\n $propertyPath,\n [\n 'gt' => 0,\n ]\n )\n );\n }\n\n return (new Filter($propertyName, $filterQuery))\n ->addAggregation(\n (new Sum('data'))->setField($propertyPath)\n );\n };\n\n return Collection::make()\n ->push(\n $statsOfInterest\n ->reduce(\n static function (Nested $carry, string $propertyName) use ($getAggregation): Nested {\n return $carry->addAggregation($getAggregation($propertyName));\n },\n new Nested('stats', 'stats')\n )\n );\n }\n\n public function getDashboardEngagementStats(FilterDefinitionCollection $filterSet): Collection\n {\n $statsOfInterest = Collection::make([\n 'talkTimeRatio' => 'talk_time_ratio',\n 'longestMonologue' => 'longest_user_monologue',\n 'longestCustomerStory' => 'longest_customer_monologue',\n 'talkingSpeed' => 'talking_speed',\n 'patience' => 'patience_time',\n 'questionRate' => 'user_questions',\n ]);\n\n $boolQuery = $filterSet->getBoolQuery(\n $filterSet->extractElasticSearchQueries()\n );\n\n $aggregationData = $this\n ->getCompositeAggregationByUser(\n $boolQuery,\n $this->getEngagementStatsAggregation($statsOfInterest)->toArray(),\n static function (array $bucket): array {\n $statsBucket = $bucket['stats'];\n unset($statsBucket['doc_count']);\n\n return Collection::make($statsBucket)\n ->map(static function (array $stat): array {\n return [\n 'count' => $stat['doc_count'],\n 'value' => $stat['data']['value'],\n ];\n })\n ->toArray();\n }\n )\n ->collect();\n\n return $statsOfInterest\n ->map(static function (string $propertyName) use ($aggregationData): ?float {\n [$count, $value] = $aggregationData->reduce(\n static function (array $accumulator, array $bucket) use ($propertyName): array {\n $accumulator[0] += (int) $bucket[$propertyName]['count'];\n $accumulator[1] += (float) $bucket[$propertyName]['value'];\n\n return $accumulator;\n },\n [0, 0]\n );\n\n if ($count === 0) {\n return null;\n }\n\n return (float) ($value / $count);\n });\n }\n\n public function getCoachingFeedbacksFilledPerUserAggregation(FilterDefinitionCollection $filterSet): Collection\n {\n $filterDefinitionQueries = $filterSet->extractElasticSearchQueries([\n FilterDefinitionQuery::instance()\n ->setQuery((new Term())->setTerm('coachingFeedbacks.visibility', CoachingFeedback::VISIBLE_TO_ALL))\n ->setPath('coachingFeedbacks', 'coachingFeedbacks'),\n FilterDefinitionQuery::instance()\n ->setQuery((new Term())->setTerm('coachingFeedbacks.coach.status', User::STATUS_ACTIVE))\n ->setPath('coachingFeedbacks', 'coachingFeedbacks'),\n ]);\n\n $currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('coachingFeedbacks');\n $boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);\n\n $data = $this\n ->getCompositeAggregationByUser(\n $boolQuery,\n [\n (new Nested('feedbacks', 'coachingFeedbacks'))\n ->addAggregation(\n (new Filter('filtered', $currentPriorityQuery))\n ->addAggregation(\n (new AggregationTerms('by_user'))\n ->setField('coachingFeedbacks.coach.id_string')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n )\n ),\n ],\n static function (array $bucket): array {\n return array_get($bucket, 'feedbacks.filtered.by_user.buckets', []);\n }\n )\n ->reduce(\n static function (array $carry, array $buckets): array {\n foreach ($buckets as $bucket) {\n $userId = $bucket['key'];\n\n if (! array_key_exists($userId, $carry)) {\n $carry[$userId] = 0;\n }\n\n $carry[$userId] += $bucket['doc_count'];\n }\n\n return $carry;\n },\n []\n );\n\n return Collection::make($data);\n }\n\n public function getCoachingFeedbacksReceivedPerUserAggregation(FilterDefinitionCollection $filterSet): Collection\n {\n $filterDefinitionQueries = $filterSet->extractElasticSearchQueries([\n FilterDefinitionQuery::instance()\n ->setQuery((new Term())->setTerm('coachingFeedbacks.visibility', CoachingFeedback::VISIBLE_TO_ALL))\n ->setPath('coachingFeedbacks', 'coachingFeedbacks'),\n FilterDefinitionQuery::instance()\n ->setQuery((new Term())->setTerm('coachingFeedbacks.coach.status', User::STATUS_ACTIVE))\n ->setPath('coachingFeedbacks', 'coachingFeedbacks'),\n ]);\n\n $currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('coachingFeedbacks');\n $boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);\n\n $data = $this\n ->getCompositeAggregationByUser(\n $boolQuery,\n [\n (new Nested('feedbacks', 'coachingFeedbacks'))\n ->addAggregation(\n (new Filter('filtered', $currentPriorityQuery))\n ->addAggregation(\n (new AggregationTerms('by_user'))\n ->setField('coachingFeedbacks.coachee.id_string')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n ->addAggregation(\n new ValueCount('volume', 'coachingFeedbacks.id_string'),\n )\n )\n ),\n ],\n static function (array $bucket): array {\n return array_get($bucket, 'feedbacks.filtered.by_user.buckets', []);\n }\n )\n ->reduce(\n static function (array $carry, array $buckets): array {\n foreach ($buckets as $bucket) {\n $userId = $bucket['key'];\n\n if (! array_key_exists($userId, $carry)) {\n $carry[$userId] = 0;\n }\n\n $carry[$userId] += $bucket['doc_count'];\n }\n\n return $carry;\n },\n []\n );\n\n return Collection::make($data);\n }\n\n public function getCoachingFeedbacksOverTimeAggregation(\n User $user,\n FilterDefinitionCollection $filterSet,\n ?CarbonImmutable $dateTimeRangeStartsAt,\n ?CarbonImmutable $dateTimeRangeEndsAt,\n string $histogramInterval = 'day',\n ): LazyCollection {\n $timezone = $user->getTimezone();\n $timezoneOffset = $user->getTimezoneOffset();\n\n $filterDefinitionQueries = $filterSet->extractElasticSearchQueries([\n FilterDefinitionQuery::instance()\n ->setQuery((new Term())->setTerm('coachingFeedbacks.visibility', CoachingFeedback::VISIBLE_TO_ALL))\n ->setPath('coachingFeedbacks', 'coachingFeedbacks'),\n ]);\n\n $currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('coachingFeedbacks');\n $boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);\n\n $hasDateRange = $dateTimeRangeStartsAt !== null && $dateTimeRangeEndsAt !== null;\n\n if ($hasDateRange) {\n $resultSetKey = 'from_as_string';\n\n $dateTimeRangeStartsAt = $dateTimeRangeStartsAt->setTimezone($timezone)->toImmutable();\n $dateTimeRangeEndsAt = $dateTimeRangeEndsAt->setTimezone($timezone)->toImmutable();\n\n $dateRangeAggregation = (new DateRange('over_time'))\n ->setField('coachingFeedbacks.updated_at')\n ->setFormat('8uuuu-MM-dd')\n ->setParam('time_zone', $timezoneOffset);\n\n if ($histogramInterval === 'day') {\n $increment = static function (CarbonImmutable $dateTime): CarbonImmutable {\n return $dateTime->addDay();\n };\n } elseif ($histogramInterval === 'hour') {\n $increment = static function (CarbonImmutable $dateTime): CarbonImmutable {\n return $dateTime->addHour();\n };\n } else {\n throw new OutOfBoundsException('Unknown date time interval');\n }\n\n $periodStartsAt = $dateTimeRangeStartsAt;\n\n while (true) {\n $periodEndsAt = $increment($periodStartsAt);\n\n $dateRangeAggregation->addRange(\n $periodStartsAt->format('Y-m-d'),\n $periodEndsAt->format('Y-m-d')\n );\n\n if ($periodEndsAt >= $dateTimeRangeEndsAt) {\n break;\n }\n\n $periodStartsAt = clone $periodEndsAt;\n }\n } else {\n $resultSetKey = 'key_as_string';\n\n $dateRangeAggregation = (new DateHistogram(\n name: 'over_time',\n field: 'coachingFeedbacks.updated_at',\n interval: $histogramInterval,\n ))\n ->setTimezone($timezoneOffset)\n ->setFormat('8uuuu-MM-dd')\n ->setMinimumDocumentCount(0);\n }\n\n $aggregationResult = $this\n ->getCompositeAggregationByCoachingFeedbackCoach(\n $boolQuery,\n [\n (new Filter('filtered', $currentPriorityQuery))\n ->addAggregation($dateRangeAggregation),\n ]\n )\n ->filter(static fn (array $bucket): bool => $bucket['filtered']['doc_count'] > 0)\n ->reduce(\n static function (array $carry, array $bucket) use ($resultSetKey): array {\n $feedbackId = $bucket['key']['by_feedback_id'];\n $date = null;\n\n foreach (array_get($bucket, 'filtered.over_time.buckets') as $dateTime) {\n if ($dateTime['doc_count'] > 0) {\n $date = $dateTime[$resultSetKey];\n\n break;\n }\n }\n\n if ($date === null) {\n return $carry;\n }\n\n $carry[$feedbackId] = $date;\n\n return $carry;\n },\n []\n );\n\n $query = (new Query($boolQuery))\n ->setSource([\n 'id_string',\n 'coachingFeedbacks.id_string',\n 'coachingFeedbacks.coach.id_string',\n 'coachingFeedbacks.coachee.id_string',\n 'coachingFeedbacks.framework.id_string',\n 'coachingFeedbacks.framework.name',\n 'coachingFeedbacks.sectionFeedbacks.section.id_string',\n 'coachingFeedbacks.sectionFeedbacks.section.name',\n 'coachingFeedbacks.sectionFeedbacks.score',\n ]);\n\n return $this->searchService->scrollOverDocuments($query, $this->model)\n ->flatMap(static function (Document $document) use ($aggregationResult): array {\n $data = $document->toArray();\n $activityId = $data['_source']['id_string'];\n\n return Collection::make($data['_source']['coachingFeedbacks'])\n ->filter(\n static fn (array $feedback): bool\n => array_key_exists($feedback['id_string'], $aggregationResult),\n )\n ->flatMap(static function (array $feedback) use ($activityId, $aggregationResult): array {\n if (! array_key_exists('sectionFeedbacks', $feedback)) {\n return [];\n }\n\n $feedbackId = $feedback['id_string'];\n $framework = $feedback['framework'];\n $sectionFeedbacks = $feedback['sectionFeedbacks'];\n\n $coachId = $feedback['coach']['id_string'];\n $coacheeId = $feedback['coachee']['id_string'];\n\n return Collection::make($sectionFeedbacks)\n ->map(static fn (array $section): array => [\n 'activityId' => $activityId,\n 'feedbackUUID' => $feedbackId,\n 'activityTypeName' => $framework['name'],\n 'activityTypeId' => $framework['id_string'],\n 'sectionId' => $section['section']['id_string'],\n 'sectionName' => $section['section']['name'],\n 'score' => $section['score'],\n\n 'date' => $aggregationResult[$feedbackId],\n 'coachId' => $coachId,\n 'coacheeId' => $coacheeId,\n ])\n ->toArray();\n })\n ->toArray();\n })\n ->filter(static fn (array $bucket): bool => ! empty($bucket));\n }\n\n public function getCoachingListensPerUserAggregation(FilterDefinitionCollection $filterSet): Collection\n {\n $filterDefinitionQueries = $filterSet->extractElasticSearchQueries();\n $boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);\n\n $data = $this\n ->getCompositeAggregationByUser(\n $boolQuery,\n [\n (new Nested('playback', 'plays'))\n ->addAggregation(\n (new Filter('filtered', $filterDefinitionQueries->getNestedQuery('plays')))\n ->addAggregation(\n (new AggregationTerms('by_user'))\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n ->setField('plays.user.id_string.keyword')\n )\n ),\n ],\n static function (array $bucket): array {\n return array_get($bucket, 'playback.filtered.by_user.buckets', []);\n }\n )\n ->reduce(\n static function (array $carry, array $buckets): array {\n foreach ($buckets as $bucket) {\n $userId = $bucket['key'];\n\n if (! array_key_exists($userId, $carry)) {\n $carry[$userId] = 0;\n }\n\n $carry[$userId] += $bucket['doc_count'];\n }\n\n return $carry;\n },\n []\n );\n\n return Collection::make($data);\n }\n\n public function getCoachingSharesPerUserAggregation(FilterDefinitionCollection $filterSet): Collection\n {\n $filterDefinitionQueries = $filterSet->extractElasticSearchQueries();\n\n $currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('shares')\n ->addMustNot(new Exists('shares.parent_share_id'));\n\n $boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);\n\n $data = $this\n ->getCompositeAggregationByUser(\n $boolQuery,\n [\n (new Nested('shares', 'shares'))\n ->addAggregation(\n (new Filter('filtered', $currentPriorityQuery))\n ->addAggregation(\n (new AggregationTerms('by_user'))\n ->setField('shares.user.id_string.keyword')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n )\n ),\n ],\n static function (array $bucket): array {\n return array_get($bucket, 'shares.filtered.by_user.buckets', []);\n }\n )\n ->reduce(\n static function (array $carry, array $buckets): array {\n foreach ($buckets as $bucket) {\n $userId = $bucket['key'];\n\n if (! array_key_exists($userId, $carry)) {\n $carry[$userId] = 0;\n }\n\n $carry[$userId] += $bucket['doc_count'];\n }\n\n return $carry;\n },\n []\n );\n\n return Collection::make($data);\n }\n\n /**\n * @param int[] $commentTypes\n */\n public function getCoachingCommentsReceivedUserAggregation(\n FilterDefinitionCollection $filterSet,\n array $commentTypes\n ): array {\n if (empty($commentTypes)) {\n throw new OutOfBoundsException('At least one comment type must be specified');\n }\n\n $filterDefinitionQueries = $filterSet->extractElasticSearchQueries([\n FilterDefinitionQuery::instance()\n ->setQuery(new Terms('comments.type', $commentTypes))\n ->setPath('comments', 'comments'),\n ]);\n\n $boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);\n\n return $this\n ->getCompositeAggregationByUser(\n $boolQuery,\n [\n (new Nested('comments', 'comments'))\n ->addAggregation(\n new Filter('filtered', $filterDefinitionQueries->getNestedQuery('comments'))\n ),\n ],\n static function (array $bucket): int {\n return array_get($bucket, 'comments.filtered.doc_count');\n }\n )\n ->collect()\n ->toArray();\n }\n\n /**\n * @param int[] $commentTypes\n */\n public function getCoachingCommentsFilledUserAggregation(\n FilterDefinitionCollection $filterSet,\n array $commentTypes\n ): array {\n if (empty($commentTypes)) {\n throw new OutOfBoundsException('At least one comment type must be specified');\n }\n\n $filterDefinitionQueries = $filterSet->extractElasticSearchQueries([\n FilterDefinitionQuery::instance()\n ->setQuery(new Terms('comments.type', $commentTypes))\n ->setPath('comments', 'comments'),\n ]);\n\n $currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('comments');\n\n $boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);\n\n return $this\n ->getCompositeAggregationByUser(\n $boolQuery,\n [\n (new Nested('comments', 'comments'))\n ->addAggregation(\n (new Filter('filtered', $currentPriorityQuery))\n ->addAggregation(\n (new AggregationTerms('by_user'))\n ->setField('comments.user.id_string.keyword')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n )\n ),\n ],\n static function (array $bucket): array {\n return array_get($bucket, 'comments.filtered.by_user.buckets', []);\n }\n )\n ->reduce(\n static function (array $carry, array $buckets): array {\n foreach ($buckets as $bucket) {\n $userId = $bucket['key'];\n\n if (! array_key_exists($userId, $carry)) {\n $carry[$userId] = 0;\n }\n\n $carry[$userId] += $bucket['doc_count'];\n }\n\n return $carry;\n },\n []\n );\n }\n\n public function getCoachingPlaylistContributionsPerUserAggregation(\n FilterDefinitionCollection $filterSet,\n ): Collection {\n $filterDefinitionQueries = $filterSet->extractElasticSearchQueries();\n $currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('playlists');\n $boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);\n\n $data = $this\n ->getCompositeAggregationByUser(\n $boolQuery,\n [\n (new Nested('playlists', 'playlists'))\n ->addAggregation(\n (new Filter('filtered', $currentPriorityQuery))\n ->addAggregation(\n (new AggregationTerms('by_user'))\n ->setField('playlists.pivot.user.id_string')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n )\n ),\n ],\n static function (array $bucket): array {\n return array_get($bucket, 'playlists.filtered.by_user.buckets', []);\n }\n )\n ->reduce(\n static function (array $carry, array $buckets): array {\n foreach ($buckets as $bucket) {\n $userId = $bucket['key'];\n\n if (! array_key_exists($userId, $carry)) {\n $carry[$userId] = 0;\n }\n\n $carry[$userId] += $bucket['doc_count'];\n }\n\n return $carry;\n },\n []\n );\n\n return Collection::make($data);\n }\n\n public function getCoachingLiveCoachingPerUserAggregation(FilterDefinitionCollection $filterSet): Collection\n {\n $filterDefinitionQueries = $filterSet->extractElasticSearchQueries();\n $currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('participants');\n $boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);\n\n $data = $this\n ->getCompositeAggregationByUser(\n $boolQuery,\n [\n (new Nested('participants', 'participants'))\n ->addAggregation(\n (new Filter('filtered', $currentPriorityQuery))\n ->addAggregation(\n (new AggregationTerms('by_user'))\n ->setField('participants.user.id_string.keyword')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n )\n ),\n ],\n static function (array $bucket): array {\n return array_get($bucket, 'participants.filtered.by_user.buckets', []);\n }\n )\n ->reduce(\n static function (array $carry, array $buckets): array {\n foreach ($buckets as $bucket) {\n $userId = $bucket['key'];\n\n if (! array_key_exists($userId, $carry)) {\n $carry[$userId] = 0;\n }\n\n $carry[$userId] += $bucket['doc_count'];\n }\n\n return $carry;\n },\n []\n );\n\n return Collection::make($data);\n }\n\n private function getEngagementActivityStatsAggregationResults(\n FilterDefinitionCollection $filterSet,\n string $propertyName,\n ): Collection {\n $statsOfInterest = Collection::make([\n $propertyName,\n ]);\n\n $boolQuery = $filterSet->getBoolQuery(\n $filterSet->extractElasticSearchQueries()\n );\n\n $aggregationData = $this->getCompositeAggregationByUser(\n $boolQuery,\n $this->getEngagementStatsAggregation($statsOfInterest)->toArray(),\n static function (array $bucket): array {\n $statsBucket = $bucket['stats'];\n unset($statsBucket['doc_count']);\n\n return Collection::make($statsBucket)\n ->map(static function (array $stat): array {\n return [\n 'count' => $stat['doc_count'],\n 'value' => $stat['data']['value'],\n ];\n })\n ->toArray();\n }\n );\n\n return $aggregationData\n ->map(static function (array $bucket) use ($propertyName): float {\n $value = $bucket[$propertyName]['value'];\n $count = $bucket[$propertyName]['count'];\n\n if ($count === 0) {\n return 0;\n }\n\n return $value / $count;\n })\n ->collect();\n }\n\n public function getEngagementTalkTimeRatioPerUser(FilterDefinitionCollection $filterSet): Collection\n {\n return $this->getEngagementActivityStatsAggregationResults($filterSet, 'talk_time_ratio');\n }\n\n public function getEngagementLongestMonologuePerUser(FilterDefinitionCollection $filterSet): Collection\n {\n return $this->getEngagementActivityStatsAggregationResults($filterSet, 'longest_user_monologue');\n }\n\n public function getEngagementLongestCustomerStoryPerUser(FilterDefinitionCollection $filterSet): Collection\n {\n return $this->getEngagementActivityStatsAggregationResults($filterSet, 'longest_customer_monologue');\n }\n\n public function getEngagementTalkingSpeedPerUser(FilterDefinitionCollection $filterSet): Collection\n {\n return $this->getEngagementActivityStatsAggregationResults($filterSet, 'talking_speed');\n }\n\n public function getEngagementInteractivityPerUser(FilterDefinitionCollection $filterSet): Collection\n {\n return $this->getEngagementActivityStatsAggregationResults($filterSet, 'interactions');\n }\n\n public function getEngagementPatiencePerUser(FilterDefinitionCollection $filterSet): Collection\n {\n return $this->getEngagementActivityStatsAggregationResults($filterSet, 'patience_time');\n }\n\n public function getEngagementQuestionRatePerUser(FilterDefinitionCollection $filterSet): Collection\n {\n return $this->getEngagementActivityStatsAggregationResults($filterSet, 'user_questions');\n }\n\n public function getCommonTopics(FilterDefinitionCollection $filterSet, int $size): Collection\n {\n $boolQuery = $filterSet->getBoolQuery(\n $filterSet->extractElasticSearchQueries()\n );\n\n $query = (new Query())\n ->setSize(0)\n ->setSource(false)\n ->addAggregation(\n (new Filter('activities', $boolQuery))\n ->addAggregation(\n (new Nested('playback_topic', 'topic_triggers.playback_topic_trigger.playback_topic'))\n ->addAggregation(\n (new AggregationTerms('by_topic'))\n ->setField('topic_triggers.playback_topic_trigger.playback_topic.id_string')\n ->setSize(self::AGG_TERMS_MAX_SIZE)\n ->setOrder('_count', 'desc')\n ->setSize($size)\n )\n )\n );\n\n $aggregationData = $this->searchService->search($query, $this->model, 'getCommonTopics')->getAggregations();\n\n return Collection::make(array_get($aggregationData, 'activities.playback_topic.by_topic.buckets', []))\n ->keyBy('key')\n ->map(static function (array $topicBucket): float {\n return $topicBucket['doc_count'];\n });\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.40957448,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"bounds":{"left":0.41821808,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"bounds":{"left":0.42918882,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"bounds":{"left":0.43783244,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"bounds":{"left":0.44647607,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"bounds":{"left":0.4574468,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"bounds":{"left":0.46841756,"top":0.09896249,"width":0.024268618,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"bounds":{"left":0.4950133,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"bounds":{"left":0.50598407,"top":0.09896249,"width":0.029587766,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"bounds":{"left":0.7084442,"top":0.09896249,"width":0.02825798,"height":0.01915403},"on_screen":true,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"40","depth":4,"bounds":{"left":0.67785907,"top":0.123703115,"width":0.010305851,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.69015956,"top":0.123703115,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"40","depth":4,"bounds":{"left":0.6994681,"top":0.123703115,"width":0.010305851,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"65","depth":4,"bounds":{"left":0.7117686,"top":0.123703115,"width":0.010305851,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.7237367,"top":0.12210695,"width":0.00731383,"height":0.018355945},"on_screen":true,"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.73105055,"top":0.12210695,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993\nSELECT * FROM users WHERE id = 25061;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 994;\nSELECT * FROM crm_profiles WHERE user_id = 25061;\n\nselect * from crm_configurations where id = 834;\nSELECT * FROM teams WHERE id = 882;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;\n\nSELECT * FROM contacts where crm_configuration_id = 834;\nSELECT * FROM opportunities WHERE team_id = 933\n# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');\nAND id IN (8482561,18352941,19042734,19232139,19445140,19472541);\nSELECT * FROM opportunity_contacts\nWHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 485; #\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nselect crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id\nwhere crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')\n# and l.converted_at IS NOT NULL\n;\n\n# ********************************************************************\nSELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')\nand opportunity_id IS NULL\norder by id desc;\n\nSELECT * FROM teams WHERE id = 604; # 598\nSELECT * FROM activities WHERE id = 74410828; # chelseaw@allvoices.co\nSELECT * FROM accounts WHERE id = 20068382;\nSELECT * FROM accounts WHERE id = 35186038;\n\nSELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 559 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;\nselect * from sidekick_settings where team_id = 781;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100\n\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 711;\nSELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL\nand is_internal = 0 and status = 'completed'\norder by id desc;\n\nSELECT * FROM crm_layout_entities\nWHERE crm_layout_id IN (2352, 2353);\n;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 556 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;\nSELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;\nselect * from contacts\nwhere crm_configuration_id = 530\nand crm_provider_id = 872252;\n\nselect * from activities where crm_configuration_id = 530\nand user_id = 14343 and type like '%softphone%'\nand created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);\n\n\nSELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t\nJOIN crm_configurations c ON t.id = c.team_id\nWHERE t.status = 'active';\n\nSELECT * FROM teams where id = 1091;\nSELECT * FROM crm_configurations where team_id = 1091;\nSELECT * FROM activity_providers where team_id = 1091;\nSELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT * FROM teams WHERE name LIKE '%Leadventure%';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1091 and sa.provider = 'salesforce';\n\nSELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812\nSELECT * FROM teams where id = 862;\nSELECT * FROM crm_configurations where team_id = 862;\nSELECT * FROM activity_providers where team_id = 862;\nSELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT t.id, crm.id, crm.provider, ap.* FROM teams t\njoin crm_configurations crm on t.id = crm.team_id\njoin activity_providers ap on t.id = ap.team_id\nwhere t.status = 'active' and ap.is_enabled = 1\nand crm.provider = 'hubspot'\nand ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',\n 'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');\n\nSELECT * FROM teams where id = 1068;\nSELECT * FROM crm_configurations where team_id = 1068;\nSELECT * FROM activity_providers where team_id = 1068;\n\nSELECT * FROM activities a\nwhere crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')\nand a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'\n )\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by a.id desc;\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1068 and sa.provider = 'hubspot';\n\n# ********************************************************************\n# ********************************************************************\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262\nSELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\nselect * from crm_layouts where crm_configuration_id = 834;\nselect * from crm_layout_entities where crm_layout_id = 2780;\nselect * from crm_fields where id IN (321153,321192,321193,321194);\n\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1057 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8\n\nSELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20\n\nSELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10\n\nSELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #\n\nSELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;\nselect * from users where team_id = 51; # 7783\nSELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130\nselect * from activity_searches where user_id = 7783;\nselect * from activity_search_filters where activity_search_id IN (32291, 32292);\n\nSELECT asf.activity_search_id, asf.id, asf.value\nFROM activity_search_filters asf\nWHERE asf.filter = 'group_id'\nAND asf.value IN (\n SELECT CONCAT(\n HEX(SUBSTR(uuid, 5, 4)), '-',\n HEX(SUBSTR(uuid, 3, 2)), '-',\n HEX(SUBSTR(uuid, 1, 2)), '-',\n HEX(SUBSTR(uuid, 9, 2)), '-',\n HEX(SUBSTR(uuid, 11))\n )\n FROM groups\n WHERE deleted_at IS NOT NULL\n);\n\nSELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th\n# ********************************************************************\nSELECT * FROM crm_configurations where provider = 'hubspot';\nSELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133\nSELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null\n# ********************************************************************\n\nselect * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';\nselect\n cp.*\n# DISTINCT t.id\n# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields\nFROM crm_profiles cp\nJOIN crm_configurations crm on crm.id = cp.crm_configuration_id\nJOIN users u on u.id = cp.user_id\nJOIN teams t ON t.id = crm.team_id\nWHERE crm.provider = 'salesforce' and t.status = 'active'\n and cp.archived_at IS NULL and u.deleted_at IS NULL\n and t.id NOT IN (1093)\n and t.id = 2\n and cp.contact_fields IS NULL;\n# and c.crm_provider_id = '003Uu00000ojD4NIAU';\n\nSELECT * FROM users WHERE id = 26484;\nSELECT * FROM crm_profiles WHERE user_id = 26484;\nSELECT * FROM social_accounts WHERE sociable_id = 26484;\nSELECT * FROM crm_configurations where provider = 'salesforce';\nselect * from users where id IN (10022, 10403);\nselect * from users where team_id IN (526);\nselect * from teams where id IN (526, 532);\nselect * from crm_configurations where id IN (500, 516);\nselect * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);\nselect * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 526 and sa.provider = 'salesforce';\nselect * from team_settings where team_id IN (526, 532);\n\nselect * from users where id IN (22824);\nselect * from crm_profiles where crm_configuration_id IN (1026);\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1093 and sa.provider = 'salesforce';\n\nselect * from teams where id = 1099;\nselect * from users where id = 29643\n\nselect * from activity_processing_states;\n\nSELECT * FROM teams where name LIKE '%Fare%'; # 233\nSELECT * FROM opportunities where crm_configuration_id = 215\n# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'\n;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1088 and sa.provider = 'hubspot';\n\nSELECT * FROM teams order by updated_at DESC\nSELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account\n\nselect * from crm_configurations where provider = 'pipedrive';\n\nselect * from teams where id = 957;\nselect * from crm_configurations where id = 957;\n\nSELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743\nSELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;\n\nselect * from users where team_id = 1; # 26726 - Gabriela Dureva\nSELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific\nselect * from activities where user_id = 26726 order by id desc;\nselect * from contacts where crm_configuration_id = 1\nand email IN ('charlotte.ward@prolific.com', 'frankie.bryant@prolific.com'); # 2094416, 2093620\nSELECT * FROM contacts WHERE id = 6284931;\n\nSELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id\nWHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;\n\nselect * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);\nselect * from crm_configurations where id = 1;\n\n43801692-1aeb-32ce-acba-5b80a479701a\n44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b\n405975c0-b3d0-7aaa-821f-09d59cae6dd1\n4caf848d-4bed-2299-b248-7788d41f9fca\n49bedc3f-f196-eef3-89c3-dea6a3b4aa63\n43420989-a09d-b8f8-9806-c8bbf7a02aac\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nSELECT * FROM activities WHERE id = 75461988;\n\nSELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;\n\nselect * from contacts where id = 17900517;\n\nselect * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id\nwhere crm.provider != 'salesforce';\n\nselect * from users where id = 21047;\nSELECT * FROM crm_configurations WHERE id = 892;\nSELECT * FROM teams WHERE id = 942;\nselect * from opportunities where team_id = 942 order by updated_at desc;\nselect * from contacts where team_id = 942 order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 942 and sa.provider = 'hubspot';\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430\nSELECT * FROM crm_configurations WHERE id = 1;\nSELECT * FROM teams WHERE crm_id = 1;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nselect id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1\nSELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430\n\nselect * from teams where id = 852;\nselect * from groups where id = 2286;\nselect * from sidekick_settings where team_id = 852;\nselect * from default_activity_types where team_id = 852;\n\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1 AND u.deleted_at IS NULL\nAND u.crm_required = 1\nAND u.team_id = 1\nORDER BY u.team_id;\n\nSELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (\n18481\n );\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1\n AND u.deleted_at IS NULL\n AND u.crm_required = 1\n# AND u.team_id = 1\n AND p.id IS NULL -- Move this condition to WHERE clause\nORDER BY u.team_id;\n\nSELECT * FROM opportunities WHERE id = 20002609;\nselect * from teams where id = 1122; # Velatir, 29953 - christian@velatir.com\nselect * from crm_configurations where id = 1060;\nselect * from crm_layouts where crm_configuration_id = 1060;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1122 and sa.provider = 'hubspot';\nselect * from opportunities where team_id = 1122 order by updated_at desc;\n\nselect * from crm_field_data where object_type = 'contact';\n\nSELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 248 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS\nSELECT * FROM users where id = 24115;\nSELECT * FROM accounts where id = 4002896;\nSELECT * FROM teams WHERE name LIKE '%adswerve%';\nSELECT * FROM opportunities where crm_configuration_id = 230 AND crm_provider_id IN (\"0069N000003GIQ9QAO\",\"0061r000019yGP9AAM\",\"0066900001S2KWlAAN\",\"0066900001TDpj2AAD\",\"0066900001b8uEwAAI\",\"0069N000001rQi0QAE\",\"006QF00000KD40mYAD\",\"006QF00000LzpRJYAZ\",\"0069N000002uomtQAA\",\"0069N000002xlMLQAY\",\"0066900001NV6ubAAD\",\"0061r00001HJp45AAD\",\"006QF00000uTlUoYAK\",\"006QF00000v0bZqYAI\");\nSELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203\n\nSELECT u.id, u.email, ac.name, a.* FROM activities a\nJOIN users u ON a.user_id = u.id\nJOIN accounts ac ON a.account_id = ac.id\nWHERE\nuuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or\nuuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or\nuuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;\n\nselect * from users where id = 5825;\nSELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;\n\nselect * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;\n19594, 862\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 862 and sa.provider = 'salesforce';\n\nselect * from automated_reports where id = 36;\nselect ar.frequency, r.*, ar.* from automated_report_results r\njoin automated_reports ar on r.report_id = ar.id\nwhere ar.frequency != 'one_off';\n\nselect s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;\nselect * from nudges n where n.activity_search_id\n\nselect * from teams where created_at > '2026-03-09';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;\n\nselect * from users where team_id = 1 and name like '%Lukas%'; # 7160\n\nSELECT * FROM teams WHERE id = 575;\nselect * from opportunities where team_id = 575;\nSELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,\nselect * from opportunities where team_id = 1126;\nSELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,\nselect * from opportunities where team_id = 1125;\nselect * from contacts c\nwhere c.team_id = 882;\n\nSELECT * FROM activities WHERE id = 76822967;\nSELECT * FROM crm_profiles WHERE user_id = 15440;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 555;\nSELECT * FROM crm_configurations WHERE id = 555;\nSELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 581 and sa.provider = 'salesforce';\n\nSELECT * FROM automated_report_results order by id desc;\n\nselect * from features;\nselect * from team_features where feature_id = 40;\n\nselect * from teams where id = 556;\n\nselect * from automated_reports;\nwhere id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , [\"pdf\",\"podcast\"]\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\nselect * from automated_report_results order by id desc;\nSELECT * FROM automated_report_results WHERE id = 1919;\n\nselect * from automated_report_results WHERE report_id = 54;\n\nselect * from opportunities where id = 7594349;\n\nSELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067 - jiminnyintegration@lesmills.com\nselect * from playbooks where team_id = 711; # event 226147\nSELECT * FROM playbook_categories WHERE playbook_id = 5515;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event';\nSELECT * FROM crm_fields WHERE id = 226147;\nSELECT * FROM crm_field_values WHERE crm_field_id = 226147;\n\nSELECT * FROM crm_configurations WHERE id = 692;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 711 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles cp JOIN users u on u.id = cp.user_id WHERE u.team_id = 711;\n\nselect * from leads;\n\nselect * from calendars;\n\nSELECT\n t.id AS team_id,\n t.name,\n LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1)) AS calendar_domain\nFROM teams t\nJOIN users u ON u.team_id = t.id\nJOIN calendars c ON c.user_id = u.id AND c.status = 'active' AND c.calendar_provider_id LIKE '%@%'\nLEFT JOIN team_domains td\n ON td.team_id = t.id\n AND td.deleted_at IS NULL\n AND td.domain = LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1))\nGROUP BY t.id, t.name, calendar_domain\nORDER BY t.name, calendar_domain;\n\nselect * from users u join calendars c on c.user_id = u.id\nwhere u.team_id = 882;\n\n\nselect * from activities where id = 74049485; # team 563 crm 537\nselect * from activities where id = 73272382; # team 563 crm 537\nselect * from activities where id = 64400389; # team 563 crm 537\nselect * from activities where id = 58081273; # team 563 crm 537\nselect * from activities where id = 54520297; # team 563 crm 537\nselect * from participants where activity_id = 58081273;\n\nselect * from activities where crm_configuration_id = 537 and provider = 'aircall'\nand account_id = 19003658 order by updated_at desc;\n\nselect * from contacts where crm_configuration_id = 537 and id = 35957759;\nselect * from accounts where crm_configuration_id = 537 and id = 19003658;\n\nselect * from automated_report_results where id = 1976;\nselect * from automated_reports where id = 583;\nselect * from activity_searches where id = 87714;\nselect * from activity_search_filters where activity_search_id = 87714;\n\nSELECT * FROM activities WHERE uuid_to_bin('8827f672-202d-4162-9d04-73ff5f0566a9') = uuid\nor uuid_to_bin('47842446-af51-4bcb-854f-cc6560290101') = uuid;\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot';\nselect * from rate_limits;\n\nselect * from automated_report_results where media_type = 'pdf' and status = 2\nand id IN (18, 1872);\nselect * from automated_reports where id = 54;\nSELECT * FROM users WHERE id IN (24623,29443,29613);\n\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\n\nselect * from text_relays where created_at > '2026-05-01'\n# and id IN (32415, 32416);\nand id = 32412;\n\nselect * from users where team_id = 2 and email like '%scott%' and id = 29510;\n\nSELECT * FROM activities WHERE uuid_to_bin('67cebfc2-ed56-44a2-8c68-7a0286ed8618') = uuid; # 79763436","depth":4,"on_screen":true,"value":"SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993\nSELECT * FROM users WHERE id = 25061;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 994;\nSELECT * FROM crm_profiles WHERE user_id = 25061;\n\nselect * from crm_configurations where id = 834;\nSELECT * FROM teams WHERE id = 882;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;\n\nSELECT * FROM contacts where crm_configuration_id = 834;\nSELECT * FROM opportunities WHERE team_id = 933\n# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');\nAND id IN (8482561,18352941,19042734,19232139,19445140,19472541);\nSELECT * FROM opportunity_contacts\nWHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 485; #\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nselect crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id\nwhere crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')\n# and l.converted_at IS NOT NULL\n;\n\n# ********************************************************************\nSELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')\nand opportunity_id IS NULL\norder by id desc;\n\nSELECT * FROM teams WHERE id = 604; # 598\nSELECT * FROM activities WHERE id = 74410828; # chelseaw@allvoices.co\nSELECT * FROM accounts WHERE id = 20068382;\nSELECT * FROM accounts WHERE id = 35186038;\n\nSELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 559 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;\nselect * from sidekick_settings where team_id = 781;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100\n\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 711;\nSELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL\nand is_internal = 0 and status = 'completed'\norder by id desc;\n\nSELECT * FROM crm_layout_entities\nWHERE crm_layout_id IN (2352, 2353);\n;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 556 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;\nSELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;\nselect * from contacts\nwhere crm_configuration_id = 530\nand crm_provider_id = 872252;\n\nselect * from activities where crm_configuration_id = 530\nand user_id = 14343 and type like '%softphone%'\nand created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);\n\n\nSELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t\nJOIN crm_configurations c ON t.id = c.team_id\nWHERE t.status = 'active';\n\nSELECT * FROM teams where id = 1091;\nSELECT * FROM crm_configurations where team_id = 1091;\nSELECT * FROM activity_providers where team_id = 1091;\nSELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT * FROM teams WHERE name LIKE '%Leadventure%';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1091 and sa.provider = 'salesforce';\n\nSELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812\nSELECT * FROM teams where id = 862;\nSELECT * FROM crm_configurations where team_id = 862;\nSELECT * FROM activity_providers where team_id = 862;\nSELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT t.id, crm.id, crm.provider, ap.* FROM teams t\njoin crm_configurations crm on t.id = crm.team_id\njoin activity_providers ap on t.id = ap.team_id\nwhere t.status = 'active' and ap.is_enabled = 1\nand crm.provider = 'hubspot'\nand ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',\n 'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');\n\nSELECT * FROM teams where id = 1068;\nSELECT * FROM crm_configurations where team_id = 1068;\nSELECT * FROM activity_providers where team_id = 1068;\n\nSELECT * FROM activities a\nwhere crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')\nand a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'\n )\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by a.id desc;\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1068 and sa.provider = 'hubspot';\n\n# ********************************************************************\n# ********************************************************************\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262\nSELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\nselect * from crm_layouts where crm_configuration_id = 834;\nselect * from crm_layout_entities where crm_layout_id = 2780;\nselect * from crm_fields where id IN (321153,321192,321193,321194);\n\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1057 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8\n\nSELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20\n\nSELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10\n\nSELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #\n\nSELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;\nselect * from users where team_id = 51; # 7783\nSELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130\nselect * from activity_searches where user_id = 7783;\nselect * from activity_search_filters where activity_search_id IN (32291, 32292);\n\nSELECT asf.activity_search_id, asf.id, asf.value\nFROM activity_search_filters asf\nWHERE asf.filter = 'group_id'\nAND asf.value IN (\n SELECT CONCAT(\n HEX(SUBSTR(uuid, 5, 4)), '-',\n HEX(SUBSTR(uuid, 3, 2)), '-',\n HEX(SUBSTR(uuid, 1, 2)), '-',\n HEX(SUBSTR(uuid, 9, 2)), '-',\n HEX(SUBSTR(uuid, 11))\n )\n FROM groups\n WHERE deleted_at IS NOT NULL\n);\n\nSELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th\n# ********************************************************************\nSELECT * FROM crm_configurations where provider = 'hubspot';\nSELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133\nSELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null\n# ********************************************************************\n\nselect * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';\nselect\n cp.*\n# DISTINCT t.id\n# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields\nFROM crm_profiles cp\nJOIN crm_configurations crm on crm.id = cp.crm_configuration_id\nJOIN users u on u.id = cp.user_id\nJOIN teams t ON t.id = crm.team_id\nWHERE crm.provider = 'salesforce' and t.status = 'active'\n and cp.archived_at IS NULL and u.deleted_at IS NULL\n and t.id NOT IN (1093)\n and t.id = 2\n and cp.contact_fields IS NULL;\n# and c.crm_provider_id = '003Uu00000ojD4NIAU';\n\nSELECT * FROM users WHERE id = 26484;\nSELECT * FROM crm_profiles WHERE user_id = 26484;\nSELECT * FROM social_accounts WHERE sociable_id = 26484;\nSELECT * FROM crm_configurations where provider = 'salesforce';\nselect * from users where id IN (10022, 10403);\nselect * from users where team_id IN (526);\nselect * from teams where id IN (526, 532);\nselect * from crm_configurations where id IN (500, 516);\nselect * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);\nselect * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 526 and sa.provider = 'salesforce';\nselect * from team_settings where team_id IN (526, 532);\n\nselect * from users where id IN (22824);\nselect * from crm_profiles where crm_configuration_id IN (1026);\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1093 and sa.provider = 'salesforce';\n\nselect * from teams where id = 1099;\nselect * from users where id = 29643\n\nselect * from activity_processing_states;\n\nSELECT * FROM teams where name LIKE '%Fare%'; # 233\nSELECT * FROM opportunities where crm_configuration_id = 215\n# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'\n;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1088 and sa.provider = 'hubspot';\n\nSELECT * FROM teams order by updated_at DESC\nSELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account\n\nselect * from crm_configurations where provider = 'pipedrive';\n\nselect * from teams where id = 957;\nselect * from crm_configurations where id = 957;\n\nSELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743\nSELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;\n\nselect * from users where team_id = 1; # 26726 - Gabriela Dureva\nSELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific\nselect * from activities where user_id = 26726 order by id desc;\nselect * from contacts where crm_configuration_id = 1\nand email IN ('charlotte.ward@prolific.com', 'frankie.bryant@prolific.com'); # 2094416, 2093620\nSELECT * FROM contacts WHERE id = 6284931;\n\nSELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id\nWHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;\n\nselect * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);\nselect * from crm_configurations where id = 1;\n\n43801692-1aeb-32ce-acba-5b80a479701a\n44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b\n405975c0-b3d0-7aaa-821f-09d59cae6dd1\n4caf848d-4bed-2299-b248-7788d41f9fca\n49bedc3f-f196-eef3-89c3-dea6a3b4aa63\n43420989-a09d-b8f8-9806-c8bbf7a02aac\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nSELECT * FROM activities WHERE id = 75461988;\n\nSELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;\n\nselect * from contacts where id = 17900517;\n\nselect * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id\nwhere crm.provider != 'salesforce';\n\nselect * from users where id = 21047;\nSELECT * FROM crm_configurations WHERE id = 892;\nSELECT * FROM teams WHERE id = 942;\nselect * from opportunities where team_id = 942 order by updated_at desc;\nselect * from contacts where team_id = 942 order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 942 and sa.provider = 'hubspot';\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430\nSELECT * FROM crm_configurations WHERE id = 1;\nSELECT * FROM teams WHERE crm_id = 1;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nselect id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1\nSELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430\n\nselect * from teams where id = 852;\nselect * from groups where id = 2286;\nselect * from sidekick_settings where team_id = 852;\nselect * from default_activity_types where team_id = 852;\n\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1 AND u.deleted_at IS NULL\nAND u.crm_required = 1\nAND u.team_id = 1\nORDER BY u.team_id;\n\nSELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (\n18481\n );\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1\n AND u.deleted_at IS NULL\n AND u.crm_required = 1\n# AND u.team_id = 1\n AND p.id IS NULL -- Move this condition to WHERE clause\nORDER BY u.team_id;\n\nSELECT * FROM opportunities WHERE id = 20002609;\nselect * from teams where id = 1122; # Velatir, 29953 - christian@velatir.com\nselect * from crm_configurations where id = 1060;\nselect * from crm_layouts where crm_configuration_id = 1060;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1122 and sa.provider = 'hubspot';\nselect * from opportunities where team_id = 1122 order by updated_at desc;\n\nselect * from crm_field_data where object_type = 'contact';\n\nSELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 248 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS\nSELECT * FROM users where id = 24115;\nSELECT * FROM accounts where id = 4002896;\nSELECT * FROM teams WHERE name LIKE '%adswerve%';\nSELECT * FROM opportunities where crm_configuration_id = 230 AND crm_provider_id IN (\"0069N000003GIQ9QAO\",\"0061r000019yGP9AAM\",\"0066900001S2KWlAAN\",\"0066900001TDpj2AAD\",\"0066900001b8uEwAAI\",\"0069N000001rQi0QAE\",\"006QF00000KD40mYAD\",\"006QF00000LzpRJYAZ\",\"0069N000002uomtQAA\",\"0069N000002xlMLQAY\",\"0066900001NV6ubAAD\",\"0061r00001HJp45AAD\",\"006QF00000uTlUoYAK\",\"006QF00000v0bZqYAI\");\nSELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203\n\nSELECT u.id, u.email, ac.name, a.* FROM activities a\nJOIN users u ON a.user_id = u.id\nJOIN accounts ac ON a.account_id = ac.id\nWHERE\nuuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or\nuuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or\nuuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;\n\nselect * from users where id = 5825;\nSELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;\n\nselect * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;\n19594, 862\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 862 and sa.provider = 'salesforce';\n\nselect * from automated_reports where id = 36;\nselect ar.frequency, r.*, ar.* from automated_report_results r\njoin automated_reports ar on r.report_id = ar.id\nwhere ar.frequency != 'one_off';\n\nselect s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;\nselect * from nudges n where n.activity_search_id\n\nselect * from teams where created_at > '2026-03-09';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;\n\nselect * from users where team_id = 1 and name like '%Lukas%'; # 7160\n\nSELECT * FROM teams WHERE id = 575;\nselect * from opportunities where team_id = 575;\nSELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,\nselect * from opportunities where team_id = 1126;\nSELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,\nselect * from opportunities where team_id = 1125;\nselect * from contacts c\nwhere c.team_id = 882;\n\nSELECT * FROM activities WHERE id = 76822967;\nSELECT * FROM crm_profiles WHERE user_id = 15440;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 555;\nSELECT * FROM crm_configurations WHERE id = 555;\nSELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 581 and sa.provider = 'salesforce';\n\nSELECT * FROM automated_report_results order by id desc;\n\nselect * from features;\nselect * from team_features where feature_id = 40;\n\nselect * from teams where id = 556;\n\nselect * from automated_reports;\nwhere id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , [\"pdf\",\"podcast\"]\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\nselect * from automated_report_results order by id desc;\nSELECT * FROM automated_report_results WHERE id = 1919;\n\nselect * from automated_report_results WHERE report_id = 54;\n\nselect * from opportunities where id = 7594349;\n\nSELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067 - jiminnyintegration@lesmills.com\nselect * from playbooks where team_id = 711; # event 226147\nSELECT * FROM playbook_categories WHERE playbook_id = 5515;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event';\nSELECT * FROM crm_fields WHERE id = 226147;\nSELECT * FROM crm_field_values WHERE crm_field_id = 226147;\n\nSELECT * FROM crm_configurations WHERE id = 692;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 711 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles cp JOIN users u on u.id = cp.user_id WHERE u.team_id = 711;\n\nselect * from leads;\n\nselect * from calendars;\n\nSELECT\n t.id AS team_id,\n t.name,\n LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1)) AS calendar_domain\nFROM teams t\nJOIN users u ON u.team_id = t.id\nJOIN calendars c ON c.user_id = u.id AND c.status = 'active' AND c.calendar_provider_id LIKE '%@%'\nLEFT JOIN team_domains td\n ON td.team_id = t.id\n AND td.deleted_at IS NULL\n AND td.domain = LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1))\nGROUP BY t.id, t.name, calendar_domain\nORDER BY t.name, calendar_domain;\n\nselect * from users u join calendars c on c.user_id = u.id\nwhere u.team_id = 882;\n\n\nselect * from activities where id = 74049485; # team 563 crm 537\nselect * from activities where id = 73272382; # team 563 crm 537\nselect * from activities where id = 64400389; # team 563 crm 537\nselect * from activities where id = 58081273; # team 563 crm 537\nselect * from activities where id = 54520297; # team 563 crm 537\nselect * from participants where activity_id = 58081273;\n\nselect * from activities where crm_configuration_id = 537 and provider = 'aircall'\nand account_id = 19003658 order by updated_at desc;\n\nselect * from contacts where crm_configuration_id = 537 and id = 35957759;\nselect * from accounts where crm_configuration_id = 537 and id = 19003658;\n\nselect * from automated_report_results where id = 1976;\nselect * from automated_reports where id = 583;\nselect * from activity_searches where id = 87714;\nselect * from activity_search_filters where activity_search_id = 87714;\n\nSELECT * FROM activities WHERE uuid_to_bin('8827f672-202d-4162-9d04-73ff5f0566a9') = uuid\nor uuid_to_bin('47842446-af51-4bcb-854f-cc6560290101') = uuid;\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot';\nselect * from rate_limits;\n\nselect * from automated_report_results where media_type = 'pdf' and status = 2\nand id IN (18, 1872);\nselect * from automated_reports where id = 54;\nSELECT * FROM users WHERE id IN (24623,29443,29613);\n\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\n\nselect * from text_relays where created_at > '2026-05-01'\n# and id IN (32415, 32416);\nand id = 32412;\n\nselect * from users where team_id = 2 and email like '%scott%' and id = 29510;\n\nSELECT * FROM activities WHERE uuid_to_bin('67cebfc2-ed56-44a2-8c68-7a0286ed8618') = uuid; # 79763436","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
7956840701792918477
|
-8967423967473009248
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20891-improve-sms-text Project: faVsco.js, menu
JY-20891-improve-sms-text-relays, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
CONTEXT_TEAM_INSIGHTS_ACTIVITY
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
0 results
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Sync Changes
Hide This Notification
Code changed:
Hide
1
6
11
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Repositories;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use Elastica\Aggregation\AbstractAggregation;
use Elastica\Aggregation\AvgBucket;
use Elastica\Aggregation\Composite;
use Elastica\Aggregation\DateHistogram;
use Elastica\Aggregation\DateRange;
use Elastica\Aggregation\Filter;
use Elastica\Aggregation\Nested;
use Elastica\Aggregation\Sum;
use Elastica\Aggregation\Terms as AggregationTerms;
use Elastica\Aggregation\ValueCount;
use Elastica\Document;
use Elastica\Query;
use Elastica\Query\BoolQuery;
use Elastica\Query\Exists;
use Elastica\Query\Range;
use Elastica\Query\Term;
use Elastica\Query\Terms;
use Elastica\Result;
use Elastica\ResultSet;
use Generator;
use Illuminate\Support\Collection;
use Illuminate\Support\LazyCollection;
use Jiminny\Component\ActivitySearch\FilterDefinitionCollection;
use Jiminny\Component\ActivitySearch\FilterDefinitionQuery;
use Jiminny\Component\ElasticSearch\Service\Search;
use Jiminny\Component\Math\BitwiseOperations;
use Jiminny\Exceptions\OutOfBoundsException;
use Jiminny\Models\Activity;
use Jiminny\Models\Activity\Comment;
use Jiminny\Models\CoachingFeedback;
use Jiminny\Models\User;
class TeamInsightsRepository
{
use BitwiseOperations;
public const array CONVERSATION_DRILLDOWNS = [
self::CONVERSATION_DRILLDOWN_SCHEDULED,
self::CONVERSATION_DRILLDOWN_ATTEMPTED,
self::CONVERSATION_DRILLDOWN_CONNECTED,
self::CONVERSATION_DRILLDOWN_LOGGED,
];
public const string CONVERSATION_DRILLDOWN_CONNECTED = 'connected';
public const string CONVERSATION_DRILLDOWN_SCHEDULED = 'scheduled';
public const string CONVERSATION_DRILLDOWN_ATTEMPTED = 'attempted';
public const string CONVERSATION_DRILLDOWN_LOGGED = 'logged';
public const array DRILL_DOWN_MAP = [
'id_string' => 'id',
'title' => 'title',
'user.id_string' => null, // needed for indirect mapping
'user.name' => 'organizer.name',
'user.job.name' => 'organizer.job.name',
'user.photo_url' => 'organizer.photoUrl',
'type' => 'type',
'lead.name' => 'prospect.lead.name',
'lead.company' => 'prospect.lead.company',
'contact.name' => 'prospect.contact.name',
'contact.account.name' => 'prospect.contact.account.name',
'account.name' => 'prospect.account.account.name',
'participants.user.id_string' => null, // this would return an array and needs manual mapping
'participants.country_code' => null, // this would return an array and needs manual mapping
'participants.phone_number' => null, // this would return an array and needs manual mapping
'favorite_count' => 'stats.favorites',
'share_count' => 'stats.shares',
'comment_count' => 'stats.comments',
'play_count' => 'stats.plays',
'stats.talk_time_ratio' => 'stats.talkTimeRatio',
'stats.talking_speed' => 'stats.talkingSpeed',
'stats.user_questions' => 'stats.userQuestionsCount',
'stats.longest_user_monologue' => 'stats.longestUserMonologue',
'stats.longest_customer_monologue' => 'stats.longestCustomerMonologue',
'stats.patience_time' => 'stats.patienceTime',
'plays.user.id_string' => null, // this would return an array and needs manual mapping
'average_score' => 'averageScore',
'ai_call_score.score' => 'aiCallScore',
'category.name' => 'category.name',
'opportunity.value' => 'opportunity.value',
'opportunity.currency_code' => 'opportunity.currency_code',
'opportunity.stage.label' => 'opportunity.stage.label',
'duration' => 'duration',
'actual_end_time' => 'actualEndTime',
'tracks.telephony_provider_id' => null, // this would return an array and needs manual mapping
'coachingFeedbacks.coach.name' => null, // this would return an array and needs manual mapping
];
public const int FLAG_DRILLDOWN_COMMENT_POSITIVE = 1;
public const int FLAG_DRILLDOWN_COMMENT_NEGATIVE = 2;
private const int AGG_TERMS_MAX_SIZE = 9999;
private const int AGG_COMPOSITE_MAX_SIZE = 10000;
private const int AGGREGATE_VALUE_AVG = 0;
private const int AGGREGATE_VALUE_MAX = 1;
private const int AGGREGATE_VALUE_SUM = 2;
public function __construct(
private readonly Search $searchService,
private readonly Activity $model,
) {
}
/**
* Ensure prospect.name is present and non-empty by applying a type-based fallback
* from the original dot-notated data structure.
*
* - type lead => prospect.lead.name
* - type contact => prospect.contact.name
* - type account => prospect.account.account.name or prospect.account.name
*/
private static function ensureProspectNameFallback(array $data, array $originalDotData): array
{
if (! array_key_exists('prospect', $data) || ! is_array($data['prospect'])
|| ! array_key_exists('type', $data['prospect'])
) {
return $data;
}
$existingName = $data['prospect']['name'] ?? null;
$needsFallback = ! is_string($existingName) || trim($existingName) === '';
if (! $needsFallback) {
return $data;
}
$prospectName = null;
switch ($data['prospect']['type']) {
case 'lead':
$prospectName = $originalDotData['prospect.lead.name'] ?? null;
break;
case 'contact':
$prospectName = $originalDotData['prospect.contact.name'] ?? null;
break;
case 'account':
// Some indices nest account name under account.account.name
$prospectName = $originalDotData['prospect.account.account.name']
?? $originalDotData['prospect.account.name']
?? null;
break;
}
// Normalize whitespace-only fallback names to null (mirror $existingName check)
if (is_string($prospectName)) {
$prospectName = trim($prospectName);
if ($prospectName === '') {
$prospectName = null;
}
}
// set the name key no matter if empty
$data['prospect']['name'] = $prospectName;
return $data;
}
public function exportConversationsPerUser(FilterDefinitionCollection $filterSet, array $include): LazyCollection
{
$boolQuery = $filterSet->getBoolQuery(
$filterSet->extractElasticSearchQueries()
);
$query = (new Query($boolQuery))
->setSize(1000)
->setSource([
'includes' => $include,
])
->setSort([
'scheduled_start_time' => 'asc',
]);
return LazyCollection::make(function () use ($query): Generator {
$scroll = $this->searchService->scroll($query, $this->model);
foreach ($scroll as $resultSet) {
yield $resultSet;
}
})
->flatMap(static function (ResultSet $resultSet): array {
return $resultSet->getResults();
})
->map(static function (Result $result): array {
$hit = $result->getHit();
return $hit['_source'];
});
}
private function queryDrillDownResults(User $consumer, BoolQuery $boolQuery, int $page, int $limit = 25): Collection
{
if ($page <= 0) {
throw new OutOfBoundsException('The page number can only be greater or equal to one');
}
$map = Collection::make(self::DRILL_DOWN_MAP);
$query = (new Query($boolQuery))
->setSort(['scheduled_start_time'])
->setSource($map->keys()->all());
if ($limit === 0) {
$query->setSize(1000);
$documents = LazyCollection::make(function () use ($query): Generator {
$scroll = $this->searchService->scroll($query, $this->model);
foreach ($scroll as $resultSet) {
yield $resultSet;
}
})
->flatMap(static function (ResultSet $resultSet): array {
return $resultSet->getDocuments();
});
} else {
$query
->setSize($limit)
->setFrom(($page - 1) * $limit);
$documents = $this->searchService->search($query, $this->model, 'queryDrillDownResults')->getDocuments();
}
return Collection::make($documents)
->map(static function (Document $document) use ($map): array {
$documentDataRaw = $document->getData();
$documentData = array_dot($documentDataRaw);
$data = [];
foreach ($map as $fromKey => $toKey) {
if ($toKey === null) {
continue;
}
array_set($data, $toKey, $documentData[$fromKey] ?? null);
}
$roomOwnerId = $documentData['user.id_string'];
$data['played'] = Collection::make($documentDataRaw['plays'] ?? [])
->map(static function (array $data): string {
return $data['user']['id_string'];
})
->all();
$data['from'] = [
'national_phone_number' => Collection::make($documentDataRaw['participants'] ?? [])
->filter(static function (array $data) use ($roomOwnerId): bool {
return array_key_exists('user', $data) && $data['user']['id_string'] === $roomOwnerId;
})
->map(static function (array $data): ?string {
return $data['phone_number'];
})
->first(),
];
$data['isRecorded'] = Collection::make($documentDataRaw['tracks'] ?? [])
->filter(static function (array $track): bool {
return $track['telephony_provider_id'] !== null;
})
->isNotEmpty();
$data['coaches'] = Collection::make(
is_array($documentDataRaw)
&& array_key_exists('coachingFeedbacks', $documentDataRaw)
&& is_array($documentDataRaw['coachingFeedbacks'])
? $documentDataRaw['coachingFeedbacks']
: [],
);
return $data;
})
->map(static function (array $data) use ($consumer): array {
$dotData = array_dot($data);
// Keep a copy of the original dot data so we can derive names after reshaping
$originalDotData = $dotData;
// map prospect to lead/contact/account
if (array_key_exists('prospect.lead.name', $dotData) && $dotData['prospect.lead.name'] !== null) {
$data['prospect'] = array_get($data, 'prospect.lead');
$data['prospect']['type'] = 'lead';
} elseif (array_key_exists('prospect.contact.name', $dotData) && $dotData['prospect.contact.name'] !== null) {
$data['prospect'] = array_get($data, 'prospect.contact');
$data['prospect']['type'] = 'contact';
} elseif (
array_key_exists('prospect.account.account.name', $dotData)
&& $dotData['prospect.account.account.name'] !== null
) {
$data['prospect'] = array_get($data, 'prospect.account');
$data['prospect']['type'] = 'account';
} else {
unset($data['prospect']);
}
// Ensure prospect.name is present; if missing or empty, fill using type-based fallback
$data = self::ensureProspectNameFallback($data, $originalDotData);
$dotData = array_dot($data);
$nationalPhoneNumber = $dotData['from.national_phone_number'];
unset($data['from']);
$data['title'] = getActivityTitleAttribute(
$dotData['organizer.name'],
$dotData['type'],
$dotData['title'],
$dotData['prospect.name'] ?? null,
$nationalPhoneNumber
);
if (array_key_exists('category.name', $dotData) && $dotData['category.name'] === null) {
unset($data['category']['name']);
}
if (array_key_exists('category', $data) && empty($data['category'])) {
unset($data['category']);
}
// Include opportunity if either a value OR a stage label exists.
$hasOpportunityValue = array_key_exists('opportunity.value', $dotData)
&& $dotData['opportunity.value'] !== null;
$hasOpportunityStage = array_key_exists('opportunity.stage.label', $dotData)
&& $dotData['opportunity.stage.label'] !== null;
if ($hasOpportunityValue || $hasOpportunityStage) {
$data['opportunity'] = [];
if ($hasOpportunityValue) {
$data['opportunity']['formattedValue'] = formatOpportunityValue(
(float) $dotData['opportunity.value'],
$dotData['opportunity.currency_code'],
);
}
if ($hasOpportunityStage) {
$data['opportunity']['stage'] = [
'label' => $dotData['opportunity.stage.label'],
];
}
} else {
unset($data['opportunity']);
}
if (array_key_exists('played', $data) && is_array($data['played'])) {
$data['played'] = in_array($consumer->id_string, $data['played'], true);
}
if (array_key_exists('duration', $data) && $data['duration'] !== null) {
$data['durationForHumans'] = secondsToHuman((int) $dotData['duration']);
}
unset($data['duration']);
return $data;
})
// Convert database times to ISO 8601 or frontend will apply timezone conversions
->map(static function (array $data): array {
if (isset($data['actualEndTime'])) {
$data['actualEndTime'] = Carbon::createFromFormat('Y-m-d H:i:s', $data['actualEndTime'])->toIso8601String();
}
return $data;
});
}
public function getConversationsPerActivityChannelDrillDown(
User $consumer,
FilterDefinitionCollection $filterSet,
string $activityChannel,
string $drillDownType,
int $page,
int $limit = 25,
): Collection {
$boolQuery = $filterSet
->getBoolQuery($filterSet->extractElasticSearchQueries())
->addMust((new Term())->setTerm('type.keyword', $activityChannel));
switch ($drillDownType) {
case self::CONVERSATION_DRILLDOWN_SCHEDULED:
case self::CONVERSATION_DRILLDOWN_ATTEMPTED:
$boolQuery->addMust(new Exists('scheduled_start_time'));
break;
case self::CONVERSATION_DRILLDOWN_CONNECTED:
$boolQuery->addMust(new Exists('actual_start_time'));
break;
case self::CONVERSATION_DRILLDOWN_LOGGED:
$boolQuery->addMust(new Exists('crm_provider_id'));
break;
default:
throw new OutOfBoundsException('Unsupported drill down type');
}
return $this->queryDrillDownResults($consumer, $boolQuery, $page, $limit);
}
public function getCoachingActivitiesDrillDown(
User $consumer,
FilterDefinitionCollection $filterSet,
int $flags,
int $page,
int $limit = 25,
): Collection {
$extraFilterDefinitionQueries = [];
if ($this->isBitwiseFlagEnabled($flags, self::FLAG_DRILLDOWN_COMMENT_NEGATIVE)) {
$extraFilterDefinitionQueries[] = FilterDefinitionQuery::instance()
->setQuery(new Terms('comments.type', [Comment::TYPE_GAME_CHANGER]))
->setPath('comments', 'comments');
}
if ($this->isBitwiseFlagEnabled($flags, self::FLAG_DRILLDOWN_COMMENT_POSITIVE)) {
$extraFilterDefinitionQueries[] = FilterDefinitionQuery::instance()
->setQuery(new Terms('comments.type', [Comment::TYPE_POSITIVE]))
->setPath('comments', 'comments');
}
$boolQuery = $filterSet->getBoolQuery(
$filterSet->extractElasticSearchQueries($extraFilterDefinitionQueries)
);
return $this->queryDrillDownResults($consumer, $boolQuery, $page, $limit);
}
public function getCoachingActivitiesOverTimeDrillDown(
User $consumer,
FilterDefinitionCollection $filterSet,
User $coachee,
?string $sectionId,
int $page,
int $limit = 25
): Collection {
$extraFilterSetQueries = [
FilterDefinitionQuery::instance()
->setQuery((new Term())->setTerm('coachingFeedbacks.visibility', CoachingFeedback::VISIBLE_TO_ALL))
->setPath('coachingFeedbacks', 'coachingFeedbacks'),
FilterDefinitionQuery::instance()
->setQuery((new Term())->setTerm('coachingFeedbacks.coachee.id_string', $coachee->id_string))
->setPath('coachingFeedbacks.coachee', 'coachingFeedbacks'),
];
if (is_string($sectionId)) {
$extraFilterSetQueries[] = FilterDefinitionQuery::instance()
->setQuery((new Term())->setTerm('coachingFeedbacks.sectionFeedbacks.section.id_string', $sectionId))
->setPath('coachingFeedbacks.sectionFeedbacks.section', 'coachingFeedbacks.sectionFeedbacks');
}
$boolQuery = $filterSet->getBoolQuery(
$filterSet->extractElasticSearchQueries($extraFilterSetQueries)
);
return $this->queryDrillDownResults($consumer, $boolQuery, $page, $limit);
}
public function getEngagementActivitiesDrillDown(
User $consumer,
FilterDefinitionCollection $filterSet,
string $elasticsearchColumn,
int $page,
int $limit = 25,
): Collection {
$boolQuery = $filterSet->getBoolQuery(
$filterSet->extractElasticSearchQueries([
FilterDefinitionQuery::instance()
->setQuery(new Exists('stats.' . $elasticsearchColumn))
->setPath('stats', 'stats'),
])
);
return $this->queryDrillDownResults($consumer, $boolQuery, $page, $limit);
}
public function getConversationsActivityChannelPerUserAggregation(FilterDefinitionCollection $filterSet): Collection
{
$boolQuery = $filterSet->getBoolQuery(
$filterSet->extractElasticSearchQueries()
);
$query = (new Query())
->setSize(0)
->setSource(false)
->addAggregation(
(new Filter('activities', $boolQuery))
->addAggregation(
(new AggregationTerms('channel'))
->setField('type.keyword')
->setSize(self::AGG_TERMS_MAX_SIZE)
->addAggregation(
(new AggregationTerms('by_user'))
->setField('user.id_string.keyword')
->setSize(self::AGG_TERMS_MAX_SIZE)
->addAggregation(
(new Sum('total_duration'))->setField('duration')
)
->addAggregation(
new ValueCount('volume', 'id_string.keyword')
)
->addAggregation(
(new Filter('volume_connected', new Exists('actual_start_time')))
->addAggregation(
new ValueCount('volume', 'id_string.keyword')
)
)
->addAggregation(
(new Filter('logged_to_crm', new Exists('crm_provider_id')))
->addAggregation(
new ValueCount('volume', 'id_string.keyword')
)
)
)
->addAggregation(
new AvgBucket('avg_duration', 'by_user>total_duration')
)
->addAggregation(
new AvgBucket('avg_volume_all', 'by_user>volume')
)
->addAggregation(
new AvgBucket('avg_volume_logged', 'by_user>logged_to_crm>volume')
)
->addAggregation(
new AvgBucket('avg_volume_connected', 'by_user>volume_connected>volume')
)
)
);
$results = $this->searchService
->search($query, $this->model, 'getConversationsActivityChannelPerUserAggregation')
->getAggregations();
$connectableActivityChannels = [
Activity::TYPE_SOFTPHONE,
Activity::TYPE_SOFTPHONE_INBOUND,
Activity::TYPE_CONFERENCE,
];
return Collection::make(array_get($results, 'activities.channel.buckets', []))
->map(static function (array $bucket) use ($connectableActivityChannels): array {
$channel = $bucket['key'];
$avgVolume = (float) array_get($bucket, 'avg_volume_all.value', 0);
$avgVolumeLogged = (float) array_get($bucket, 'avg_volume_logged.value', 0);
$avgVolumeConnected = (float) array_get($bucket, 'avg_volume_connected.value', 0);
$avgDuration = (float) array_get($bucket, 'avg_duration.value', 0);
return [
'channel' => $channel,
'stats' => [
'avg_volume' => $avgVolume,
'avg_volume_logged' => $avgVolumeLogged,
'avg_volume_connected' => $avgVolumeConnected,
'avg_duration' => $avgDuration,
],
'per_user' => Collection::make(array_get($bucket, 'by_user.buckets', []))
->keyBy('key')
->map(static function (array $userBucket) use ($channel, $connectableActivityChannels): array {
$data = [
'volume_logged' => (int) array_get($userBucket, 'logged_to_crm.doc_count', 0),
'volume' => (int) $userBucket['doc_count'],
'duration' => (float) array_get($userBucket, 'total_duration.value', 0),
];
if (in_array($channel, $connectableActivityChannels, true)) {
$data['volume_connected'] = (int) array_get($userBucket, 'volume_connected.doc_count');
}
return $data;
})
->all(),
];
});
}
public function getDashboardActivityOverTime(
User $user,
FilterDefinitionCollection $filterSet,
string $histogramInterval = 'day'
): Collection {
$timezoneOffset = $user->getTimezoneOffset();
$boolQuery = $filterSet->getBoolQuery(
$filterSet->extractElasticSearchQueries()
);
$query = (new Query())
->setSize(0)
->setSource(false)
->addAggregation(
(new Filter(
'voice_activities',
(clone $boolQuery)
->addFilter(
(new BoolQuery())
->addShould((new Term())->setTerm('type.keyword', Activity::TYPE_CONFERENCE))
->addShould((new Term())->setTerm('type.keyword', Activity::TYPE_SOFTPHONE))
->addShould((new Term())->setTerm('type.keyword', Activity::TYPE_SOFTPHONE_INBOUND))
)
->addFilter(
new Exists('actual_start_time')
)
))
->addAggregation(
(new DateHistogram('over_time', 'actual_end_time', $histogramInterval))
->setFormat('8uuuu-MM-dd')
->setTimezone($timezoneOffset)
->setMinimumDocumentCount(0)
->addAggregation(
(new AggregationTerms('by_channel'))
->setField('type.keyword')
->setSize(self::AGG_TERMS_MAX_SIZE)
)
)
)
->addAggregation(
(new Filter(
'text_activities',
(clone $boolQuery)
->addFilter(
(new Term())->setTerm('type.keyword', Activity::TYPE_SMS_OUTBOUND)
)
))
->addAggregation(
(new DateHistogram('over_time', 'created_at', $histogramInterval))
->setFormat('8uuuu-MM-dd')
->setTimezone($timezoneOffset)
->setMinimumDocumentCount(0)
->addAggregation(
(new AggregationTerms('by_channel'))
->setField('type.keyword')
->setSize(self::AGG_TERMS_MAX_SIZE)
)
)
);
$aggregationData = $this->searchService->search($query, $this->model, 'getDashboardActivityOverTime')->getAggregations();
$activityData = Collection::make([
array_get($aggregationData, 'voice_activities.over_time.buckets'),
array_get($aggregationData, 'text_activities.over_time.buckets'),
])
->collapse()
->reduce(
static function (array $carry, array $bucketData): array {
$byChannel = Collection::make(array_get($bucketData, 'by_channel.buckets'))
->keyBy('key')
->map(static function (array $bucketData): int {
return $bucketData['doc_count'];
});
$date = $bucketData['key_as_string'];
if (array_key_exists($date, $carry)) {
$byChannel = $byChannel->merge($carry[$date]);
}
$carry[$date] = $byChannel->all();
return $carry;
},
[]
);
return Collection::make($activityData);
}
public function getDashboardCoachingOverTime(
User $user,
FilterDefinitionCollection $filterSet,
?CarbonImmutable $dateTimeRangeStartsAt,
?CarbonImmutable $dateTimeRangeEndsAt,
string $histogramInterval = 'day',
): Collection {
$filterDefinitionQueries = $filterSet->extractElasticSearchQueries();
$currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('plays');
$boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);
$timezone = $user->getTimezone();
$timezoneOffset = $user->getTimezoneOffset();
$hasDateRange = $dateTimeRangeStartsAt !== null && $dateTimeRangeEndsAt !== null;
if ($hasDateRange) {
$resultSetKey = 'from_as_string';
$dateTimeRangeStartsAt = $dateTimeRangeStartsAt->setTimezone($timezone)->toImmutable();
$dateTimeRangeEndsAt = $dateTimeRangeEndsAt->setTimezone($timezone)->toImmutable();
$dateRangeAggregation = (new DateRange('over_time'))
->setField('plays.created_at')
->setFormat('8uuuu-MM-dd')
->setParam('time_zone', $timezoneOffset);
if ($histogramInterval === 'day') {
$increment = static function (CarbonImmutable $dateTime): CarbonImmutable {
return $dateTime->addDay();
};
} elseif ($histogramInterval === 'hour') {
$increment = static function (CarbonImmutable $dateTime): CarbonImmutable {
return $dateTime->addHour();
};
} else {
throw new OutOfBoundsException('Unknown date time interval');
}
$periodStartsAt = $dateTimeRangeStartsAt;
while (true) {
$periodEndsAt = $increment($periodStartsAt);
$dateRangeAggregation->addRange(
$periodStartsAt->format('Y-m-d'),
$periodEndsAt->format('Y-m-d')
);
if ($periodEndsAt >= $dateTimeRangeEndsAt) {
break;
}
$periodStartsAt = clone $periodEndsAt;
}
} else {
$resultSetKey = 'key_as_string';
$dateRangeAggregation = (new DateHistogram('over_time', 'plays.created_at', $histogramInterval))
->setTimezone($timezoneOffset)
->setFormat('8uuuu-MM-dd')
->setMinimumDocumentCount(0);
}
$query = (new Query())
->setSize(0)
->setSource(false)
->addAggregation(
(new Filter('activities', $boolQuery))
->addAggregation(
(new Nested('playback', 'plays'))
->addAggregation(
(new Filter('filtered', $currentPriorityQuery))
->addAggregation($dateRangeAggregation)
)
)
);
$aggregationData = $this->searchService->search($query, $this->model, 'getDashboardCoachingOverTime')->getAggregations();
return Collection::make(array_get($aggregationData, 'activities.playback.filtered.over_time.buckets'))
->keyBy($resultSetKey)
->map(static function (array $bucketData): int {
return $bucketData['doc_count'];
});
}
public function getDashboardCoachingBreakdownListensByUserRole(FilterDefinitionCollection $filterSet): Collection
{
$filterDefinitionQueries = $filterSet->extractElasticSearchQueries();
$currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('plays');
$boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);
$query = (new Query())
->setSize(0)
->setSource(false)
->addAggregation(
(new Filter('activities', $boolQuery))
->addAggregation(
(new AggregationTerms('by_user'))
->setField('user.id_string.keyword')
->setSize(self::AGG_TERMS_MAX_SIZE)
->addAggregation(
(new Nested('played_by', 'plays'))
->addAggregation(
(new Filter('filtered', $currentPriorityQuery))
->addAggregation(
(new AggregationTerms('user'))
->setField('plays.user.id_string.keyword')
->setSize(self::AGG_TERMS_MAX_SIZE)
->addAggregation(
(new AggregationTerms('role'))
->setField('plays.user.roles.name')
->setSize(self::AGG_TERMS_MAX_SIZE)
)
)
)
)
)
);
$aggregationData = $this->searchService
->search($query, $this->model, 'getDashboardCoachingBreakdownListensByUserRole')
->getAggregations();
return Collection::make(array_get($aggregationData, 'activities.by_user.buckets'))
->keyBy('key')
->map(static function (array $roomOwnerBucket): array {
return Collection::make(array_get($roomOwnerBucket, 'played_by.filtered.user.buckets'))
->map(static function (array $playbackBucket): array {
$userId = $playbackBucket['key'];
$count = $playbackBucket['doc_count'];
$userRoles = Collection::make(array_get($playbackBucket, 'role.buckets'))
->keyBy('key')
->keys();
return [
'count' => $count,
'userId' => $userId,
'userRoles' => $userRoles->all(),
];
})
->all();
});
}
public function getDashboardCoachingBreakdownCoachingFocusFilledByUserRole(
FilterDefinitionCollection $filterSet
): Collection {
$filterDefinitionQueries = $filterSet->extractElasticSearchQueries();
$currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('comments');
$boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);
$query = (new Query($boolQuery))
->setSize(0)
->setSource(false)
->addAggregation(
(new AggregationTerms('by_user'))
->setField('user.id_string.keyword')
->setSize(self::AGG_TERMS_MAX_SIZE)
->addAggregation(
(new Nested('commented_by', 'comments'))
->addAggregation(
(new Filter('filtered', $currentPriorityQuery))
->addAggregation(
(new AggregationTerms('user'))
->setField('comments.user.id_string.keyword')
->setSize(self::AGG_TERMS_MAX_SIZE)
->addAggregation(
(new AggregationTerms('role'))
->setField('comments.user.roles.name')
->setSize(self::AGG_TERMS_MAX_SIZE)
)
)
)
)
);
$aggregationData = $this->searchService
->search($query, $this->model, 'getDashboardCoachingBreakdownCoachingFocusByUserRole')
->getAggregations();
return Collection::make(array_get($aggregationData, 'by_user.buckets'))
->keyBy('key')
->map(static function (array $roomOwnerBucket): array {
return Collection::make(array_get($roomOwnerBucket, 'commented_by.filtered.user.buckets'))
->map(static function (array $commentBucket): array {
$userId = $commentBucket['key'];
$count = $commentBucket['doc_count'];
$userRoles = Collection::make(array_get($commentBucket, 'role.buckets'))
->keyBy('key')
->keys();
return [
'count' => $count,
'userId' => $userId,
'userRoles' => $userRoles->all(),
];
})
->all();
});
}
private function getCompositeAggregationBy(
BoolQuery $boolQuery,
AbstractAggregation $sourceAggregation,
?array $customAggregations = null,
?AbstractAggregation $aggregationParent = null,
?callable $compositeAggregationExtractor = null,
?AbstractAggregation $immediateAggregationParent = null
): LazyCollection {
if ($compositeAggregationExtractor === null) {
$compositeAggregationExtractor = static fn (ResultSet $aggregationData): array => $aggregationData
->getAggregation('composite')['buckets'];
}
return LazyCollection::make(
function () use (
$sourceAggregation,
$customAggregations,
$aggregationParent,
$immediateAggregationParent,
$boolQuery,
$compositeAggregationExtractor,
): Generator {
$compositeAggregation = (new Composite('composite'))
->setSize(self::AGG_COMPOSITE_MAX_SIZE)
->addSource($sourceAggregation);
if (is_array($customAggregations)) {
foreach ($customAggregations as $customAggregation) {
$compositeAggregation->addAggregation($customAggregation);
}
}
$aggregation = $compositeAggregation;
if ($aggregationParent instanceof AbstractAggregation) {
$aggregation = $aggregationParent;
if ($immediateAggregationParent instanceof AbstractAggregation) {
$immediateAggregationParent->addAggregation($compositeAggregation);
} else {
$aggregationParent->addAggregation($compositeAggregation);
}
}
while (true) {
$query = (new Query($boolQuery))
->setSource(false)
->addAggregation($aggregation);
$aggregationData = $this->searchService
->search($query, $this->model, 'getCompositeAggregationBy');
foreach ($compositeAggregationExtractor($aggregationData) as $bucket) {
yield $bucket;
}
$cursor = array_get($aggregationData, 'composite.after_key', null);
if (! is_array($cursor)) {
break;
}
$compositeAggregation->addAfter($cursor);
}
},
);
}
private function getCompositeAggregationByUser(
BoolQuery $boolQuery,
?array $customAggregations = null,
?callable $callback = null,
): LazyCollection {
if ($callback === null) {
$callback = static fn (array $bucket): int => $bucket['doc_count'];
}
$sourceAggregation = (new AggregationTerms('by_user'))
->setField('user.id_string.keyword');
return $this
->getCompositeAggregationBy(
boolQuery: $boolQuery,
sourceAggregation: $sourceAggregation,
customAggregations: $customAggregations,
)
->mapWithKeys(static fn (array $bucket): array => [
$bucket['key']['by_user'] => $bucket,
])
->map($callback);
}
private function getCompositeAggregationByCoachingFeedbackCoach(
BoolQuery $boolQuery,
?array $customAggregations = null
): LazyCollection {
return $this
->getCompositeAggregationBy(
$boolQuery,
(new AggregationTerms('by_feedback_id'))
->setField('coachingFeedbacks.id_string'),
$customAggregations,
new Nested('coachingFeedbacks', 'coachingFeedbacks'),
static fn (ResultSet $aggregationData): array => $aggregationData
->getAggregation('coachingFeedbacks')['composite']['buckets']
);
}
/**
* @param Collection|string[] $statsOfInterest
*
* @return Collection|AbstractAggregation[]
*/
private function getEngagementStatsAggregation(Collection $statsOfInterest): Collection
{
$getAggregation = static function (string $propertyName): Filter {
$totalAmountProperties = [
'talk_time_ratio',
'longest_user_monologue',
'longest_customer_monologue',
'talking_speed',
'user_questions',
];
$propertyPath = 'stats.' . $propertyName;
$filterQuery = (new BoolQuery())
->addMust(new Exists($propertyPath));
if (in_array($propertyName, $totalAmountProperties, true)) {
$filterQuery->addMust(
new Range(
$propertyPath,
[
'gt' => 0,
]
)
);
}
return (new Filter($propertyName, $filterQuery))
->addAggregation(
(new Sum('data'))->setField($propertyPath)
);
};
return Collection::make()
->push(
$statsOfInterest
->reduce(
static function (Nested $carry, string $propertyName) use ($getAggregation): Nested {
return $carry->addAggregation($getAggregation($propertyName));
},
new Nested('stats', 'stats')
)
);
}
public function getDashboardEngagementStats(FilterDefinitionCollection $filterSet): Collection
{
$statsOfInterest = Collection::make([
'talkTimeRatio' => 'talk_time_ratio',
'longestMonologue' => 'longest_user_monologue',
'longestCustomerStory' => 'longest_customer_monologue',
'talkingSpeed' => 'talking_speed',
'patience' => 'patience_time',
'questionRate' => 'user_questions',
]);
$boolQuery = $filterSet->getBoolQuery(
$filterSet->extractElasticSearchQueries()
);
$aggregationData = $this
->getCompositeAggregationByUser(
$boolQuery,
$this->getEngagementStatsAggregation($statsOfInterest)->toArray(),
static function (array $bucket): array {
$statsBucket = $bucket['stats'];
unset($statsBucket['doc_count']);
return Collection::make($statsBucket)
->map(static function (array $stat): array {
return [
'count' => $stat['doc_count'],
'value' => $stat['data']['value'],
];
})
->toArray();
}
)
->collect();
return $statsOfInterest
->map(static function (string $propertyName) use ($aggregationData): ?float {
[$count, $value] = $aggregationData->reduce(
static function (array $accumulator, array $bucket) use ($propertyName): array {
$accumulator[0] += (int) $bucket[$propertyName]['count'];
$accumulator[1] += (float) $bucket[$propertyName]['value'];
return $accumulator;
},
[0, 0]
);
if ($count === 0) {
return null;
}
return (float) ($value / $count);
});
}
public function getCoachingFeedbacksFilledPerUserAggregation(FilterDefinitionCollection $filterSet): Collection
{
$filterDefinitionQueries = $filterSet->extractElasticSearchQueries([
FilterDefinitionQuery::instance()
->setQuery((new Term())->setTerm('coachingFeedbacks.visibility', CoachingFeedback::VISIBLE_TO_ALL))
->setPath('coachingFeedbacks', 'coachingFeedbacks'),
FilterDefinitionQuery::instance()
->setQuery((new Term())->setTerm('coachingFeedbacks.coach.status', User::STATUS_ACTIVE))
->setPath('coachingFeedbacks', 'coachingFeedbacks'),
]);
$currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('coachingFeedbacks');
$boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);
$data = $this
->getCompositeAggregationByUser(
$boolQuery,
[
(new Nested('feedbacks', 'coachingFeedbacks'))
->addAggregation(
(new Filter('filtered', $currentPriorityQuery))
->addAggregation(
(new AggregationTerms('by_user'))
->setField('coachingFeedbacks.coach.id_string')
->setSize(self::AGG_TERMS_MAX_SIZE)
)
),
],
static function (array $bucket): array {
return array_get($bucket, 'feedbacks.filtered.by_user.buckets', []);
}
)
->reduce(
static function (array $carry, array $buckets): array {
foreach ($buckets as $bucket) {
$userId = $bucket['key'];
if (! array_key_exists($userId, $carry)) {
$carry[$userId] = 0;
}
$carry[$userId] += $bucket['doc_count'];
}
return $carry;
},
[]
);
return Collection::make($data);
}
public function getCoachingFeedbacksReceivedPerUserAggregation(FilterDefinitionCollection $filterSet): Collection
{
$filterDefinitionQueries = $filterSet->extractElasticSearchQueries([
FilterDefinitionQuery::instance()
->setQuery((new Term())->setTerm('coachingFeedbacks.visibility', CoachingFeedback::VISIBLE_TO_ALL))
->setPath('coachingFeedbacks', 'coachingFeedbacks'),
FilterDefinitionQuery::instance()
->setQuery((new Term())->setTerm('coachingFeedbacks.coach.status', User::STATUS_ACTIVE))
->setPath('coachingFeedbacks', 'coachingFeedbacks'),
]);
$currentPriorityQuery = $filterDefinitionQueries->getNestedQuery('coachingFeedbacks');
$boolQuery = $filterSet->getBoolQuery($filterDefinitionQueries);
$data = $this
->getCompositeAggregationByUser(
$boolQuery,
[
(new Nested('feedbacks', 'coachingFeedbacks'))
->addAggregation(
(new Filter('filtered', $currentPriorityQuery))
->addAggregation(
(new AggregationTerms('by_user'))
->setField('coachingFeedbacks.coachee.id_string')
->setSize(self::AGG_TERMS_MAX_SIZE)
->addAggregation(
new ValueCount('volume', 'coachingFeedbacks.id_string'),
...
|
35167
|
NULL
|
NULL
|
NULL
|
|
35170
|
1315
|
19
|
2026-05-13T12:16:30.957789+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-13/1778 /Users/lukas/.screenpipe/data/data/2026-05-13/1778674590957_m1.jpg...
|
PhpStorm
|
faVsco.js – TeamInsightsRepository.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20891-improve-sms-text Project: faVsco.js, menu
JY-20891-improve-sms-text-relays, menu...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20891-improve-sms-text-relays, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20891-improve-sms-text-relays","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-1362947798257697316
|
-2575675244731720149
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
JY-20891-improve-sms-text Project: faVsco.js, menu
JY-20891-improve-sms-text-relays, menu
Firefox FileEditViewHistoryBookmarksToolsWindowHelp• 0APPapp/Console/Commands/Activities/SetupIntegration/TwilioSetS3RecordingCredentialsCommand.bapp/Console/Kernel.phpapp/Jobs/ImportRemoteTrackJob.phpapp/Services/Activity/Twilio/S3RecordingCredentialsService.phptests/Feature/Jobs/ImportRemoteTrackJobTest.phptests/Unit/Component/Encoding/Job/AnalyzeTrackChannelsJobTest.phptests/Unit/Component/FFMpeg/Services/GetSpeechIntervalsTest.phptests/Unit/Services/Activity/Twilio/S3RecordingCredentialsServiceTest.php10 files changed, 823 insertions(+), 23 deletions(-)create mode 100644 app/Console/Commands/Activities/SetupIntegration/TwilioSetS3Recordingcreate mode 100644 app/Services/Activity/Twilio/S3RecordingCredentialsService.phpcreate mode 100644 tests/Unit/Services/Activity/Twilio/S3RecordingCredentialsServiceTestlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20891-improve-:Switched to a new branch 'JY-20891-improve-sms-text-relays'lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20891-improve-sms-text-relays)docker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.plPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminskicontributors.Running analysis on 7 cores with 10 files per process.Parallel runner is an experimental feature and may be unstable, use it at your own risk.Loaded configdefault from-php-cs-fixer.dist.php"5665/5665100%1) app/Jobs/Mailbox/EmailTextRelay.php (no_unused_imports)begin diff/home/jiminny/app/Jobs/Mailbox/EmailTextRelay.php+++/home/jiminny/app/Jobs/Mailbox/EmailTextRelay.php-16,7 +16,6 @@use Jiminny\Mail\Activities|SmsRelayFailed;use Jiminny\Models\Activity;use Jiminny Models \TextRelay;Jiminny\Models\User;useJiminny\Repositories\UserRepository;use Jiminny\Rules\SmsMessage;use Jiminny\Services\Mail\TextRelayService;end diffFixed 1 of 5665 files in 52.535 seconds, 67.00 MBmemory usedWhat's next:Try Docker Debug for seamless, persistent debugging tools in any containeror image →Learn more at https://docs.docker.com/go/debug-cli/lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20891-improve-sms-text-relays)HomeDMsActivityFilesLater..•More> 0lallJiminny ...# contusion-clinic# curiosity_lab# engineering# general# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...• Direct messages. Nikolay Ivanovd. James GrahamStoyan Tanev®. Galya Dimitrova ORe Steliyan Georgiev&. Petko Kashinskie. Aneliya Angelova8. Stefka StoyanovaB. Vasil VasilevLukas Kovalik y...i: AppsToastJira CloudConala CalaSprint Review - in 44 m100% <78•Wed 13 May 15:16:30Describe what you are looking for# releases8 226 0MessagesC FilesBookmarks+3 new messagesNewCircleCI AP.-Deployment Successful!Project: appWhen:05/13/202611:57:10Tag:View JobGitHub APP3:10 PM6 new commits pushed to master by Vasil-Jiminny851bf1a7 - Remove ignored records that causelocal phpstan errors. No functional changes.d08f0260 - Merge branch 'master' intoremove-phpstan-errorsb2d676c3 - Merge branch 'master' intoremove-phpstan-errors1ff6f70f - Merge branch 'master' intoremove-phpstan-errors62a7fe62 - Merge branch 'master' intoremove-phpstan-errorsShow morejiminny/app Added by GitHubVasil Vasilev 3:11 PMStopped. The PR is internal dev optimisation, noneed to reach prod.Message #releases+..•...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
35171
|
1316
|
20
|
2026-05-13T12:16:30.957396+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-13/1778 /Users/lukas/.screenpipe/data/data/2026-05-13/1778674590957_m2.jpg...
|
PhpStorm
|
faVsco.js – TeamInsightsRepository.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20891-improve-sms-text Project: faVsco.js, menu
JY-20891-improve-sms-text-relays, menu...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20891-improve-sms-text-relays, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.08843085,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20891-improve-sms-text-relays","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-1362947798257697316
|
-2575675244731720149
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
JY-20891-improve-sms-text Project: faVsco.js, menu
JY-20891-improve-sms-text-relays, menu
PhpStormFV faVsco.js~INavigarecode(C) ActivitvCommentRenosita©ActivityLogRepository-phy© ActivityMessageRepositol©ActivityMomentRepositor© ActivityProviderRepositor©ActivityRepository.php© ActivitySearchFilterReposActivityShareRepository.F© ActivityUploadSettingRepC AIPromptkepository.onc© AskAnythingRepository.plC) DeviceRerositorv.oho© ElasticActivityRepository.(c) EmailMessadeRenositorv.(C) GenericA PromotRenosito© GroupRepository.php© InboxEmailBatchRepositoC InboxRepositorv.oho© InvitationRepository.php© JobRepository.php© LanguageRepository.php© MomentRepository.phpNotificationRepository.ph© ParticipantSpeechReposit© ParticipantStatsRepositor© PlaybookCategoryReposit© PlaybookRepository.phpPlaylistActivityRepository.© PlaylistRepository.phpPlaylistShareRepository.p© QuestionRepository.php© RoleChangeEventRepositi© RoleRepository.phpc) SearchRenositorv.onv© SnapshotRepository.phpC) SocialAccountRevositorv.© StageRepository.php(C) SubscriotionSetRevositon©TaskRepository.php(C) TeamA ContextRenositon© TeamDomainsRepository.(c) TeaminciahtcRenositorvr©TeamRepository.php(C) ThemeRenositorv nhn655675677679© MailboxController.php© SsoController.php©smsmessage.ong© SmsLength.phpQ- CONTEXT_TEAM.INSIGHTS ACTIVITY X 5 Cc w .*TU8:class leamenszencskepos.corypublic function getDashboardActivity0verTime(-›setSize( size: self::AGG_TERMS_MAX_SIZE)A16Y1 A->addAggregation(new rilterIclone sbooluuery)->addFilter((new lermo->setlerm key: "type.keyword",value: Activity::TYPE_SMS_OUTBOUND)(new DateHistogram( name: 'over time' field: 'created at'. ShistogramInterval))at: 'Suuuu-MM-dd")->settimezone (Stimezonelffset).->addAdaredation(new AggregationTerms( name:'by_channel'))->setFieldd field'type.keyword')-›setSize( size: self::AGG_TERMS_MAX_SIZE)SaggregationData = $this-›searchService-›search($query, $this-›model, queryName: 'getDashboardActivitySactivityData = Collection: :make([array_get($aggregationData,array get saggregaclonvaca.key: 'voice_activities.over_time.buckets'),key:|'text_activities.over_time.buckets'),->collapse()->reoucestatic function (array $carry, array $bucketData): array f...7,return Collection::make(SactivityData):oublic function aetDashboardCoachina0vertimedE custom.logA console [STAGING]E laravel.logA SF jiminny@localhost]A HS_Jocal [jiminny@localhost]A console (PROD] X A console (EU]644645646648649650652-653654655656657665-666667=668669—0/0Tx: AutovORDER BY t.name, calendar domain:So jiminny040 A1 A40 V 65 ^select * trom users u jo1n calendars c 1<->I.n. on c.user 1d = U.1dwhere u.team_id = 882;select * from activities where id = 14049485: # team 563 crm 53select * from activities where id = 73272382; # team 563 crm 537select * from activities where id = 64400389: # team 563 crm 537select * from activities where id = 58081273; # team 563 crm 537select * from activities where id = 54520297; # team 563 crm 537select * from participants where activity_id = 58081273;select * from activities where crm_configuration_id = 537 and provider = 'aircall'and account_id = 19003658 order by updated_at desc;select * from contacts where crm_configuration_id = 537 and id = 35957759;select * from accounts where crm_configuration_id = 537 and id = 19003658;select * from automated_report_results where id = 1976;select * from automated_reports where id = 583;select * from activity_searches where id = 87714;select * from activity_search_filters where activity_search_id = 87714;SELECT * FROM activities WHERE uuid_to_bin('8827f672-202d-4162-9d04-73ff5f0566a9') = uuidor uvid_to_bin(*47842446-af51-4bcb-854f-CC6560290101') = uuid;SELECT * FROM crm_configurations WHERE provider = 'hubspot';select * from rate_limits;select * from alated_report_results where nedia,tyRe = 'pdf' and status = 2and id IN (18,1872):select * from automated_reports where id = 54;SELECT * FROM users WHERE id IN (24623,29443,29613);SELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;select * from text pelavs where created at 12026-05-014# and id IN (32415, 32416);and id = 72412.select * from users where team_id = 2 and email like '%scott%' and id = 29510;672vSELECT * FROM activities WHERE uvid_to_bin('67cebfc2-ed56-44a2-8C68-7a0286ed8618') = uuid; # 79763436sonnt Keview • In 44m100% L28• Wed 13 May 15:16:30U AskJiminnyReportActivityServiceTest~CascadeTrial Owner Role SeleRetactoring User EmaDashboard Activity De+0 ..Give me the whole flowComplete Dashboard Activity Flow1.AP Request Entrv PointFile: DashboardController.php:33-68• phppubuic tunction Terchtkequest srequest): JsonkesponseSuser = $this->getUserFromRequest (Srequest);Scriteria = Criteria::createFromRequest(Srequest-sallo. Suser->aetTimezone@)•Srequest→>val1date(Stilterser→>getva Lidaclonku les()→>auuo;return sthis->response->withArray([= StilterSet->renderon'activity-over-time' = Sthis->getActivityOverTime($user, $filterSet),Context set: CONTEXT_TEAM_ INSIGHTS_ACTIVITY = 'tean-insights-activity'2. Filter Set CreationFile: ActivitySearch.php: 132-244private function getteaminsightsPageFilters(bool SisExport = false): Filterdefinitioncollectionreturn FilterDefinitionCollection::make(Collection: :make(lFilterDefinition Teaminsiahts DateRangeFilter::class.FilterDefinition TeamInsiahts|Exists:.class.Ask anvthina (84L)« Code SWF-1.6f 4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
49392
|
1762
|
19
|
2026-05-18T06:39:39.271722+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086379271_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
8043719072324535154
|
-8628527368849355612
|
visual_change
|
hybrid
|
NULL
|
Project: faVsco.js, menu
Term2ShellEditSessionProf Project: faVsco.js, menu
Term2ShellEditSessionProfilesWindowHelpDOCKER (docker-compose)O 82DOCKER81DEV (-zsh)11DOCKER (docker-compose)NFO""component" ::"e802ad473a4f""o.e.p.PluginsService","cluster. name" :"docker-cluster""node. name""message" :module [x-pack-ql]"elasticsearch"timestamp" :"2026-05-18T06:39:35,926Z"NFO""component": "o.e.p.PluginsService""cluster.name":"docker-cluster""level": "I"node .name""e802ad473a4f""message":elasticsearch"loaded module [x-pack-rollup]" }"timestamp":NFO""2026-05-18T06:39:35,926Z""component": "o.e.p.PluginsService""node. name""e802ad473a4f""message" :"loaded module [x-pack-security]" }elasticsearch"timestamp" :"2026-05-18T06:39:35,926Z"NFO","component" :"o.e.p.PluginsService""cluster.name":"docker-cluster","node. name": "e802ad473a4f""message":"Loaded module [x-pack-sql]" }elasticsearchNFO",1 {"type":"server""timestamp" :"2026-05-18T06:39:35,926Z""component":"o.e.p.PluginsService","cluster.name":"docker-cluster""level":"I"node. name": "e802ad473a4f""message" :"loadedmodule [x-pack-stack]"}elasticsearchI {"type":"server""timestamp""2026-05-18T06:39:35,926Z"NFO""component":"o.e.p.PluginsService"cluster.name":"docker-cluster""level":"I"node. name""e802ad473a4f""message":"loadedmodule[x-pack-voting-only-node]" }elasticsearch | {"type": "server""timestamp": "2026-05-18T06:39:35,926Z"NFO","component": "o.e.p.PluginsService","cluster.name":"docker-cluster""level":"I"node. name": "e802ad473a4f"', "message": "loaded module [x-pack-watcher]" }elasticsearch1 {"type": "server""timestamp": "2026-05-18T06:39:35,926Z"NFO", "component": "o.e.p.PluginsService", "cluster.name":"docker-cluster""level":"I"node. name": "e802ad473a4f"', "message": "no plugins loaded" 3elasticsearchI {"type": "deprecation", "timestamp": "2026-05-18T06:39:35,995Z", "level": "DEPRECATION","component": "o.e.d.c.s.Settings", "cluster.name";"docker-cluster"node.name":"e802ad473a4f", "message":"[node.data] setting was deprecated in Elasticsearch and will be removed in a future release! See the breakingdocumentation forthe next major version." }elasticsearch1 {"type": "server"NFO""component": "o.e.e.NodeEnvironment",ments, uster-n-m5-18 Tdocker-clusten","Level name"node. name":"e80Zad473a4f", "message": "using [1] data paths, mounts [[/usr/share/elasticsearch/data (/dev/vda1)]], net usable_space [14gb], net total_space [58.3gb], types [ext4]" }elasticsearch1 {"type": "server""timestamp": "2026-05-18T06:39:36,015Z","level": "INFO", "component": "o.e.e.NodeEnvironment""cluster.name": "docker-cluster""node.name": "e802ad473a4f", "message": "heap size [700mb], compressed ordinary object pointers [true]" }elasticsearchNFO",1 {"type": "server"', "timestamp": "2026-05-18T06:39:36,106Z""level": "I"component": "o.e.n.Node""cluster.name": "docker-cluster""node.name": "e802ad473a4f""message": "node name [e802ad473a4f], node ID [e2ZKzgw4Q4aCf2w51jWr1A], clustername [docker-cluster], roles [transform, master, remote_cluster_client, data, ml, data_content, data_hot, data_warm, data_cold, ingest]" }100% <8• Mon 18 May 9:39:39screenpipe"181O $4APP (-zsh)*3T2PROD (-zsh)Last login: Sat May 16 18:04:33 on ttys001Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.toml file in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ |X L3 EU (-zsh)Last login: Sat May 16 18:04:33 on ttys001Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parents@Lukas-Kovaliks-MacBook-Pro-Jiminny~$ IX T4STAGE (-zsh)Last login: Sat May 16 18:04:33 on ttys001Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.toml file in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-JiminnyT5QA (-zsh)Last login: Mon May 18 09:17:28on ttys003Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentsX T6FE (-zsh)Last login: Mon May 18 09:17:28on ttys004Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry couldnotfind a pyproject.tomlfile in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ IEXT (-zsh)Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry couldnot find a pyproject.tomlfile in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ [|PRODSTAGEFRONTENDEXTENSIONV View in Docker Desktop• View ConfigEnable Watch...
|
49390
|
NULL
|
NULL
|
NULL
|
|
49393
|
1763
|
21
|
2026-05-18T06:39:40.960366+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086380960_m2.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhostormVIewINavicarecodeLaravelKeractorFV faVsco. PhostormVIewINavicarecodeLaravelKeractorFV faVsco.js?° pipedrive-sdk-pocProiect© SyncProfileMetadata.phpCConvertLeadActivities.ong© SyncPlanhat.php©syncleammetadata.ong1estPlpeariveomricialsakcommand.orUpoateopponunityspecrications.onocreatePlaybookcreatedevent.ong>@ Dealinsights>@Dev> 0 Dialersc) IntearationApp/Service.php(C) LeadConverted.php© CreateSelfCoachedEvent.phpD DTOS©) CreateCommentedEvent.phpC) CreateSmsSentEvent.ohpC) PlanhatActivityListener.phoC Elasticsearch(C)AskAnvthinaPromotService.ohd› EnqagementStatsш GeckoExport(C)AutomatedReportsCommand.ohvphp api y2.ohoC) RequestGenerateReport.Job.oho/>D Livestream(C) AutomatedReportkesulconp© AutomatedReport.phpA console [STAGING]Mallboxes• MidratePlavbackithemes• M Plavbooksdectarelstrict_types-ty,Playlists> M Postmark> M PronhetAuse…..v M Renorto© AutomatedReportsCommand.phpC) AutomatedRenortsRetentionPolicvCor14 Dclass syncubnects extends command© AutomatedReportsSendCommand.pht© CreateMockAskJiminnyReportResultC© DeleteReportCommand.php© GenerateMarketingReport.php* The name and sianature of the console commandi© Team.php© Usage.php>C Slack* ovar strind›_ Teams>C Tracks7301orotected ssionature = crm:sunc-ohnects «team?, <--svnct'*w Transcription>C Twilio* The console command descrintion.w Users> M Vocabularv>Zoom*Avan strinaC) Command.oho© CreateDatabaseUsers.phpprotected $description = 'Sync remote CRM objects.';C)DatabaseTiableCount.oho© DeleteOIdAiCrmNotesCommand.ohv* Eyecuto the concole commandlCDeleteS?Leftoverscommand.ohn@ DevPostmanCommand.phpC) DiarizeViaAiParticinantldentificationCompublic function handle: voidf...;@ EncrvntTokensCommand.nhn(C EnaadementStatsRedenerateCommand.r( FeatureFlaasHelner nhr69 6t)protected function getStaggerDelavSeconds@: floatf...© FixCrossTenantlssues.php© FlushRolesPermissionsCache.php74 6tprotected function getMaxDelavSeconds@: int{...}© GeneratelnternalWebhookToken.php9 GrounSetDefaultLanguageCommand.php81 6t>protected function getLoqPrefix@: stringf...;l© HelperTruncateCoachingTables.phpHelner Code will hoin INF to underctand vour Laravel ann code II Generate II Don't Show Anvmore (4 miAskJiminnyReportActivityServiceTest v=custom.log=laravel.log4 SF jiminny@localhost]A HS_local (jiminny@localhost]A console [PROD]© TestPipedriveOfficialSdkCoand.php x © Kernel.phpA console [EU]119class TestPipedrive0fficialSdkCommand extends Commandprivate function testBasicCrudOperations(SocialAccount $socialAccount): void'Test 2: Basic CRUD Operations');Analyzing...^vtryfGet the raw token from database$rawDbToken = $socialAccount->getProviderUserToken();Sthis->infod strino: "DB token Genath." strlenScawihToken? "")$this->info( string: "DB token preview:.substr(SrawDbToken// Check token expirySexnines = ScocialAccount->exninps.$this->info( string: "Token expires at: " . (Sexpires ? Carbon:: createFromTimes$this->info( string: "Token is expired: " . (Sexpires && $expires ‹ timeO ? 'Y// Test with the DB token directlySthis->info( string: "n--- Testing with DB token ---"):Sconfig = new ConfiqurationO:Sconfig->setAccessToken(SrawDbToken):// Test 2a: Get DealsSthis->testGetDeals(Sconfig):II Test 2b: Get PersonsSthis->testGetPersons(Sconfig)I/ Test 2c: Get ActivitiesSthis->testGetActivities(Sconfig):} catch (Exception $e) {sthis->recordResultd testNan•'Basic CRUD Onerations'success: fallse. Se->aet$this->newLine():private function testCurrentSystemApproach(SocialAccount $socialAccount): voidf...}-private function testGetDeals(Configuration $config): voidf….;private function testGetPersons(Configuration $config): void{...}private function testGetActivities(Configuration Sconfig): void{...}private function testFieldMetadata(SocialAccount SsocialAccount): void{...}private function testGetDealFields(Confiquration Sconfiq): void{...}lprivate function testGetPersonFields(Configuration Sconfiq): voidf...}private function testGetActivitvFields(Confiquration Sconfig): void{...}100% Lz• Mon 18 May 9:39:40+0 •wDebugging Pipedrive sdocker exec docker lamp_ 1 pho artisan 11minnv:test-o1pedrive-ott1c1al-sdk 19Starting Pipedrive Official SDK POC for Team ID: 19Found Pipedrive account for team: Pipedrive, IncTeSt PASS - OAuth Inatialization: Client initialized successfully in 32.85msDe t 2e Bsen cRu Soperations6/6 tasks done ›POC Results: Official SDK Works SuccessfullySummarvThe official Pipedrive SDK IS compatible with your tokens. The issue was simply that the token had expired. After refreshing, all core operations work perfectlyTest ResultsSuccessful (6/10 tests):• ®Auth Initialization: 32.85ms•Get Deals Summary: 1197.89ms• Ger Deal Fields: 711.68ms (49 tields)•Get Person Fields: 697.38ms (27 fields)•Get Activity Fields: 889.77ms (35 fields)• Invalid Token Error: 709.55ms (correct error handling)X Expected Failures (4/10):•Get Persons/Activities: Different API structure in official SDK•v2 APl Supnort: v1 SDK doesn't have v2 cursor padination.Nan-ayictent Dacourca. Mothad name differancasKey Findinas1. Token format is compatible: The official SDK accepts the same token format as the community SDK7. Core onerations work: Deals. field metadata. and error handilina all function correctivi3. Architecture differences: Method names and API structure differ between SDKs4. Token retresh works. Your token refresh mechanism uodated the token successfullvConclucionThe official SDK is viable for migration. The main work will be:•Mapping method names between SDKs• Adapting to different API response structuresImplementing v2 API support if needed (may require direct HTTP calls)SWF.16Cal ae* Reiect allAccent allWN Windsurf Teamo127-11Po 4 spaces...
|
NULL
|
6145865064454473855
|
NULL
|
visual_change
|
ocr
|
NULL
|
PhostormVIewINavicarecodeLaravelKeractorFV faVsco. PhostormVIewINavicarecodeLaravelKeractorFV faVsco.js?° pipedrive-sdk-pocProiect© SyncProfileMetadata.phpCConvertLeadActivities.ong© SyncPlanhat.php©syncleammetadata.ong1estPlpeariveomricialsakcommand.orUpoateopponunityspecrications.onocreatePlaybookcreatedevent.ong>@ Dealinsights>@Dev> 0 Dialersc) IntearationApp/Service.php(C) LeadConverted.php© CreateSelfCoachedEvent.phpD DTOS©) CreateCommentedEvent.phpC) CreateSmsSentEvent.ohpC) PlanhatActivityListener.phoC Elasticsearch(C)AskAnvthinaPromotService.ohd› EnqagementStatsш GeckoExport(C)AutomatedReportsCommand.ohvphp api y2.ohoC) RequestGenerateReport.Job.oho/>D Livestream(C) AutomatedReportkesulconp© AutomatedReport.phpA console [STAGING]Mallboxes• MidratePlavbackithemes• M Plavbooksdectarelstrict_types-ty,Playlists> M Postmark> M PronhetAuse…..v M Renorto© AutomatedReportsCommand.phpC) AutomatedRenortsRetentionPolicvCor14 Dclass syncubnects extends command© AutomatedReportsSendCommand.pht© CreateMockAskJiminnyReportResultC© DeleteReportCommand.php© GenerateMarketingReport.php* The name and sianature of the console commandi© Team.php© Usage.php>C Slack* ovar strind›_ Teams>C Tracks7301orotected ssionature = crm:sunc-ohnects «team?, <--svnct'*w Transcription>C Twilio* The console command descrintion.w Users> M Vocabularv>Zoom*Avan strinaC) Command.oho© CreateDatabaseUsers.phpprotected $description = 'Sync remote CRM objects.';C)DatabaseTiableCount.oho© DeleteOIdAiCrmNotesCommand.ohv* Eyecuto the concole commandlCDeleteS?Leftoverscommand.ohn@ DevPostmanCommand.phpC) DiarizeViaAiParticinantldentificationCompublic function handle: voidf...;@ EncrvntTokensCommand.nhn(C EnaadementStatsRedenerateCommand.r( FeatureFlaasHelner nhr69 6t)protected function getStaggerDelavSeconds@: floatf...© FixCrossTenantlssues.php© FlushRolesPermissionsCache.php74 6tprotected function getMaxDelavSeconds@: int{...}© GeneratelnternalWebhookToken.php9 GrounSetDefaultLanguageCommand.php81 6t>protected function getLoqPrefix@: stringf...;l© HelperTruncateCoachingTables.phpHelner Code will hoin INF to underctand vour Laravel ann code II Generate II Don't Show Anvmore (4 miAskJiminnyReportActivityServiceTest v=custom.log=laravel.log4 SF jiminny@localhost]A HS_local (jiminny@localhost]A console [PROD]© TestPipedriveOfficialSdkCoand.php x © Kernel.phpA console [EU]119class TestPipedrive0fficialSdkCommand extends Commandprivate function testBasicCrudOperations(SocialAccount $socialAccount): void'Test 2: Basic CRUD Operations');Analyzing...^vtryfGet the raw token from database$rawDbToken = $socialAccount->getProviderUserToken();Sthis->infod strino: "DB token Genath." strlenScawihToken? "")$this->info( string: "DB token preview:.substr(SrawDbToken// Check token expirySexnines = ScocialAccount->exninps.$this->info( string: "Token expires at: " . (Sexpires ? Carbon:: createFromTimes$this->info( string: "Token is expired: " . (Sexpires && $expires ‹ timeO ? 'Y// Test with the DB token directlySthis->info( string: "n--- Testing with DB token ---"):Sconfig = new ConfiqurationO:Sconfig->setAccessToken(SrawDbToken):// Test 2a: Get DealsSthis->testGetDeals(Sconfig):II Test 2b: Get PersonsSthis->testGetPersons(Sconfig)I/ Test 2c: Get ActivitiesSthis->testGetActivities(Sconfig):} catch (Exception $e) {sthis->recordResultd testNan•'Basic CRUD Onerations'success: fallse. Se->aet$this->newLine():private function testCurrentSystemApproach(SocialAccount $socialAccount): voidf...}-private function testGetDeals(Configuration $config): voidf….;private function testGetPersons(Configuration $config): void{...}private function testGetActivities(Configuration Sconfig): void{...}private function testFieldMetadata(SocialAccount SsocialAccount): void{...}private function testGetDealFields(Confiquration Sconfiq): void{...}lprivate function testGetPersonFields(Configuration Sconfiq): voidf...}private function testGetActivitvFields(Confiquration Sconfig): void{...}100% Lz• Mon 18 May 9:39:40+0 •wDebugging Pipedrive sdocker exec docker lamp_ 1 pho artisan 11minnv:test-o1pedrive-ott1c1al-sdk 19Starting Pipedrive Official SDK POC for Team ID: 19Found Pipedrive account for team: Pipedrive, IncTeSt PASS - OAuth Inatialization: Client initialized successfully in 32.85msDe t 2e Bsen cRu Soperations6/6 tasks done ›POC Results: Official SDK Works SuccessfullySummarvThe official Pipedrive SDK IS compatible with your tokens. The issue was simply that the token had expired. After refreshing, all core operations work perfectlyTest ResultsSuccessful (6/10 tests):• ®Auth Initialization: 32.85ms•Get Deals Summary: 1197.89ms• Ger Deal Fields: 711.68ms (49 tields)•Get Person Fields: 697.38ms (27 fields)•Get Activity Fields: 889.77ms (35 fields)• Invalid Token Error: 709.55ms (correct error handling)X Expected Failures (4/10):•Get Persons/Activities: Different API structure in official SDK•v2 APl Supnort: v1 SDK doesn't have v2 cursor padination.Nan-ayictent Dacourca. Mothad name differancasKey Findinas1. Token format is compatible: The official SDK accepts the same token format as the community SDK7. Core onerations work: Deals. field metadata. and error handilina all function correctivi3. Architecture differences: Method names and API structure differ between SDKs4. Token retresh works. Your token refresh mechanism uodated the token successfullvConclucionThe official SDK is viable for migration. The main work will be:•Mapping method names between SDKs• Adapting to different API response structuresImplementing v2 API support if needed (may require direct HTTP calls)SWF.16Cal ae* Reiect allAccent allWN Windsurf Teamo127-11Po 4 spaces...
|
49391
|
NULL
|
NULL
|
NULL
|
|
49394
|
1763
|
22
|
2026-05-18T06:39:45.879363+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086385879_m2.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
1
51
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Jiminny\Component\Encryption\EncryptedTokenManager;
use Jiminny\Models\SocialAccount;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Pipedrive\versions\v1\Api\ActivitiesApi;
use Pipedrive\versions\v1\Api\ActivityFieldsApi;
use Pipedrive\versions\v1\Api\DealFieldsApi;
use Pipedrive\versions\v1\Api\DealsApi;
use Pipedrive\versions\v1\Api\PersonFieldsApi;
use Pipedrive\versions\v1\Api\PersonsApi;
use Pipedrive\versions\v1\Configuration;
class TestPipedriveOfficialSdkCommand extends Command
{
protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';
protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';
private array $results = [];
private EncryptedTokenManager $tokenManager;
public function __construct(EncryptedTokenManager $tokenManager)
{
parent::__construct();
$this->tokenManager = $tokenManager;
}
public function handle(): int
{
$teamId = (int) $this->argument('teamId');
$this->info("Starting Pipedrive Official SDK POC for Team ID: {$teamId}");
$this->newLine();
try {
$this->runTests($teamId);
} catch (Exception $e) {
$this->error("POC failed with error: {$e->getMessage()}");
$this->error($e->getTraceAsString());
return 1;
}
$this->displayResults();
return 0;
}
private function runTests(int $teamId): void
{
$team = Team::find($teamId);
if (! $team) {
throw new Exception("Team with ID {$teamId} not found");
}
$socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)
->whereHas('sociable', function ($query) use ($team) {
$query->where('team_id', $team->id);
})
->orderByDesc('expires') // Get the most recently expiring token
->first();
if (! $socialAccount) {
throw new Exception("No Pipedrive social account found for team {$teamId}");
}
$this->info("Found Pipedrive account for team: {$team->name}");
$this->newLine();
// Test 1: OAuth Initialization
$this->testOAuthInitialization($socialAccount);
// Test 2: Basic CRUD Operations
$this->testBasicCrudOperations($socialAccount);
// Test 3: Field Metadata
$this->testFieldMetadata($socialAccount);
// Test 4: v2 API Support
$this->testV2ApiSupport($socialAccount);
// Test 5: Error Handling
$this->testErrorHandling($socialAccount);
}
private function testOAuthInitialization(SocialAccount $socialAccount): void
{
$this->info('Test 1: OAuth Initialization');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', true, "Client initialized successfully in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);
}
$this->newLine();
}
private function testBasicCrudOperations(SocialAccount $socialAccount): void
{
$this->info('Test 2: Basic CRUD Operations');
try {
// Get the raw token from database
$rawDbToken = $socialAccount->getProviderUserToken();
$this->info("DB token (length: " . strlen($rawDbToken) . ")");
$this->info("DB token preview: " . substr($rawDbToken, 0, 20) . "...");
// Check token expiry
$expires = $socialAccount->expires;
$this->info("Token expires at: " . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));
$this->info("Token is expired: " . ($expires && $expires < time() ? 'YES' : 'NO'));
// Test with the DB token directly
$this->info("\n--- Testing with DB token ---");
$config = new Configuration();
$config->setAccessToken($rawDbToken);
// Test 2a: Get Deals
$this->testGetDeals($config);
// Test 2b: Get Persons
$this->testGetPersons($config);
// Test 2c: Get Activities
$this->testGetActivities($config);
} catch (Exception $e) {
$this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testCurrentSystemApproach(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
// Simulate how current system makes requests
$token = $socialAccount->getProviderUserToken();
$url = 'https://api.pipedrive.com/api/v2/deals';
$headers = [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
];
$client = new \GuzzleHttp\Client();
$response = $client->get($url, [
'headers' => $headers,
'timeout' => 30,
]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$statusCode = $response->getStatusCode();
$this->recordResult('Current System v2 API', $statusCode === 200, "Status: {$statusCode} in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);
}
}
private function testGetDeals(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealsApi(null, $config);
// Official SDK uses getDealsSummary instead of getDeals
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', true, "Retrieved deals summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersons(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getPersons method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivities(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getActivities method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);
}
}
private function testFieldMetadata(SocialAccount $socialAccount): void
{
$this->info('Test 3: Field Metadata');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
// Test 3a: Get Deal Fields
$this->testGetDealFields($config);
// Test 3b: Get Person Fields
$this->testGetPersonFields($config);
// Test 3c: Get Activity Fields
$this->testGetActivityFields($config);
} catch (Exception $e) {
$this->recordResult('Field Metadata', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testGetDealFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealFieldsApi(null, $config);
$result = $apiInstance->getDealFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Deal Fields', true, "Retrieved {$count} deal fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersonFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new PersonFieldsApi(null, $config);
$result = $apiInstance->getPersonFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Person Fields', true, "Retrieved {$count} person fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivityFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new ActivityFieldsApi(null, $config);
$result = $apiInstance->getActivityFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Activity Fields', true, "Retrieved {$count} activity fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);
}
}
private function testV2ApiSupport(SocialAccount $socialAccount): void
{
$this->info('Test 4: v2 API Support (Cursor-based Pagination)');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$startTime = microtime(true);
// Test v2 deals endpoint with cursor pagination
// Note: The official SDK may or may not have v2 API support
// This test will determine if v2 is available
try {
$apiInstance = new DealsApi(null, $config);
$updatedSince = Carbon::now()->subDays(30)->toRfc3339String();
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
// Check if response has cursor information (v2 indicator)
// The response object doesn't have getAdditionalData method
// Just test if the call works - v2 API support is limited in official SDK v1
$this->recordResult('v2 API Support', false, "v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);
}
} catch (Exception $e) {
$this->recordResult('v2 API Support', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testErrorHandling(SocialAccount $socialAccount): void
{
$this->info('Test 5: Error Handling');
try {
// Test 5a: Invalid Token
$this->testInvalidToken();
// Test 5b: Non-existent Resource
$this->testNonExistentResource($socialAccount);
} catch (Exception $e) {
$this->recordResult('Error Handling', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testInvalidToken(): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken('invalid_token');
$client = new DealsApi(null, $config);
$client->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function testNonExistentResource(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
// Try to get a deal with a very high ID that likely doesn't exist
// Note: Official SDK doesn't have a simple getDeal method, so skip this
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function recordResult(string $testName, bool $success, string $message, float $elapsed): void
{
$this->results[] = [
'test' => $testName,
'success' => $success,
'message' => $message,
'elapsed' => $elapsed,
];
$status = $success ? '✅ PASS' : '❌ FAIL';
$this->line(" {$status} - {$testName}: {$message}");
}
private function displayResults(): void
{
$this->newLine();
$this->info('=== POC Test Results Summary ===');
$this->newLine();
$total = count($this->results);
$passed = count(array_filter($this->results, fn ($r) => $r['success']));
$failed = $total - $passed;
$this->line("Total Tests: {$total}");
$this->line("Passed: {$passed}");
$this->line("Failed: {$failed}");
$this->newLine();
$totalTime = array_sum(array_column($this->results, 'elapsed'));
$this->line("Total Execution Time: {$totalTime}ms");
$this->newLine();
if ($failed > 0) {
$this->warn('Failed Tests:');
foreach ($this->results as $result) {
if (! $result['success']) {
$this->line(" - {$result['test']}: {$result['message']}");
}
}
$this->newLine();
}
$this->info('=== Detailed Results ===');
foreach ($this->results as $result) {
$status = $result['success'] ? '✅' : '❌';
$this->line("{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}");
}
$this->newLine();
$this->info('=== Recommendations ===');
if ($passed === $total) {
$this->info('✅ All tests passed! The official SDK is viable for migration.');
$this->info('Next steps: Proceed with full migration planning.');
} else {
$this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');
$this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');
}
}
}
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.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"pipedrive-sdk-poc, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.05618351,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: pipedrive-sdk-poc","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.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","depth":4,"bounds":{"left":0.15724733,"top":0.2952913,"width":0.21476063,"height":0.7047087},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.57912236,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"51","depth":4,"bounds":{"left":0.5884308,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.6000665,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6090425,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"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.6163564,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"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\\Console\\Commands\\Crm;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Console\\Command;\nuse Jiminny\\Component\\Encryption\\EncryptedTokenManager;\nuse Jiminny\\Models\\SocialAccount;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Pipedrive\\versions\\v1\\Api\\ActivitiesApi;\nuse Pipedrive\\versions\\v1\\Api\\ActivityFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonsApi;\nuse Pipedrive\\versions\\v1\\Configuration;\n\nclass TestPipedriveOfficialSdkCommand extends Command\n{\n protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';\n\n protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';\n\n private array $results = [];\n\n private EncryptedTokenManager $tokenManager;\n\n public function __construct(EncryptedTokenManager $tokenManager)\n {\n parent::__construct();\n $this->tokenManager = $tokenManager;\n }\n\n public function handle(): int\n {\n $teamId = (int) $this->argument('teamId');\n\n $this->info(\"Starting Pipedrive Official SDK POC for Team ID: {$teamId}\");\n $this->newLine();\n\n try {\n $this->runTests($teamId);\n } catch (Exception $e) {\n $this->error(\"POC failed with error: {$e->getMessage()}\");\n $this->error($e->getTraceAsString());\n\n return 1;\n }\n\n $this->displayResults();\n\n return 0;\n }\n\n private function runTests(int $teamId): void\n {\n $team = Team::find($teamId);\n if (! $team) {\n throw new Exception(\"Team with ID {$teamId} not found\");\n }\n\n $socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)\n ->whereHas('sociable', function ($query) use ($team) {\n $query->where('team_id', $team->id);\n })\n ->orderByDesc('expires') // Get the most recently expiring token\n ->first();\n\n if (! $socialAccount) {\n throw new Exception(\"No Pipedrive social account found for team {$teamId}\");\n }\n\n $this->info(\"Found Pipedrive account for team: {$team->name}\");\n $this->newLine();\n\n // Test 1: OAuth Initialization\n $this->testOAuthInitialization($socialAccount);\n\n // Test 2: Basic CRUD Operations\n $this->testBasicCrudOperations($socialAccount);\n\n // Test 3: Field Metadata\n $this->testFieldMetadata($socialAccount);\n\n // Test 4: v2 API Support\n $this->testV2ApiSupport($socialAccount);\n\n // Test 5: Error Handling\n $this->testErrorHandling($socialAccount);\n }\n\n private function testOAuthInitialization(SocialAccount $socialAccount): void\n {\n $this->info('Test 1: OAuth Initialization');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $client = new DealsApi(null, $config);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', true, \"Client initialized successfully in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);\n }\n\n $this->newLine();\n }\n\n private function testBasicCrudOperations(SocialAccount $socialAccount): void\n {\n $this->info('Test 2: Basic CRUD Operations');\n\n try {\n // Get the raw token from database\n $rawDbToken = $socialAccount->getProviderUserToken();\n $this->info(\"DB token (length: \" . strlen($rawDbToken) . \")\");\n $this->info(\"DB token preview: \" . substr($rawDbToken, 0, 20) . \"...\");\n\n // Check token expiry\n $expires = $socialAccount->expires;\n $this->info(\"Token expires at: \" . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));\n $this->info(\"Token is expired: \" . ($expires && $expires < time() ? 'YES' : 'NO'));\n\n // Test with the DB token directly\n $this->info(\"\\n--- Testing with DB token ---\");\n $config = new Configuration();\n $config->setAccessToken($rawDbToken);\n\n // Test 2a: Get Deals\n $this->testGetDeals($config);\n\n // Test 2b: Get Persons\n $this->testGetPersons($config);\n\n // Test 2c: Get Activities\n $this->testGetActivities($config);\n } catch (Exception $e) {\n $this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testCurrentSystemApproach(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n // Simulate how current system makes requests\n $token = $socialAccount->getProviderUserToken();\n $url = 'https://api.pipedrive.com/api/v2/deals';\n\n $headers = [\n 'Authorization' => 'Bearer ' . $token,\n 'Accept' => 'application/json',\n ];\n\n $client = new \\GuzzleHttp\\Client();\n $response = $client->get($url, [\n 'headers' => $headers,\n 'timeout' => 30,\n ]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $statusCode = $response->getStatusCode();\n $this->recordResult('Current System v2 API', $statusCode === 200, \"Status: {$statusCode} in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetDeals(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealsApi(null, $config);\n // Official SDK uses getDealsSummary instead of getDeals\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', true, \"Retrieved deals summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersons(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getPersons method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivities(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getActivities method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testFieldMetadata(SocialAccount $socialAccount): void\n {\n $this->info('Test 3: Field Metadata');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n // Test 3a: Get Deal Fields\n $this->testGetDealFields($config);\n\n // Test 3b: Get Person Fields\n $this->testGetPersonFields($config);\n\n // Test 3c: Get Activity Fields\n $this->testGetActivityFields($config);\n } catch (Exception $e) {\n $this->recordResult('Field Metadata', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testGetDealFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealFieldsApi(null, $config);\n $result = $apiInstance->getDealFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Deal Fields', true, \"Retrieved {$count} deal fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersonFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonFieldsApi(null, $config);\n $result = $apiInstance->getPersonFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Person Fields', true, \"Retrieved {$count} person fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivityFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivityFieldsApi(null, $config);\n $result = $apiInstance->getActivityFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Activity Fields', true, \"Retrieved {$count} activity fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testV2ApiSupport(SocialAccount $socialAccount): void\n {\n $this->info('Test 4: v2 API Support (Cursor-based Pagination)');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $startTime = microtime(true);\n\n // Test v2 deals endpoint with cursor pagination\n // Note: The official SDK may or may not have v2 API support\n // This test will determine if v2 is available\n try {\n $apiInstance = new DealsApi(null, $config);\n $updatedSince = Carbon::now()->subDays(30)->toRfc3339String();\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n\n // Check if response has cursor information (v2 indicator)\n // The response object doesn't have getAdditionalData method\n // Just test if the call works - v2 API support is limited in official SDK v1\n $this->recordResult('v2 API Support', false, \"v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);\n }\n } catch (Exception $e) {\n $this->recordResult('v2 API Support', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testErrorHandling(SocialAccount $socialAccount): void\n {\n $this->info('Test 5: Error Handling');\n\n try {\n // Test 5a: Invalid Token\n $this->testInvalidToken();\n\n // Test 5b: Non-existent Resource\n $this->testNonExistentResource($socialAccount);\n } catch (Exception $e) {\n $this->recordResult('Error Handling', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testInvalidToken(): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken('invalid_token');\n $client = new DealsApi(null, $config);\n\n $client->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function testNonExistentResource(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n $client = new DealsApi(null, $config);\n\n // Try to get a deal with a very high ID that likely doesn't exist\n // Note: Official SDK doesn't have a simple getDeal method, so skip this\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function recordResult(string $testName, bool $success, string $message, float $elapsed): void\n {\n $this->results[] = [\n 'test' => $testName,\n 'success' => $success,\n 'message' => $message,\n 'elapsed' => $elapsed,\n ];\n\n $status = $success ? '✅ PASS' : '❌ FAIL';\n $this->line(\" {$status} - {$testName}: {$message}\");\n }\n\n private function displayResults(): void\n {\n $this->newLine();\n $this->info('=== POC Test Results Summary ===');\n $this->newLine();\n\n $total = count($this->results);\n $passed = count(array_filter($this->results, fn ($r) => $r['success']));\n $failed = $total - $passed;\n\n $this->line(\"Total Tests: {$total}\");\n $this->line(\"Passed: {$passed}\");\n $this->line(\"Failed: {$failed}\");\n $this->newLine();\n\n $totalTime = array_sum(array_column($this->results, 'elapsed'));\n $this->line(\"Total Execution Time: {$totalTime}ms\");\n $this->newLine();\n\n if ($failed > 0) {\n $this->warn('Failed Tests:');\n foreach ($this->results as $result) {\n if (! $result['success']) {\n $this->line(\" - {$result['test']}: {$result['message']}\");\n }\n }\n $this->newLine();\n }\n\n $this->info('=== Detailed Results ===');\n foreach ($this->results as $result) {\n $status = $result['success'] ? '✅' : '❌';\n $this->line(\"{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}\");\n }\n\n $this->newLine();\n $this->info('=== Recommendations ===');\n\n if ($passed === $total) {\n $this->info('✅ All tests passed! The official SDK is viable for migration.');\n $this->info('Next steps: Proceed with full migration planning.');\n } else {\n $this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');\n $this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Console\\Commands\\Crm;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Console\\Command;\nuse Jiminny\\Component\\Encryption\\EncryptedTokenManager;\nuse Jiminny\\Models\\SocialAccount;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Pipedrive\\versions\\v1\\Api\\ActivitiesApi;\nuse Pipedrive\\versions\\v1\\Api\\ActivityFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonsApi;\nuse Pipedrive\\versions\\v1\\Configuration;\n\nclass TestPipedriveOfficialSdkCommand extends Command\n{\n protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';\n\n protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';\n\n private array $results = [];\n\n private EncryptedTokenManager $tokenManager;\n\n public function __construct(EncryptedTokenManager $tokenManager)\n {\n parent::__construct();\n $this->tokenManager = $tokenManager;\n }\n\n public function handle(): int\n {\n $teamId = (int) $this->argument('teamId');\n\n $this->info(\"Starting Pipedrive Official SDK POC for Team ID: {$teamId}\");\n $this->newLine();\n\n try {\n $this->runTests($teamId);\n } catch (Exception $e) {\n $this->error(\"POC failed with error: {$e->getMessage()}\");\n $this->error($e->getTraceAsString());\n\n return 1;\n }\n\n $this->displayResults();\n\n return 0;\n }\n\n private function runTests(int $teamId): void\n {\n $team = Team::find($teamId);\n if (! $team) {\n throw new Exception(\"Team with ID {$teamId} not found\");\n }\n\n $socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)\n ->whereHas('sociable', function ($query) use ($team) {\n $query->where('team_id', $team->id);\n })\n ->orderByDesc('expires') // Get the most recently expiring token\n ->first();\n\n if (! $socialAccount) {\n throw new Exception(\"No Pipedrive social account found for team {$teamId}\");\n }\n\n $this->info(\"Found Pipedrive account for team: {$team->name}\");\n $this->newLine();\n\n // Test 1: OAuth Initialization\n $this->testOAuthInitialization($socialAccount);\n\n // Test 2: Basic CRUD Operations\n $this->testBasicCrudOperations($socialAccount);\n\n // Test 3: Field Metadata\n $this->testFieldMetadata($socialAccount);\n\n // Test 4: v2 API Support\n $this->testV2ApiSupport($socialAccount);\n\n // Test 5: Error Handling\n $this->testErrorHandling($socialAccount);\n }\n\n private function testOAuthInitialization(SocialAccount $socialAccount): void\n {\n $this->info('Test 1: OAuth Initialization');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $client = new DealsApi(null, $config);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', true, \"Client initialized successfully in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);\n }\n\n $this->newLine();\n }\n\n private function testBasicCrudOperations(SocialAccount $socialAccount): void\n {\n $this->info('Test 2: Basic CRUD Operations');\n\n try {\n // Get the raw token from database\n $rawDbToken = $socialAccount->getProviderUserToken();\n $this->info(\"DB token (length: \" . strlen($rawDbToken) . \")\");\n $this->info(\"DB token preview: \" . substr($rawDbToken, 0, 20) . \"...\");\n\n // Check token expiry\n $expires = $socialAccount->expires;\n $this->info(\"Token expires at: \" . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));\n $this->info(\"Token is expired: \" . ($expires && $expires < time() ? 'YES' : 'NO'));\n\n // Test with the DB token directly\n $this->info(\"\\n--- Testing with DB token ---\");\n $config = new Configuration();\n $config->setAccessToken($rawDbToken);\n\n // Test 2a: Get Deals\n $this->testGetDeals($config);\n\n // Test 2b: Get Persons\n $this->testGetPersons($config);\n\n // Test 2c: Get Activities\n $this->testGetActivities($config);\n } catch (Exception $e) {\n $this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testCurrentSystemApproach(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n // Simulate how current system makes requests\n $token = $socialAccount->getProviderUserToken();\n $url = 'https://api.pipedrive.com/api/v2/deals';\n\n $headers = [\n 'Authorization' => 'Bearer ' . $token,\n 'Accept' => 'application/json',\n ];\n\n $client = new \\GuzzleHttp\\Client();\n $response = $client->get($url, [\n 'headers' => $headers,\n 'timeout' => 30,\n ]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $statusCode = $response->getStatusCode();\n $this->recordResult('Current System v2 API', $statusCode === 200, \"Status: {$statusCode} in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetDeals(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealsApi(null, $config);\n // Official SDK uses getDealsSummary instead of getDeals\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', true, \"Retrieved deals summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersons(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getPersons method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivities(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getActivities method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testFieldMetadata(SocialAccount $socialAccount): void\n {\n $this->info('Test 3: Field Metadata');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n // Test 3a: Get Deal Fields\n $this->testGetDealFields($config);\n\n // Test 3b: Get Person Fields\n $this->testGetPersonFields($config);\n\n // Test 3c: Get Activity Fields\n $this->testGetActivityFields($config);\n } catch (Exception $e) {\n $this->recordResult('Field Metadata', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testGetDealFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealFieldsApi(null, $config);\n $result = $apiInstance->getDealFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Deal Fields', true, \"Retrieved {$count} deal fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersonFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonFieldsApi(null, $config);\n $result = $apiInstance->getPersonFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Person Fields', true, \"Retrieved {$count} person fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivityFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivityFieldsApi(null, $config);\n $result = $apiInstance->getActivityFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Activity Fields', true, \"Retrieved {$count} activity fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testV2ApiSupport(SocialAccount $socialAccount): void\n {\n $this->info('Test 4: v2 API Support (Cursor-based Pagination)');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $startTime = microtime(true);\n\n // Test v2 deals endpoint with cursor pagination\n // Note: The official SDK may or may not have v2 API support\n // This test will determine if v2 is available\n try {\n $apiInstance = new DealsApi(null, $config);\n $updatedSince = Carbon::now()->subDays(30)->toRfc3339String();\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n\n // Check if response has cursor information (v2 indicator)\n // The response object doesn't have getAdditionalData method\n // Just test if the call works - v2 API support is limited in official SDK v1\n $this->recordResult('v2 API Support', false, \"v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);\n }\n } catch (Exception $e) {\n $this->recordResult('v2 API Support', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testErrorHandling(SocialAccount $socialAccount): void\n {\n $this->info('Test 5: Error Handling');\n\n try {\n // Test 5a: Invalid Token\n $this->testInvalidToken();\n\n // Test 5b: Non-existent Resource\n $this->testNonExistentResource($socialAccount);\n } catch (Exception $e) {\n $this->recordResult('Error Handling', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testInvalidToken(): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken('invalid_token');\n $client = new DealsApi(null, $config);\n\n $client->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function testNonExistentResource(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n $client = new DealsApi(null, $config);\n\n // Try to get a deal with a very high ID that likely doesn't exist\n // Note: Official SDK doesn't have a simple getDeal method, so skip this\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function recordResult(string $testName, bool $success, string $message, float $elapsed): void\n {\n $this->results[] = [\n 'test' => $testName,\n 'success' => $success,\n 'message' => $message,\n 'elapsed' => $elapsed,\n ];\n\n $status = $success ? '✅ PASS' : '❌ FAIL';\n $this->line(\" {$status} - {$testName}: {$message}\");\n }\n\n private function displayResults(): void\n {\n $this->newLine();\n $this->info('=== POC Test Results Summary ===');\n $this->newLine();\n\n $total = count($this->results);\n $passed = count(array_filter($this->results, fn ($r) => $r['success']));\n $failed = $total - $passed;\n\n $this->line(\"Total Tests: {$total}\");\n $this->line(\"Passed: {$passed}\");\n $this->line(\"Failed: {$failed}\");\n $this->newLine();\n\n $totalTime = array_sum(array_column($this->results, 'elapsed'));\n $this->line(\"Total Execution Time: {$totalTime}ms\");\n $this->newLine();\n\n if ($failed > 0) {\n $this->warn('Failed Tests:');\n foreach ($this->results as $result) {\n if (! $result['success']) {\n $this->line(\" - {$result['test']}: {$result['message']}\");\n }\n }\n $this->newLine();\n }\n\n $this->info('=== Detailed Results ===');\n foreach ($this->results as $result) {\n $status = $result['success'] ? '✅' : '❌';\n $this->line(\"{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}\");\n }\n\n $this->newLine();\n $this->info('=== Recommendations ===');\n\n if ($passed === $total) {\n $this->info('✅ All tests passed! The official SDK is viable for migration.');\n $this->info('Next steps: Proceed with full migration planning.');\n } else {\n $this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');\n $this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
5365831007722038384
|
61370652661533572
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
1
51
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Jiminny\Component\Encryption\EncryptedTokenManager;
use Jiminny\Models\SocialAccount;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Pipedrive\versions\v1\Api\ActivitiesApi;
use Pipedrive\versions\v1\Api\ActivityFieldsApi;
use Pipedrive\versions\v1\Api\DealFieldsApi;
use Pipedrive\versions\v1\Api\DealsApi;
use Pipedrive\versions\v1\Api\PersonFieldsApi;
use Pipedrive\versions\v1\Api\PersonsApi;
use Pipedrive\versions\v1\Configuration;
class TestPipedriveOfficialSdkCommand extends Command
{
protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';
protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';
private array $results = [];
private EncryptedTokenManager $tokenManager;
public function __construct(EncryptedTokenManager $tokenManager)
{
parent::__construct();
$this->tokenManager = $tokenManager;
}
public function handle(): int
{
$teamId = (int) $this->argument('teamId');
$this->info("Starting Pipedrive Official SDK POC for Team ID: {$teamId}");
$this->newLine();
try {
$this->runTests($teamId);
} catch (Exception $e) {
$this->error("POC failed with error: {$e->getMessage()}");
$this->error($e->getTraceAsString());
return 1;
}
$this->displayResults();
return 0;
}
private function runTests(int $teamId): void
{
$team = Team::find($teamId);
if (! $team) {
throw new Exception("Team with ID {$teamId} not found");
}
$socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)
->whereHas('sociable', function ($query) use ($team) {
$query->where('team_id', $team->id);
})
->orderByDesc('expires') // Get the most recently expiring token
->first();
if (! $socialAccount) {
throw new Exception("No Pipedrive social account found for team {$teamId}");
}
$this->info("Found Pipedrive account for team: {$team->name}");
$this->newLine();
// Test 1: OAuth Initialization
$this->testOAuthInitialization($socialAccount);
// Test 2: Basic CRUD Operations
$this->testBasicCrudOperations($socialAccount);
// Test 3: Field Metadata
$this->testFieldMetadata($socialAccount);
// Test 4: v2 API Support
$this->testV2ApiSupport($socialAccount);
// Test 5: Error Handling
$this->testErrorHandling($socialAccount);
}
private function testOAuthInitialization(SocialAccount $socialAccount): void
{
$this->info('Test 1: OAuth Initialization');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', true, "Client initialized successfully in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);
}
$this->newLine();
}
private function testBasicCrudOperations(SocialAccount $socialAccount): void
{
$this->info('Test 2: Basic CRUD Operations');
try {
// Get the raw token from database
$rawDbToken = $socialAccount->getProviderUserToken();
$this->info("DB token (length: " . strlen($rawDbToken) . ")");
$this->info("DB token preview: " . substr($rawDbToken, 0, 20) . "...");
// Check token expiry
$expires = $socialAccount->expires;
$this->info("Token expires at: " . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));
$this->info("Token is expired: " . ($expires && $expires < time() ? 'YES' : 'NO'));
// Test with the DB token directly
$this->info("\n--- Testing with DB token ---");
$config = new Configuration();
$config->setAccessToken($rawDbToken);
// Test 2a: Get Deals
$this->testGetDeals($config);
// Test 2b: Get Persons
$this->testGetPersons($config);
// Test 2c: Get Activities
$this->testGetActivities($config);
} catch (Exception $e) {
$this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testCurrentSystemApproach(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
// Simulate how current system makes requests
$token = $socialAccount->getProviderUserToken();
$url = 'https://api.pipedrive.com/api/v2/deals';
$headers = [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
];
$client = new \GuzzleHttp\Client();
$response = $client->get($url, [
'headers' => $headers,
'timeout' => 30,
]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$statusCode = $response->getStatusCode();
$this->recordResult('Current System v2 API', $statusCode === 200, "Status: {$statusCode} in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);
}
}
private function testGetDeals(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealsApi(null, $config);
// Official SDK uses getDealsSummary instead of getDeals
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', true, "Retrieved deals summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersons(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getPersons method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivities(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getActivities method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);
}
}
private function testFieldMetadata(SocialAccount $socialAccount): void
{
$this->info('Test 3: Field Metadata');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
// Test 3a: Get Deal Fields
$this->testGetDealFields($config);
// Test 3b: Get Person Fields
$this->testGetPersonFields($config);
// Test 3c: Get Activity Fields
$this->testGetActivityFields($config);
} catch (Exception $e) {
$this->recordResult('Field Metadata', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testGetDealFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealFieldsApi(null, $config);
$result = $apiInstance->getDealFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Deal Fields', true, "Retrieved {$count} deal fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersonFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new PersonFieldsApi(null, $config);
$result = $apiInstance->getPersonFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Person Fields', true, "Retrieved {$count} person fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivityFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new ActivityFieldsApi(null, $config);
$result = $apiInstance->getActivityFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Activity Fields', true, "Retrieved {$count} activity fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);
}
}
private function testV2ApiSupport(SocialAccount $socialAccount): void
{
$this->info('Test 4: v2 API Support (Cursor-based Pagination)');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$startTime = microtime(true);
// Test v2 deals endpoint with cursor pagination
// Note: The official SDK may or may not have v2 API support
// This test will determine if v2 is available
try {
$apiInstance = new DealsApi(null, $config);
$updatedSince = Carbon::now()->subDays(30)->toRfc3339String();
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
// Check if response has cursor information (v2 indicator)
// The response object doesn't have getAdditionalData method
// Just test if the call works - v2 API support is limited in official SDK v1
$this->recordResult('v2 API Support', false, "v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);
}
} catch (Exception $e) {
$this->recordResult('v2 API Support', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testErrorHandling(SocialAccount $socialAccount): void
{
$this->info('Test 5: Error Handling');
try {
// Test 5a: Invalid Token
$this->testInvalidToken();
// Test 5b: Non-existent Resource
$this->testNonExistentResource($socialAccount);
} catch (Exception $e) {
$this->recordResult('Error Handling', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testInvalidToken(): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken('invalid_token');
$client = new DealsApi(null, $config);
$client->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function testNonExistentResource(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
// Try to get a deal with a very high ID that likely doesn't exist
// Note: Official SDK doesn't have a simple getDeal method, so skip this
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function recordResult(string $testName, bool $success, string $message, float $elapsed): void
{
$this->results[] = [
'test' => $testName,
'success' => $success,
'message' => $message,
'elapsed' => $elapsed,
];
$status = $success ? '✅ PASS' : '❌ FAIL';
$this->line(" {$status} - {$testName}: {$message}");
}
private function displayResults(): void
{
$this->newLine();
$this->info('=== POC Test Results Summary ===');
$this->newLine();
$total = count($this->results);
$passed = count(array_filter($this->results, fn ($r) => $r['success']));
$failed = $total - $passed;
$this->line("Total Tests: {$total}");
$this->line("Passed: {$passed}");
$this->line("Failed: {$failed}");
$this->newLine();
$totalTime = array_sum(array_column($this->results, 'elapsed'));
$this->line("Total Execution Time: {$totalTime}ms");
$this->newLine();
if ($failed > 0) {
$this->warn('Failed Tests:');
foreach ($this->results as $result) {
if (! $result['success']) {
$this->line(" - {$result['test']}: {$result['message']}");
}
}
$this->newLine();
}
$this->info('=== Detailed Results ===');
foreach ($this->results as $result) {
$status = $result['success'] ? '✅' : '❌';
$this->line("{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}");
}
$this->newLine();
$this->info('=== Recommendations ===');
if ($passed === $total) {
$this->info('✅ All tests passed! The official SDK is viable for migration.');
$this->info('Next steps: Proceed with full migration planning.');
} else {
$this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');
$this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
49395
|
1762
|
20
|
2026-05-18T06:39:45.852201+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086385852_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"pipedrive-sdk-poc, menu","depth":5,"on_screen":true,"help_text":"Git Branch: pipedrive-sdk-poc","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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-8603436982613086966
|
-8708828999581586494
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
Term2ShellEditSessionProfilesWindowHelpDOCKER (docker-compose)O ₴2DOCKER81DEV (-zsh)11DOCKER (docker-compose)NFO""component" ::"e802ad473a4f""o.e.p.PluginsService","cluster. name" :"docker-cluster""node. name""message" :module [x-pack-ql]"elasticsearch"timestamp":"2026-05-18T06:39:35,926Z"NFO""component": "o.e.p.PluginsService""cluster.name" :"docker-cluster""level": "I"node .name""e802ad473a4f""message":elasticsearch"loaded module [x-pack-rollup]" }"timestamp":NFO""2026-05-18T06:39:35,926Z""component": "o.e.p.PluginsService""node. name""e802ad473a4f""message" :"loaded module [x-pack-security]" }elasticsearch"timestamp" :"2026-05-18T06:39:35,926Z"NFO","component" :"o.e.p.PluginsService""cluster.name":"docker-cluster","node. name": "e802ad473a4f""message":"Loaded module [x-pack-sql]" }elasticsearchNFO",1 {"type":"server""timestamp" :"2026-05-18T06:39:35,926Z""component":"o.e.p.PluginsService","cluster.name":"docker-cluster""level":"I"node. name""e802ad473a4f""message" :"loadedmodule [x-pack-stack]"}elasticsearchI {"type":"server""timestamp""2026-05-18T06:39:35,926Z"NFO""component":"o.e.p.PluginsService"cluster.name":"docker-cluster""level":"I"node. name""e802ad473a4f""message" :"loadedmodule[x-pack-voting-only-node]" }elasticsearch | {"type": "server""timestamp": "2026-05-18T06:39:35,926Z"NFO","component": "o.e.p.PluginsService","cluster.name":"docker-cluster""level":"I"node. name": "e802ad473a4f"', "message": "loaded module [x-pack-watcher]" }elasticsearch1 {"type": "server""timestamp": "2026-05-18T06:39:35,926Z"NFO", "component": "o.e.p.PluginsService", "cluster.name":"docker-cluster""level":"I"node. name": "e802ad473a4f"', "message": "no plugins loaded" 3elasticsearchI {"type": "deprecation", "timestamp": "2026-05-18T06:39:35,995Z", "level": "DEPRECATION",node.name":"component":"o.e.d.c.s.Settings", "cluster.name":"docker-cluster""e802ad473a4f", "message":"[node.data] setting was deprecated in Elasticsearch and will be removed in a future release! See the breakingdocumentation forthe next major version." }elasticsearch1 {"type": "server"NFO""component": "o.e.e.NodeEnvironment",ments, uster-n-m5-18 Tdocker-clusten","Level name"node. name":"e80Zad473a4f", "message": "using [1] data paths, mounts [[/usr/share/elasticsearch/data (/dev/vda1)]], net usable_space [14gb], net total_space [58.3gb], types [ext4]" }elasticsearch1 {"type": "server""timestamp": "2026-05-18T06:39:36,015Z","level": "INFO", "component": "o.e.e.NodeEnvironment""cluster.name": "docker-cluster""node.name": "e802ad473a4f", "message": "heap size [700mb], compressed ordinary object pointers [true]" }elasticsearchNFO",1 {"type": "server"', "timestamp": "2026-05-18T06:39:36,106Z""level": "I"component": "o.e.n.Node""cluster.name": "docker-cluster""node.name": "e802ad473a4f""message": "node name [e802ad473a4f], node ID [e2ZKzgw4Q4aCf2w51jWr1A], clustername [docker-cluster], roles [transform, master, remote_cluster_client, data, ml, data_content, data_hot, data_warm, data_cold, ingest]" }100% <8• Mon 18 May 9:39:45screenpipe"181O &4APP (-zsh)*3Y2PROD (-zsh)Last login: Sat May 16 18:04:33 on ttys001Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.toml file in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ |X L3 EU (-zsh)Last login: Sat May 16 18:04:33 on ttys001Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parents@Lukas-Kovaliks-MacBook-Pro-Jiminny~$ IX T4STAGE (-zsh)Last login: Sat May 16 18:04:33 on ttys001Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.toml file in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-JiminnyT5QA (-zsh)Last login: Mon May 18 09:17:28on ttys003Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentsX T6FE (-zsh)Last login: Mon May 18 09:17:28on ttys004Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry couldnotfind a pyproject.tomlfile in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ IEXT(-zsh)Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry couldnot find a pyproject.tomlfile in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ [|PRODSTAGEFRONTENDEXTENSIONV View in Docker Desktop• View ConfigEnable Watch...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
49396
|
1762
|
21
|
2026-05-18T06:39:57.425889+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086397425_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
1
51
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Jiminny\Component\Encryption\EncryptedTokenManager;
use Jiminny\Models\SocialAccount;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Pipedrive\versions\v1\Api\ActivitiesApi;
use Pipedrive\versions\v1\Api\ActivityFieldsApi;
use Pipedrive\versions\v1\Api\DealFieldsApi;
use Pipedrive\versions\v1\Api\DealsApi;
use Pipedrive\versions\v1\Api\PersonFieldsApi;
use Pipedrive\versions\v1\Api\PersonsApi;
use Pipedrive\versions\v1\Configuration;
class TestPipedriveOfficialSdkCommand extends Command
{
protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';
protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';
private array $results = [];
private EncryptedTokenManager $tokenManager;
public function __construct(EncryptedTokenManager $tokenManager)
{
parent::__construct();
$this->tokenManager = $tokenManager;
}
public function handle(): int
{
$teamId = (int) $this->argument('teamId');
$this->info("Starting Pipedrive Official SDK POC for Team ID: {$teamId}");
$this->newLine();
try {
$this->runTests($teamId);
} catch (Exception $e) {
$this->error("POC failed with error: {$e->getMessage()}");
$this->error($e->getTraceAsString());
return 1;
}
$this->displayResults();
return 0;
}
private function runTests(int $teamId): void
{
$team = Team::find($teamId);
if (! $team) {
throw new Exception("Team with ID {$teamId} not found");
}
$socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)
->whereHas('sociable', function ($query) use ($team) {
$query->where('team_id', $team->id);
})
->orderByDesc('expires') // Get the most recently expiring token
->first();
if (! $socialAccount) {
throw new Exception("No Pipedrive social account found for team {$teamId}");
}
$this->info("Found Pipedrive account for team: {$team->name}");
$this->newLine();
// Test 1: OAuth Initialization
$this->testOAuthInitialization($socialAccount);
// Test 2: Basic CRUD Operations
$this->testBasicCrudOperations($socialAccount);
// Test 3: Field Metadata
$this->testFieldMetadata($socialAccount);
// Test 4: v2 API Support
$this->testV2ApiSupport($socialAccount);
// Test 5: Error Handling
$this->testErrorHandling($socialAccount);
}
private function testOAuthInitialization(SocialAccount $socialAccount): void
{
$this->info('Test 1: OAuth Initialization');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', true, "Client initialized successfully in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);
}
$this->newLine();
}
private function testBasicCrudOperations(SocialAccount $socialAccount): void
{
$this->info('Test 2: Basic CRUD Operations');
try {
// Get the raw token from database
$rawDbToken = $socialAccount->getProviderUserToken();
$this->info("DB token (length: " . strlen($rawDbToken) . ")");
$this->info("DB token preview: " . substr($rawDbToken, 0, 20) . "...");
// Check token expiry
$expires = $socialAccount->expires;
$this->info("Token expires at: " . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));
$this->info("Token is expired: " . ($expires && $expires < time() ? 'YES' : 'NO'));
// Test with the DB token directly
$this->info("\n--- Testing with DB token ---");
$config = new Configuration();
$config->setAccessToken($rawDbToken);
// Test 2a: Get Deals
$this->testGetDeals($config);
// Test 2b: Get Persons
$this->testGetPersons($config);
// Test 2c: Get Activities
$this->testGetActivities($config);
} catch (Exception $e) {
$this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testCurrentSystemApproach(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
// Simulate how current system makes requests
$token = $socialAccount->getProviderUserToken();
$url = 'https://api.pipedrive.com/api/v2/deals';
$headers = [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
];
$client = new \GuzzleHttp\Client();
$response = $client->get($url, [
'headers' => $headers,
'timeout' => 30,
]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$statusCode = $response->getStatusCode();
$this->recordResult('Current System v2 API', $statusCode === 200, "Status: {$statusCode} in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);
}
}
private function testGetDeals(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealsApi(null, $config);
// Official SDK uses getDealsSummary instead of getDeals
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', true, "Retrieved deals summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersons(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getPersons method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivities(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getActivities method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);
}
}
private function testFieldMetadata(SocialAccount $socialAccount): void
{
$this->info('Test 3: Field Metadata');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
// Test 3a: Get Deal Fields
$this->testGetDealFields($config);
// Test 3b: Get Person Fields
$this->testGetPersonFields($config);
// Test 3c: Get Activity Fields
$this->testGetActivityFields($config);
} catch (Exception $e) {
$this->recordResult('Field Metadata', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testGetDealFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealFieldsApi(null, $config);
$result = $apiInstance->getDealFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Deal Fields', true, "Retrieved {$count} deal fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersonFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new PersonFieldsApi(null, $config);
$result = $apiInstance->getPersonFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Person Fields', true, "Retrieved {$count} person fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivityFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new ActivityFieldsApi(null, $config);
$result = $apiInstance->getActivityFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Activity Fields', true, "Retrieved {$count} activity fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);
}
}
private function testV2ApiSupport(SocialAccount $socialAccount): void
{
$this->info('Test 4: v2 API Support (Cursor-based Pagination)');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$startTime = microtime(true);
// Test v2 deals endpoint with cursor pagination
// Note: The official SDK may or may not have v2 API support
// This test will determine if v2 is available
try {
$apiInstance = new DealsApi(null, $config);
$updatedSince = Carbon::now()->subDays(30)->toRfc3339String();
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
// Check if response has cursor information (v2 indicator)
// The response object doesn't have getAdditionalData method
// Just test if the call works - v2 API support is limited in official SDK v1
$this->recordResult('v2 API Support', false, "v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);
}
} catch (Exception $e) {
$this->recordResult('v2 API Support', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testErrorHandling(SocialAccount $socialAccount): void
{
$this->info('Test 5: Error Handling');
try {
// Test 5a: Invalid Token
$this->testInvalidToken();
// Test 5b: Non-existent Resource
$this->testNonExistentResource($socialAccount);
} catch (Exception $e) {
$this->recordResult('Error Handling', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testInvalidToken(): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken('invalid_token');
$client = new DealsApi(null, $config);
$client->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function testNonExistentResource(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
// Try to get a deal with a very high ID that likely doesn't exist
// Note: Official SDK doesn't have a simple getDeal method, so skip this
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function recordResult(string $testName, bool $success, string $message, float $elapsed): void
{
$this->results[] = [
'test' => $testName,
'success' => $success,
'message' => $message,
'elapsed' => $elapsed,
];
$status = $success ? '✅ PASS' : '❌ FAIL';
$this->line(" {$status} - {$testName}: {$message}");
}
private function displayResults(): void
{
$this->newLine();
$this->info('=== POC Test Results Summary ===');
$this->newLine();
$total = count($this->results);
$passed = count(array_filter($this->results, fn ($r) => $r['success']));
$failed = $total - $passed;
$this->line("Total Tests: {$total}");
$this->line("Passed: {$passed}");
$this->line("Failed: {$failed}");
$this->newLine();
$totalTime = array_sum(array_column($this->results, 'elapsed'));
$this->line("Total Execution Time: {$totalTime}ms");
$this->newLine();
if ($failed > 0) {
$this->warn('Failed Tests:');
foreach ($this->results as $result) {
if (! $result['success']) {
$this->line(" - {$result['test']}: {$result['message']}");
}
}
$this->newLine();
}
$this->info('=== Detailed Results ===');
foreach ($this->results as $result) {
$status = $result['success'] ? '✅' : '❌';
$this->line("{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}");
}
$this->newLine();
$this->info('=== Recommendations ===');
if ($passed === $total) {
$this->info('✅ All tests passed! The official SDK is viable for migration.');
$this->info('Next steps: Proceed with full migration planning.');
} else {
$this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');
$this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"pipedrive-sdk-poc, menu","depth":5,"on_screen":true,"help_text":"Git Branch: pipedrive-sdk-poc","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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","role_description":"text entry area","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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"51","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"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\\Console\\Commands\\Crm;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Console\\Command;\nuse Jiminny\\Component\\Encryption\\EncryptedTokenManager;\nuse Jiminny\\Models\\SocialAccount;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Pipedrive\\versions\\v1\\Api\\ActivitiesApi;\nuse Pipedrive\\versions\\v1\\Api\\ActivityFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonsApi;\nuse Pipedrive\\versions\\v1\\Configuration;\n\nclass TestPipedriveOfficialSdkCommand extends Command\n{\n protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';\n\n protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';\n\n private array $results = [];\n\n private EncryptedTokenManager $tokenManager;\n\n public function __construct(EncryptedTokenManager $tokenManager)\n {\n parent::__construct();\n $this->tokenManager = $tokenManager;\n }\n\n public function handle(): int\n {\n $teamId = (int) $this->argument('teamId');\n\n $this->info(\"Starting Pipedrive Official SDK POC for Team ID: {$teamId}\");\n $this->newLine();\n\n try {\n $this->runTests($teamId);\n } catch (Exception $e) {\n $this->error(\"POC failed with error: {$e->getMessage()}\");\n $this->error($e->getTraceAsString());\n\n return 1;\n }\n\n $this->displayResults();\n\n return 0;\n }\n\n private function runTests(int $teamId): void\n {\n $team = Team::find($teamId);\n if (! $team) {\n throw new Exception(\"Team with ID {$teamId} not found\");\n }\n\n $socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)\n ->whereHas('sociable', function ($query) use ($team) {\n $query->where('team_id', $team->id);\n })\n ->orderByDesc('expires') // Get the most recently expiring token\n ->first();\n\n if (! $socialAccount) {\n throw new Exception(\"No Pipedrive social account found for team {$teamId}\");\n }\n\n $this->info(\"Found Pipedrive account for team: {$team->name}\");\n $this->newLine();\n\n // Test 1: OAuth Initialization\n $this->testOAuthInitialization($socialAccount);\n\n // Test 2: Basic CRUD Operations\n $this->testBasicCrudOperations($socialAccount);\n\n // Test 3: Field Metadata\n $this->testFieldMetadata($socialAccount);\n\n // Test 4: v2 API Support\n $this->testV2ApiSupport($socialAccount);\n\n // Test 5: Error Handling\n $this->testErrorHandling($socialAccount);\n }\n\n private function testOAuthInitialization(SocialAccount $socialAccount): void\n {\n $this->info('Test 1: OAuth Initialization');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $client = new DealsApi(null, $config);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', true, \"Client initialized successfully in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);\n }\n\n $this->newLine();\n }\n\n private function testBasicCrudOperations(SocialAccount $socialAccount): void\n {\n $this->info('Test 2: Basic CRUD Operations');\n\n try {\n // Get the raw token from database\n $rawDbToken = $socialAccount->getProviderUserToken();\n $this->info(\"DB token (length: \" . strlen($rawDbToken) . \")\");\n $this->info(\"DB token preview: \" . substr($rawDbToken, 0, 20) . \"...\");\n\n // Check token expiry\n $expires = $socialAccount->expires;\n $this->info(\"Token expires at: \" . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));\n $this->info(\"Token is expired: \" . ($expires && $expires < time() ? 'YES' : 'NO'));\n\n // Test with the DB token directly\n $this->info(\"\\n--- Testing with DB token ---\");\n $config = new Configuration();\n $config->setAccessToken($rawDbToken);\n\n // Test 2a: Get Deals\n $this->testGetDeals($config);\n\n // Test 2b: Get Persons\n $this->testGetPersons($config);\n\n // Test 2c: Get Activities\n $this->testGetActivities($config);\n } catch (Exception $e) {\n $this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testCurrentSystemApproach(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n // Simulate how current system makes requests\n $token = $socialAccount->getProviderUserToken();\n $url = 'https://api.pipedrive.com/api/v2/deals';\n\n $headers = [\n 'Authorization' => 'Bearer ' . $token,\n 'Accept' => 'application/json',\n ];\n\n $client = new \\GuzzleHttp\\Client();\n $response = $client->get($url, [\n 'headers' => $headers,\n 'timeout' => 30,\n ]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $statusCode = $response->getStatusCode();\n $this->recordResult('Current System v2 API', $statusCode === 200, \"Status: {$statusCode} in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetDeals(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealsApi(null, $config);\n // Official SDK uses getDealsSummary instead of getDeals\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', true, \"Retrieved deals summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersons(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getPersons method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivities(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getActivities method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testFieldMetadata(SocialAccount $socialAccount): void\n {\n $this->info('Test 3: Field Metadata');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n // Test 3a: Get Deal Fields\n $this->testGetDealFields($config);\n\n // Test 3b: Get Person Fields\n $this->testGetPersonFields($config);\n\n // Test 3c: Get Activity Fields\n $this->testGetActivityFields($config);\n } catch (Exception $e) {\n $this->recordResult('Field Metadata', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testGetDealFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealFieldsApi(null, $config);\n $result = $apiInstance->getDealFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Deal Fields', true, \"Retrieved {$count} deal fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersonFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonFieldsApi(null, $config);\n $result = $apiInstance->getPersonFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Person Fields', true, \"Retrieved {$count} person fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivityFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivityFieldsApi(null, $config);\n $result = $apiInstance->getActivityFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Activity Fields', true, \"Retrieved {$count} activity fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testV2ApiSupport(SocialAccount $socialAccount): void\n {\n $this->info('Test 4: v2 API Support (Cursor-based Pagination)');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $startTime = microtime(true);\n\n // Test v2 deals endpoint with cursor pagination\n // Note: The official SDK may or may not have v2 API support\n // This test will determine if v2 is available\n try {\n $apiInstance = new DealsApi(null, $config);\n $updatedSince = Carbon::now()->subDays(30)->toRfc3339String();\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n\n // Check if response has cursor information (v2 indicator)\n // The response object doesn't have getAdditionalData method\n // Just test if the call works - v2 API support is limited in official SDK v1\n $this->recordResult('v2 API Support', false, \"v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);\n }\n } catch (Exception $e) {\n $this->recordResult('v2 API Support', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testErrorHandling(SocialAccount $socialAccount): void\n {\n $this->info('Test 5: Error Handling');\n\n try {\n // Test 5a: Invalid Token\n $this->testInvalidToken();\n\n // Test 5b: Non-existent Resource\n $this->testNonExistentResource($socialAccount);\n } catch (Exception $e) {\n $this->recordResult('Error Handling', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testInvalidToken(): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken('invalid_token');\n $client = new DealsApi(null, $config);\n\n $client->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function testNonExistentResource(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n $client = new DealsApi(null, $config);\n\n // Try to get a deal with a very high ID that likely doesn't exist\n // Note: Official SDK doesn't have a simple getDeal method, so skip this\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function recordResult(string $testName, bool $success, string $message, float $elapsed): void\n {\n $this->results[] = [\n 'test' => $testName,\n 'success' => $success,\n 'message' => $message,\n 'elapsed' => $elapsed,\n ];\n\n $status = $success ? '✅ PASS' : '❌ FAIL';\n $this->line(\" {$status} - {$testName}: {$message}\");\n }\n\n private function displayResults(): void\n {\n $this->newLine();\n $this->info('=== POC Test Results Summary ===');\n $this->newLine();\n\n $total = count($this->results);\n $passed = count(array_filter($this->results, fn ($r) => $r['success']));\n $failed = $total - $passed;\n\n $this->line(\"Total Tests: {$total}\");\n $this->line(\"Passed: {$passed}\");\n $this->line(\"Failed: {$failed}\");\n $this->newLine();\n\n $totalTime = array_sum(array_column($this->results, 'elapsed'));\n $this->line(\"Total Execution Time: {$totalTime}ms\");\n $this->newLine();\n\n if ($failed > 0) {\n $this->warn('Failed Tests:');\n foreach ($this->results as $result) {\n if (! $result['success']) {\n $this->line(\" - {$result['test']}: {$result['message']}\");\n }\n }\n $this->newLine();\n }\n\n $this->info('=== Detailed Results ===');\n foreach ($this->results as $result) {\n $status = $result['success'] ? '✅' : '❌';\n $this->line(\"{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}\");\n }\n\n $this->newLine();\n $this->info('=== Recommendations ===');\n\n if ($passed === $total) {\n $this->info('✅ All tests passed! The official SDK is viable for migration.');\n $this->info('Next steps: Proceed with full migration planning.');\n } else {\n $this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');\n $this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Console\\Commands\\Crm;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Console\\Command;\nuse Jiminny\\Component\\Encryption\\EncryptedTokenManager;\nuse Jiminny\\Models\\SocialAccount;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Pipedrive\\versions\\v1\\Api\\ActivitiesApi;\nuse Pipedrive\\versions\\v1\\Api\\ActivityFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonsApi;\nuse Pipedrive\\versions\\v1\\Configuration;\n\nclass TestPipedriveOfficialSdkCommand extends Command\n{\n protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';\n\n protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';\n\n private array $results = [];\n\n private EncryptedTokenManager $tokenManager;\n\n public function __construct(EncryptedTokenManager $tokenManager)\n {\n parent::__construct();\n $this->tokenManager = $tokenManager;\n }\n\n public function handle(): int\n {\n $teamId = (int) $this->argument('teamId');\n\n $this->info(\"Starting Pipedrive Official SDK POC for Team ID: {$teamId}\");\n $this->newLine();\n\n try {\n $this->runTests($teamId);\n } catch (Exception $e) {\n $this->error(\"POC failed with error: {$e->getMessage()}\");\n $this->error($e->getTraceAsString());\n\n return 1;\n }\n\n $this->displayResults();\n\n return 0;\n }\n\n private function runTests(int $teamId): void\n {\n $team = Team::find($teamId);\n if (! $team) {\n throw new Exception(\"Team with ID {$teamId} not found\");\n }\n\n $socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)\n ->whereHas('sociable', function ($query) use ($team) {\n $query->where('team_id', $team->id);\n })\n ->orderByDesc('expires') // Get the most recently expiring token\n ->first();\n\n if (! $socialAccount) {\n throw new Exception(\"No Pipedrive social account found for team {$teamId}\");\n }\n\n $this->info(\"Found Pipedrive account for team: {$team->name}\");\n $this->newLine();\n\n // Test 1: OAuth Initialization\n $this->testOAuthInitialization($socialAccount);\n\n // Test 2: Basic CRUD Operations\n $this->testBasicCrudOperations($socialAccount);\n\n // Test 3: Field Metadata\n $this->testFieldMetadata($socialAccount);\n\n // Test 4: v2 API Support\n $this->testV2ApiSupport($socialAccount);\n\n // Test 5: Error Handling\n $this->testErrorHandling($socialAccount);\n }\n\n private function testOAuthInitialization(SocialAccount $socialAccount): void\n {\n $this->info('Test 1: OAuth Initialization');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $client = new DealsApi(null, $config);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', true, \"Client initialized successfully in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);\n }\n\n $this->newLine();\n }\n\n private function testBasicCrudOperations(SocialAccount $socialAccount): void\n {\n $this->info('Test 2: Basic CRUD Operations');\n\n try {\n // Get the raw token from database\n $rawDbToken = $socialAccount->getProviderUserToken();\n $this->info(\"DB token (length: \" . strlen($rawDbToken) . \")\");\n $this->info(\"DB token preview: \" . substr($rawDbToken, 0, 20) . \"...\");\n\n // Check token expiry\n $expires = $socialAccount->expires;\n $this->info(\"Token expires at: \" . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));\n $this->info(\"Token is expired: \" . ($expires && $expires < time() ? 'YES' : 'NO'));\n\n // Test with the DB token directly\n $this->info(\"\\n--- Testing with DB token ---\");\n $config = new Configuration();\n $config->setAccessToken($rawDbToken);\n\n // Test 2a: Get Deals\n $this->testGetDeals($config);\n\n // Test 2b: Get Persons\n $this->testGetPersons($config);\n\n // Test 2c: Get Activities\n $this->testGetActivities($config);\n } catch (Exception $e) {\n $this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testCurrentSystemApproach(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n // Simulate how current system makes requests\n $token = $socialAccount->getProviderUserToken();\n $url = 'https://api.pipedrive.com/api/v2/deals';\n\n $headers = [\n 'Authorization' => 'Bearer ' . $token,\n 'Accept' => 'application/json',\n ];\n\n $client = new \\GuzzleHttp\\Client();\n $response = $client->get($url, [\n 'headers' => $headers,\n 'timeout' => 30,\n ]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $statusCode = $response->getStatusCode();\n $this->recordResult('Current System v2 API', $statusCode === 200, \"Status: {$statusCode} in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetDeals(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealsApi(null, $config);\n // Official SDK uses getDealsSummary instead of getDeals\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', true, \"Retrieved deals summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersons(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getPersons method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivities(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getActivities method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testFieldMetadata(SocialAccount $socialAccount): void\n {\n $this->info('Test 3: Field Metadata');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n // Test 3a: Get Deal Fields\n $this->testGetDealFields($config);\n\n // Test 3b: Get Person Fields\n $this->testGetPersonFields($config);\n\n // Test 3c: Get Activity Fields\n $this->testGetActivityFields($config);\n } catch (Exception $e) {\n $this->recordResult('Field Metadata', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testGetDealFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealFieldsApi(null, $config);\n $result = $apiInstance->getDealFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Deal Fields', true, \"Retrieved {$count} deal fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersonFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonFieldsApi(null, $config);\n $result = $apiInstance->getPersonFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Person Fields', true, \"Retrieved {$count} person fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivityFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivityFieldsApi(null, $config);\n $result = $apiInstance->getActivityFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Activity Fields', true, \"Retrieved {$count} activity fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testV2ApiSupport(SocialAccount $socialAccount): void\n {\n $this->info('Test 4: v2 API Support (Cursor-based Pagination)');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $startTime = microtime(true);\n\n // Test v2 deals endpoint with cursor pagination\n // Note: The official SDK may or may not have v2 API support\n // This test will determine if v2 is available\n try {\n $apiInstance = new DealsApi(null, $config);\n $updatedSince = Carbon::now()->subDays(30)->toRfc3339String();\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n\n // Check if response has cursor information (v2 indicator)\n // The response object doesn't have getAdditionalData method\n // Just test if the call works - v2 API support is limited in official SDK v1\n $this->recordResult('v2 API Support', false, \"v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);\n }\n } catch (Exception $e) {\n $this->recordResult('v2 API Support', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testErrorHandling(SocialAccount $socialAccount): void\n {\n $this->info('Test 5: Error Handling');\n\n try {\n // Test 5a: Invalid Token\n $this->testInvalidToken();\n\n // Test 5b: Non-existent Resource\n $this->testNonExistentResource($socialAccount);\n } catch (Exception $e) {\n $this->recordResult('Error Handling', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testInvalidToken(): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken('invalid_token');\n $client = new DealsApi(null, $config);\n\n $client->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function testNonExistentResource(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n $client = new DealsApi(null, $config);\n\n // Try to get a deal with a very high ID that likely doesn't exist\n // Note: Official SDK doesn't have a simple getDeal method, so skip this\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function recordResult(string $testName, bool $success, string $message, float $elapsed): void\n {\n $this->results[] = [\n 'test' => $testName,\n 'success' => $success,\n 'message' => $message,\n 'elapsed' => $elapsed,\n ];\n\n $status = $success ? '✅ PASS' : '❌ FAIL';\n $this->line(\" {$status} - {$testName}: {$message}\");\n }\n\n private function displayResults(): void\n {\n $this->newLine();\n $this->info('=== POC Test Results Summary ===');\n $this->newLine();\n\n $total = count($this->results);\n $passed = count(array_filter($this->results, fn ($r) => $r['success']));\n $failed = $total - $passed;\n\n $this->line(\"Total Tests: {$total}\");\n $this->line(\"Passed: {$passed}\");\n $this->line(\"Failed: {$failed}\");\n $this->newLine();\n\n $totalTime = array_sum(array_column($this->results, 'elapsed'));\n $this->line(\"Total Execution Time: {$totalTime}ms\");\n $this->newLine();\n\n if ($failed > 0) {\n $this->warn('Failed Tests:');\n foreach ($this->results as $result) {\n if (! $result['success']) {\n $this->line(\" - {$result['test']}: {$result['message']}\");\n }\n }\n $this->newLine();\n }\n\n $this->info('=== Detailed Results ===');\n foreach ($this->results as $result) {\n $status = $result['success'] ? '✅' : '❌';\n $this->line(\"{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}\");\n }\n\n $this->newLine();\n $this->info('=== Recommendations ===');\n\n if ($passed === $total) {\n $this->info('✅ All tests passed! The official SDK is viable for migration.');\n $this->info('Next steps: Proceed with full migration planning.');\n } else {\n $this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');\n $this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
5365831007722038384
|
61370652661533572
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
1
51
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Jiminny\Component\Encryption\EncryptedTokenManager;
use Jiminny\Models\SocialAccount;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Pipedrive\versions\v1\Api\ActivitiesApi;
use Pipedrive\versions\v1\Api\ActivityFieldsApi;
use Pipedrive\versions\v1\Api\DealFieldsApi;
use Pipedrive\versions\v1\Api\DealsApi;
use Pipedrive\versions\v1\Api\PersonFieldsApi;
use Pipedrive\versions\v1\Api\PersonsApi;
use Pipedrive\versions\v1\Configuration;
class TestPipedriveOfficialSdkCommand extends Command
{
protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';
protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';
private array $results = [];
private EncryptedTokenManager $tokenManager;
public function __construct(EncryptedTokenManager $tokenManager)
{
parent::__construct();
$this->tokenManager = $tokenManager;
}
public function handle(): int
{
$teamId = (int) $this->argument('teamId');
$this->info("Starting Pipedrive Official SDK POC for Team ID: {$teamId}");
$this->newLine();
try {
$this->runTests($teamId);
} catch (Exception $e) {
$this->error("POC failed with error: {$e->getMessage()}");
$this->error($e->getTraceAsString());
return 1;
}
$this->displayResults();
return 0;
}
private function runTests(int $teamId): void
{
$team = Team::find($teamId);
if (! $team) {
throw new Exception("Team with ID {$teamId} not found");
}
$socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)
->whereHas('sociable', function ($query) use ($team) {
$query->where('team_id', $team->id);
})
->orderByDesc('expires') // Get the most recently expiring token
->first();
if (! $socialAccount) {
throw new Exception("No Pipedrive social account found for team {$teamId}");
}
$this->info("Found Pipedrive account for team: {$team->name}");
$this->newLine();
// Test 1: OAuth Initialization
$this->testOAuthInitialization($socialAccount);
// Test 2: Basic CRUD Operations
$this->testBasicCrudOperations($socialAccount);
// Test 3: Field Metadata
$this->testFieldMetadata($socialAccount);
// Test 4: v2 API Support
$this->testV2ApiSupport($socialAccount);
// Test 5: Error Handling
$this->testErrorHandling($socialAccount);
}
private function testOAuthInitialization(SocialAccount $socialAccount): void
{
$this->info('Test 1: OAuth Initialization');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', true, "Client initialized successfully in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);
}
$this->newLine();
}
private function testBasicCrudOperations(SocialAccount $socialAccount): void
{
$this->info('Test 2: Basic CRUD Operations');
try {
// Get the raw token from database
$rawDbToken = $socialAccount->getProviderUserToken();
$this->info("DB token (length: " . strlen($rawDbToken) . ")");
$this->info("DB token preview: " . substr($rawDbToken, 0, 20) . "...");
// Check token expiry
$expires = $socialAccount->expires;
$this->info("Token expires at: " . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));
$this->info("Token is expired: " . ($expires && $expires < time() ? 'YES' : 'NO'));
// Test with the DB token directly
$this->info("\n--- Testing with DB token ---");
$config = new Configuration();
$config->setAccessToken($rawDbToken);
// Test 2a: Get Deals
$this->testGetDeals($config);
// Test 2b: Get Persons
$this->testGetPersons($config);
// Test 2c: Get Activities
$this->testGetActivities($config);
} catch (Exception $e) {
$this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testCurrentSystemApproach(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
// Simulate how current system makes requests
$token = $socialAccount->getProviderUserToken();
$url = 'https://api.pipedrive.com/api/v2/deals';
$headers = [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
];
$client = new \GuzzleHttp\Client();
$response = $client->get($url, [
'headers' => $headers,
'timeout' => 30,
]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$statusCode = $response->getStatusCode();
$this->recordResult('Current System v2 API', $statusCode === 200, "Status: {$statusCode} in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);
}
}
private function testGetDeals(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealsApi(null, $config);
// Official SDK uses getDealsSummary instead of getDeals
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', true, "Retrieved deals summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersons(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getPersons method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivities(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getActivities method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);
}
}
private function testFieldMetadata(SocialAccount $socialAccount): void
{
$this->info('Test 3: Field Metadata');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
// Test 3a: Get Deal Fields
$this->testGetDealFields($config);
// Test 3b: Get Person Fields
$this->testGetPersonFields($config);
// Test 3c: Get Activity Fields
$this->testGetActivityFields($config);
} catch (Exception $e) {
$this->recordResult('Field Metadata', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testGetDealFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealFieldsApi(null, $config);
$result = $apiInstance->getDealFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Deal Fields', true, "Retrieved {$count} deal fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersonFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new PersonFieldsApi(null, $config);
$result = $apiInstance->getPersonFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Person Fields', true, "Retrieved {$count} person fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivityFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new ActivityFieldsApi(null, $config);
$result = $apiInstance->getActivityFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Activity Fields', true, "Retrieved {$count} activity fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);
}
}
private function testV2ApiSupport(SocialAccount $socialAccount): void
{
$this->info('Test 4: v2 API Support (Cursor-based Pagination)');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$startTime = microtime(true);
// Test v2 deals endpoint with cursor pagination
// Note: The official SDK may or may not have v2 API support
// This test will determine if v2 is available
try {
$apiInstance = new DealsApi(null, $config);
$updatedSince = Carbon::now()->subDays(30)->toRfc3339String();
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
// Check if response has cursor information (v2 indicator)
// The response object doesn't have getAdditionalData method
// Just test if the call works - v2 API support is limited in official SDK v1
$this->recordResult('v2 API Support', false, "v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);
}
} catch (Exception $e) {
$this->recordResult('v2 API Support', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testErrorHandling(SocialAccount $socialAccount): void
{
$this->info('Test 5: Error Handling');
try {
// Test 5a: Invalid Token
$this->testInvalidToken();
// Test 5b: Non-existent Resource
$this->testNonExistentResource($socialAccount);
} catch (Exception $e) {
$this->recordResult('Error Handling', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testInvalidToken(): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken('invalid_token');
$client = new DealsApi(null, $config);
$client->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function testNonExistentResource(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
// Try to get a deal with a very high ID that likely doesn't exist
// Note: Official SDK doesn't have a simple getDeal method, so skip this
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function recordResult(string $testName, bool $success, string $message, float $elapsed): void
{
$this->results[] = [
'test' => $testName,
'success' => $success,
'message' => $message,
'elapsed' => $elapsed,
];
$status = $success ? '✅ PASS' : '❌ FAIL';
$this->line(" {$status} - {$testName}: {$message}");
}
private function displayResults(): void
{
$this->newLine();
$this->info('=== POC Test Results Summary ===');
$this->newLine();
$total = count($this->results);
$passed = count(array_filter($this->results, fn ($r) => $r['success']));
$failed = $total - $passed;
$this->line("Total Tests: {$total}");
$this->line("Passed: {$passed}");
$this->line("Failed: {$failed}");
$this->newLine();
$totalTime = array_sum(array_column($this->results, 'elapsed'));
$this->line("Total Execution Time: {$totalTime}ms");
$this->newLine();
if ($failed > 0) {
$this->warn('Failed Tests:');
foreach ($this->results as $result) {
if (! $result['success']) {
$this->line(" - {$result['test']}: {$result['message']}");
}
}
$this->newLine();
}
$this->info('=== Detailed Results ===');
foreach ($this->results as $result) {
$status = $result['success'] ? '✅' : '❌';
$this->line("{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}");
}
$this->newLine();
$this->info('=== Recommendations ===');
if ($passed === $total) {
$this->info('✅ All tests passed! The official SDK is viable for migration.');
$this->info('Next steps: Proceed with full migration planning.');
} else {
$this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');
$this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
49395
|
NULL
|
NULL
|
NULL
|
|
49397
|
1763
|
23
|
2026-05-18T06:40:16.495340+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086416495_m2.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
1
51
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Jiminny\Component\Encryption\EncryptedTokenManager;
use Jiminny\Models\SocialAccount;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Pipedrive\versions\v1\Api\ActivitiesApi;
use Pipedrive\versions\v1\Api\ActivityFieldsApi;
use Pipedrive\versions\v1\Api\DealFieldsApi;
use Pipedrive\versions\v1\Api\DealsApi;
use Pipedrive\versions\v1\Api\PersonFieldsApi;
use Pipedrive\versions\v1\Api\PersonsApi;
use Pipedrive\versions\v1\Configuration;
class TestPipedriveOfficialSdkCommand extends Command
{
protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';
protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';
private array $results = [];
private EncryptedTokenManager $tokenManager;
public function __construct(EncryptedTokenManager $tokenManager)
{
parent::__construct();
$this->tokenManager = $tokenManager;
}
public function handle(): int
{
$teamId = (int) $this->argument('teamId');
$this->info("Starting Pipedrive Official SDK POC for Team ID: {$teamId}");
$this->newLine();
try {
$this->runTests($teamId);
} catch (Exception $e) {
$this->error("POC failed with error: {$e->getMessage()}");
$this->error($e->getTraceAsString());
return 1;
}
$this->displayResults();
return 0;
}
private function runTests(int $teamId): void
{
$team = Team::find($teamId);
if (! $team) {
throw new Exception("Team with ID {$teamId} not found");
}
$socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)
->whereHas('sociable', function ($query) use ($team) {
$query->where('team_id', $team->id);
})
->orderByDesc('expires') // Get the most recently expiring token
->first();
if (! $socialAccount) {
throw new Exception("No Pipedrive social account found for team {$teamId}");
}
$this->info("Found Pipedrive account for team: {$team->name}");
$this->newLine();
// Test 1: OAuth Initialization
$this->testOAuthInitialization($socialAccount);
// Test 2: Basic CRUD Operations
$this->testBasicCrudOperations($socialAccount);
// Test 3: Field Metadata
$this->testFieldMetadata($socialAccount);
// Test 4: v2 API Support
$this->testV2ApiSupport($socialAccount);
// Test 5: Error Handling
$this->testErrorHandling($socialAccount);
}
private function testOAuthInitialization(SocialAccount $socialAccount): void
{
$this->info('Test 1: OAuth Initialization');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', true, "Client initialized successfully in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);
}
$this->newLine();
}
private function testBasicCrudOperations(SocialAccount $socialAccount): void
{
$this->info('Test 2: Basic CRUD Operations');
try {
// Get the raw token from database
$rawDbToken = $socialAccount->getProviderUserToken();
$this->info("DB token (length: " . strlen($rawDbToken) . ")");
$this->info("DB token preview: " . substr($rawDbToken, 0, 20) . "...");
// Check token expiry
$expires = $socialAccount->expires;
$this->info("Token expires at: " . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));
$this->info("Token is expired: " . ($expires && $expires < time() ? 'YES' : 'NO'));
// Test with the DB token directly
$this->info("\n--- Testing with DB token ---");
$config = new Configuration();
$config->setAccessToken($rawDbToken);
// Test 2a: Get Deals
$this->testGetDeals($config);
// Test 2b: Get Persons
$this->testGetPersons($config);
// Test 2c: Get Activities
$this->testGetActivities($config);
} catch (Exception $e) {
$this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testCurrentSystemApproach(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
// Simulate how current system makes requests
$token = $socialAccount->getProviderUserToken();
$url = 'https://api.pipedrive.com/api/v2/deals';
$headers = [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
];
$client = new \GuzzleHttp\Client();
$response = $client->get($url, [
'headers' => $headers,
'timeout' => 30,
]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$statusCode = $response->getStatusCode();
$this->recordResult('Current System v2 API', $statusCode === 200, "Status: {$statusCode} in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);
}
}
private function testGetDeals(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealsApi(null, $config);
// Official SDK uses getDealsSummary instead of getDeals
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', true, "Retrieved deals summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersons(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getPersons method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivities(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getActivities method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);
}
}
private function testFieldMetadata(SocialAccount $socialAccount): void
{
$this->info('Test 3: Field Metadata');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
// Test 3a: Get Deal Fields
$this->testGetDealFields($config);
// Test 3b: Get Person Fields
$this->testGetPersonFields($config);
// Test 3c: Get Activity Fields
$this->testGetActivityFields($config);
} catch (Exception $e) {
$this->recordResult('Field Metadata', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testGetDealFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealFieldsApi(null, $config);
$result = $apiInstance->getDealFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Deal Fields', true, "Retrieved {$count} deal fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersonFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new PersonFieldsApi(null, $config);
$result = $apiInstance->getPersonFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Person Fields', true, "Retrieved {$count} person fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivityFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new ActivityFieldsApi(null, $config);
$result = $apiInstance->getActivityFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Activity Fields', true, "Retrieved {$count} activity fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);
}
}
private function testV2ApiSupport(SocialAccount $socialAccount): void
{
$this->info('Test 4: v2 API Support (Cursor-based Pagination)');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$startTime = microtime(true);
// Test v2 deals endpoint with cursor pagination
// Note: The official SDK may or may not have v2 API support
// This test will determine if v2 is available
try {
$apiInstance = new DealsApi(null, $config);
$updatedSince = Carbon::now()->subDays(30)->toRfc3339String();
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
// Check if response has cursor information (v2 indicator)
// The response object doesn't have getAdditionalData method
// Just test if the call works - v2 API support is limited in official SDK v1
$this->recordResult('v2 API Support', false, "v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);
}
} catch (Exception $e) {
$this->recordResult('v2 API Support', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testErrorHandling(SocialAccount $socialAccount): void
{
$this->info('Test 5: Error Handling');
try {
// Test 5a: Invalid Token
$this->testInvalidToken();
// Test 5b: Non-existent Resource
$this->testNonExistentResource($socialAccount);
} catch (Exception $e) {
$this->recordResult('Error Handling', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testInvalidToken(): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken('invalid_token');
$client = new DealsApi(null, $config);
$client->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function testNonExistentResource(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
// Try to get a deal with a very high ID that likely doesn't exist
// Note: Official SDK doesn't have a simple getDeal method, so skip this
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function recordResult(string $testName, bool $success, string $message, float $elapsed): void
{
$this->results[] = [
'test' => $testName,
'success' => $success,
'message' => $message,
'elapsed' => $elapsed,
];
$status = $success ? '✅ PASS' : '❌ FAIL';
$this->line(" {$status} - {$testName}: {$message}");
}
private function displayResults(): void
{
$this->newLine();
$this->info('=== POC Test Results Summary ===');
$this->newLine();
$total = count($this->results);
$passed = count(array_filter($this->results, fn ($r) => $r['success']));
$failed = $total - $passed;
$this->line("Total Tests: {$total}");
$this->line("Passed: {$passed}");
$this->line("Failed: {$failed}");
$this->newLine();
$totalTime = array_sum(array_column($this->results, 'elapsed'));
$this->line("Total Execution Time: {$totalTime}ms");
$this->newLine();
if ($failed > 0) {
$this->warn('Failed Tests:');
foreach ($this->results as $result) {
if (! $result['success']) {
$this->line(" - {$result['test']}: {$result['message']}");
}
}
$this->newLine();
}
$this->info('=== Detailed Results ===');
foreach ($this->results as $result) {
$status = $result['success'] ? '✅' : '❌';
$this->line("{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}");
}
$this->newLine();
$this->info('=== Recommendations ===');
if ($passed === $total) {
$this->info('✅ All tests passed! The official SDK is viable for migration.');
$this->info('Next steps: Proceed with full migration planning.');
} else {
$this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');
$this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');
}
}
}
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.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"pipedrive-sdk-poc, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.05618351,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: pipedrive-sdk-poc","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.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","depth":4,"bounds":{"left":0.15724733,"top":0.2952913,"width":0.21476063,"height":0.7047087},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.57912236,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"51","depth":4,"bounds":{"left":0.5884308,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.6000665,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6090425,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"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.6163564,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"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\\Console\\Commands\\Crm;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Console\\Command;\nuse Jiminny\\Component\\Encryption\\EncryptedTokenManager;\nuse Jiminny\\Models\\SocialAccount;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Pipedrive\\versions\\v1\\Api\\ActivitiesApi;\nuse Pipedrive\\versions\\v1\\Api\\ActivityFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonsApi;\nuse Pipedrive\\versions\\v1\\Configuration;\n\nclass TestPipedriveOfficialSdkCommand extends Command\n{\n protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';\n\n protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';\n\n private array $results = [];\n\n private EncryptedTokenManager $tokenManager;\n\n public function __construct(EncryptedTokenManager $tokenManager)\n {\n parent::__construct();\n $this->tokenManager = $tokenManager;\n }\n\n public function handle(): int\n {\n $teamId = (int) $this->argument('teamId');\n\n $this->info(\"Starting Pipedrive Official SDK POC for Team ID: {$teamId}\");\n $this->newLine();\n\n try {\n $this->runTests($teamId);\n } catch (Exception $e) {\n $this->error(\"POC failed with error: {$e->getMessage()}\");\n $this->error($e->getTraceAsString());\n\n return 1;\n }\n\n $this->displayResults();\n\n return 0;\n }\n\n private function runTests(int $teamId): void\n {\n $team = Team::find($teamId);\n if (! $team) {\n throw new Exception(\"Team with ID {$teamId} not found\");\n }\n\n $socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)\n ->whereHas('sociable', function ($query) use ($team) {\n $query->where('team_id', $team->id);\n })\n ->orderByDesc('expires') // Get the most recently expiring token\n ->first();\n\n if (! $socialAccount) {\n throw new Exception(\"No Pipedrive social account found for team {$teamId}\");\n }\n\n $this->info(\"Found Pipedrive account for team: {$team->name}\");\n $this->newLine();\n\n // Test 1: OAuth Initialization\n $this->testOAuthInitialization($socialAccount);\n\n // Test 2: Basic CRUD Operations\n $this->testBasicCrudOperations($socialAccount);\n\n // Test 3: Field Metadata\n $this->testFieldMetadata($socialAccount);\n\n // Test 4: v2 API Support\n $this->testV2ApiSupport($socialAccount);\n\n // Test 5: Error Handling\n $this->testErrorHandling($socialAccount);\n }\n\n private function testOAuthInitialization(SocialAccount $socialAccount): void\n {\n $this->info('Test 1: OAuth Initialization');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $client = new DealsApi(null, $config);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', true, \"Client initialized successfully in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);\n }\n\n $this->newLine();\n }\n\n private function testBasicCrudOperations(SocialAccount $socialAccount): void\n {\n $this->info('Test 2: Basic CRUD Operations');\n\n try {\n // Get the raw token from database\n $rawDbToken = $socialAccount->getProviderUserToken();\n $this->info(\"DB token (length: \" . strlen($rawDbToken) . \")\");\n $this->info(\"DB token preview: \" . substr($rawDbToken, 0, 20) . \"...\");\n\n // Check token expiry\n $expires = $socialAccount->expires;\n $this->info(\"Token expires at: \" . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));\n $this->info(\"Token is expired: \" . ($expires && $expires < time() ? 'YES' : 'NO'));\n\n // Test with the DB token directly\n $this->info(\"\\n--- Testing with DB token ---\");\n $config = new Configuration();\n $config->setAccessToken($rawDbToken);\n\n // Test 2a: Get Deals\n $this->testGetDeals($config);\n\n // Test 2b: Get Persons\n $this->testGetPersons($config);\n\n // Test 2c: Get Activities\n $this->testGetActivities($config);\n } catch (Exception $e) {\n $this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testCurrentSystemApproach(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n // Simulate how current system makes requests\n $token = $socialAccount->getProviderUserToken();\n $url = 'https://api.pipedrive.com/api/v2/deals';\n\n $headers = [\n 'Authorization' => 'Bearer ' . $token,\n 'Accept' => 'application/json',\n ];\n\n $client = new \\GuzzleHttp\\Client();\n $response = $client->get($url, [\n 'headers' => $headers,\n 'timeout' => 30,\n ]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $statusCode = $response->getStatusCode();\n $this->recordResult('Current System v2 API', $statusCode === 200, \"Status: {$statusCode} in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetDeals(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealsApi(null, $config);\n // Official SDK uses getDealsSummary instead of getDeals\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', true, \"Retrieved deals summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersons(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getPersons method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivities(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getActivities method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testFieldMetadata(SocialAccount $socialAccount): void\n {\n $this->info('Test 3: Field Metadata');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n // Test 3a: Get Deal Fields\n $this->testGetDealFields($config);\n\n // Test 3b: Get Person Fields\n $this->testGetPersonFields($config);\n\n // Test 3c: Get Activity Fields\n $this->testGetActivityFields($config);\n } catch (Exception $e) {\n $this->recordResult('Field Metadata', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testGetDealFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealFieldsApi(null, $config);\n $result = $apiInstance->getDealFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Deal Fields', true, \"Retrieved {$count} deal fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersonFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonFieldsApi(null, $config);\n $result = $apiInstance->getPersonFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Person Fields', true, \"Retrieved {$count} person fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivityFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivityFieldsApi(null, $config);\n $result = $apiInstance->getActivityFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Activity Fields', true, \"Retrieved {$count} activity fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testV2ApiSupport(SocialAccount $socialAccount): void\n {\n $this->info('Test 4: v2 API Support (Cursor-based Pagination)');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $startTime = microtime(true);\n\n // Test v2 deals endpoint with cursor pagination\n // Note: The official SDK may or may not have v2 API support\n // This test will determine if v2 is available\n try {\n $apiInstance = new DealsApi(null, $config);\n $updatedSince = Carbon::now()->subDays(30)->toRfc3339String();\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n\n // Check if response has cursor information (v2 indicator)\n // The response object doesn't have getAdditionalData method\n // Just test if the call works - v2 API support is limited in official SDK v1\n $this->recordResult('v2 API Support', false, \"v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);\n }\n } catch (Exception $e) {\n $this->recordResult('v2 API Support', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testErrorHandling(SocialAccount $socialAccount): void\n {\n $this->info('Test 5: Error Handling');\n\n try {\n // Test 5a: Invalid Token\n $this->testInvalidToken();\n\n // Test 5b: Non-existent Resource\n $this->testNonExistentResource($socialAccount);\n } catch (Exception $e) {\n $this->recordResult('Error Handling', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testInvalidToken(): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken('invalid_token');\n $client = new DealsApi(null, $config);\n\n $client->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function testNonExistentResource(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n $client = new DealsApi(null, $config);\n\n // Try to get a deal with a very high ID that likely doesn't exist\n // Note: Official SDK doesn't have a simple getDeal method, so skip this\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function recordResult(string $testName, bool $success, string $message, float $elapsed): void\n {\n $this->results[] = [\n 'test' => $testName,\n 'success' => $success,\n 'message' => $message,\n 'elapsed' => $elapsed,\n ];\n\n $status = $success ? '✅ PASS' : '❌ FAIL';\n $this->line(\" {$status} - {$testName}: {$message}\");\n }\n\n private function displayResults(): void\n {\n $this->newLine();\n $this->info('=== POC Test Results Summary ===');\n $this->newLine();\n\n $total = count($this->results);\n $passed = count(array_filter($this->results, fn ($r) => $r['success']));\n $failed = $total - $passed;\n\n $this->line(\"Total Tests: {$total}\");\n $this->line(\"Passed: {$passed}\");\n $this->line(\"Failed: {$failed}\");\n $this->newLine();\n\n $totalTime = array_sum(array_column($this->results, 'elapsed'));\n $this->line(\"Total Execution Time: {$totalTime}ms\");\n $this->newLine();\n\n if ($failed > 0) {\n $this->warn('Failed Tests:');\n foreach ($this->results as $result) {\n if (! $result['success']) {\n $this->line(\" - {$result['test']}: {$result['message']}\");\n }\n }\n $this->newLine();\n }\n\n $this->info('=== Detailed Results ===');\n foreach ($this->results as $result) {\n $status = $result['success'] ? '✅' : '❌';\n $this->line(\"{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}\");\n }\n\n $this->newLine();\n $this->info('=== Recommendations ===');\n\n if ($passed === $total) {\n $this->info('✅ All tests passed! The official SDK is viable for migration.');\n $this->info('Next steps: Proceed with full migration planning.');\n } else {\n $this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');\n $this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Console\\Commands\\Crm;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Console\\Command;\nuse Jiminny\\Component\\Encryption\\EncryptedTokenManager;\nuse Jiminny\\Models\\SocialAccount;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Pipedrive\\versions\\v1\\Api\\ActivitiesApi;\nuse Pipedrive\\versions\\v1\\Api\\ActivityFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonsApi;\nuse Pipedrive\\versions\\v1\\Configuration;\n\nclass TestPipedriveOfficialSdkCommand extends Command\n{\n protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';\n\n protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';\n\n private array $results = [];\n\n private EncryptedTokenManager $tokenManager;\n\n public function __construct(EncryptedTokenManager $tokenManager)\n {\n parent::__construct();\n $this->tokenManager = $tokenManager;\n }\n\n public function handle(): int\n {\n $teamId = (int) $this->argument('teamId');\n\n $this->info(\"Starting Pipedrive Official SDK POC for Team ID: {$teamId}\");\n $this->newLine();\n\n try {\n $this->runTests($teamId);\n } catch (Exception $e) {\n $this->error(\"POC failed with error: {$e->getMessage()}\");\n $this->error($e->getTraceAsString());\n\n return 1;\n }\n\n $this->displayResults();\n\n return 0;\n }\n\n private function runTests(int $teamId): void\n {\n $team = Team::find($teamId);\n if (! $team) {\n throw new Exception(\"Team with ID {$teamId} not found\");\n }\n\n $socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)\n ->whereHas('sociable', function ($query) use ($team) {\n $query->where('team_id', $team->id);\n })\n ->orderByDesc('expires') // Get the most recently expiring token\n ->first();\n\n if (! $socialAccount) {\n throw new Exception(\"No Pipedrive social account found for team {$teamId}\");\n }\n\n $this->info(\"Found Pipedrive account for team: {$team->name}\");\n $this->newLine();\n\n // Test 1: OAuth Initialization\n $this->testOAuthInitialization($socialAccount);\n\n // Test 2: Basic CRUD Operations\n $this->testBasicCrudOperations($socialAccount);\n\n // Test 3: Field Metadata\n $this->testFieldMetadata($socialAccount);\n\n // Test 4: v2 API Support\n $this->testV2ApiSupport($socialAccount);\n\n // Test 5: Error Handling\n $this->testErrorHandling($socialAccount);\n }\n\n private function testOAuthInitialization(SocialAccount $socialAccount): void\n {\n $this->info('Test 1: OAuth Initialization');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $client = new DealsApi(null, $config);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', true, \"Client initialized successfully in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);\n }\n\n $this->newLine();\n }\n\n private function testBasicCrudOperations(SocialAccount $socialAccount): void\n {\n $this->info('Test 2: Basic CRUD Operations');\n\n try {\n // Get the raw token from database\n $rawDbToken = $socialAccount->getProviderUserToken();\n $this->info(\"DB token (length: \" . strlen($rawDbToken) . \")\");\n $this->info(\"DB token preview: \" . substr($rawDbToken, 0, 20) . \"...\");\n\n // Check token expiry\n $expires = $socialAccount->expires;\n $this->info(\"Token expires at: \" . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));\n $this->info(\"Token is expired: \" . ($expires && $expires < time() ? 'YES' : 'NO'));\n\n // Test with the DB token directly\n $this->info(\"\\n--- Testing with DB token ---\");\n $config = new Configuration();\n $config->setAccessToken($rawDbToken);\n\n // Test 2a: Get Deals\n $this->testGetDeals($config);\n\n // Test 2b: Get Persons\n $this->testGetPersons($config);\n\n // Test 2c: Get Activities\n $this->testGetActivities($config);\n } catch (Exception $e) {\n $this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testCurrentSystemApproach(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n // Simulate how current system makes requests\n $token = $socialAccount->getProviderUserToken();\n $url = 'https://api.pipedrive.com/api/v2/deals';\n\n $headers = [\n 'Authorization' => 'Bearer ' . $token,\n 'Accept' => 'application/json',\n ];\n\n $client = new \\GuzzleHttp\\Client();\n $response = $client->get($url, [\n 'headers' => $headers,\n 'timeout' => 30,\n ]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $statusCode = $response->getStatusCode();\n $this->recordResult('Current System v2 API', $statusCode === 200, \"Status: {$statusCode} in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetDeals(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealsApi(null, $config);\n // Official SDK uses getDealsSummary instead of getDeals\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', true, \"Retrieved deals summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersons(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getPersons method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivities(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getActivities method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testFieldMetadata(SocialAccount $socialAccount): void\n {\n $this->info('Test 3: Field Metadata');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n // Test 3a: Get Deal Fields\n $this->testGetDealFields($config);\n\n // Test 3b: Get Person Fields\n $this->testGetPersonFields($config);\n\n // Test 3c: Get Activity Fields\n $this->testGetActivityFields($config);\n } catch (Exception $e) {\n $this->recordResult('Field Metadata', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testGetDealFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealFieldsApi(null, $config);\n $result = $apiInstance->getDealFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Deal Fields', true, \"Retrieved {$count} deal fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersonFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonFieldsApi(null, $config);\n $result = $apiInstance->getPersonFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Person Fields', true, \"Retrieved {$count} person fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivityFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivityFieldsApi(null, $config);\n $result = $apiInstance->getActivityFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Activity Fields', true, \"Retrieved {$count} activity fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testV2ApiSupport(SocialAccount $socialAccount): void\n {\n $this->info('Test 4: v2 API Support (Cursor-based Pagination)');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $startTime = microtime(true);\n\n // Test v2 deals endpoint with cursor pagination\n // Note: The official SDK may or may not have v2 API support\n // This test will determine if v2 is available\n try {\n $apiInstance = new DealsApi(null, $config);\n $updatedSince = Carbon::now()->subDays(30)->toRfc3339String();\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n\n // Check if response has cursor information (v2 indicator)\n // The response object doesn't have getAdditionalData method\n // Just test if the call works - v2 API support is limited in official SDK v1\n $this->recordResult('v2 API Support', false, \"v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);\n }\n } catch (Exception $e) {\n $this->recordResult('v2 API Support', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testErrorHandling(SocialAccount $socialAccount): void\n {\n $this->info('Test 5: Error Handling');\n\n try {\n // Test 5a: Invalid Token\n $this->testInvalidToken();\n\n // Test 5b: Non-existent Resource\n $this->testNonExistentResource($socialAccount);\n } catch (Exception $e) {\n $this->recordResult('Error Handling', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testInvalidToken(): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken('invalid_token');\n $client = new DealsApi(null, $config);\n\n $client->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function testNonExistentResource(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n $client = new DealsApi(null, $config);\n\n // Try to get a deal with a very high ID that likely doesn't exist\n // Note: Official SDK doesn't have a simple getDeal method, so skip this\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function recordResult(string $testName, bool $success, string $message, float $elapsed): void\n {\n $this->results[] = [\n 'test' => $testName,\n 'success' => $success,\n 'message' => $message,\n 'elapsed' => $elapsed,\n ];\n\n $status = $success ? '✅ PASS' : '❌ FAIL';\n $this->line(\" {$status} - {$testName}: {$message}\");\n }\n\n private function displayResults(): void\n {\n $this->newLine();\n $this->info('=== POC Test Results Summary ===');\n $this->newLine();\n\n $total = count($this->results);\n $passed = count(array_filter($this->results, fn ($r) => $r['success']));\n $failed = $total - $passed;\n\n $this->line(\"Total Tests: {$total}\");\n $this->line(\"Passed: {$passed}\");\n $this->line(\"Failed: {$failed}\");\n $this->newLine();\n\n $totalTime = array_sum(array_column($this->results, 'elapsed'));\n $this->line(\"Total Execution Time: {$totalTime}ms\");\n $this->newLine();\n\n if ($failed > 0) {\n $this->warn('Failed Tests:');\n foreach ($this->results as $result) {\n if (! $result['success']) {\n $this->line(\" - {$result['test']}: {$result['message']}\");\n }\n }\n $this->newLine();\n }\n\n $this->info('=== Detailed Results ===');\n foreach ($this->results as $result) {\n $status = $result['success'] ? '✅' : '❌';\n $this->line(\"{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}\");\n }\n\n $this->newLine();\n $this->info('=== Recommendations ===');\n\n if ($passed === $total) {\n $this->info('✅ All tests passed! The official SDK is viable for migration.');\n $this->info('Next steps: Proceed with full migration planning.');\n } else {\n $this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');\n $this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
5365831007722038384
|
61370652661533572
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
1
51
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Jiminny\Component\Encryption\EncryptedTokenManager;
use Jiminny\Models\SocialAccount;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Pipedrive\versions\v1\Api\ActivitiesApi;
use Pipedrive\versions\v1\Api\ActivityFieldsApi;
use Pipedrive\versions\v1\Api\DealFieldsApi;
use Pipedrive\versions\v1\Api\DealsApi;
use Pipedrive\versions\v1\Api\PersonFieldsApi;
use Pipedrive\versions\v1\Api\PersonsApi;
use Pipedrive\versions\v1\Configuration;
class TestPipedriveOfficialSdkCommand extends Command
{
protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';
protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';
private array $results = [];
private EncryptedTokenManager $tokenManager;
public function __construct(EncryptedTokenManager $tokenManager)
{
parent::__construct();
$this->tokenManager = $tokenManager;
}
public function handle(): int
{
$teamId = (int) $this->argument('teamId');
$this->info("Starting Pipedrive Official SDK POC for Team ID: {$teamId}");
$this->newLine();
try {
$this->runTests($teamId);
} catch (Exception $e) {
$this->error("POC failed with error: {$e->getMessage()}");
$this->error($e->getTraceAsString());
return 1;
}
$this->displayResults();
return 0;
}
private function runTests(int $teamId): void
{
$team = Team::find($teamId);
if (! $team) {
throw new Exception("Team with ID {$teamId} not found");
}
$socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)
->whereHas('sociable', function ($query) use ($team) {
$query->where('team_id', $team->id);
})
->orderByDesc('expires') // Get the most recently expiring token
->first();
if (! $socialAccount) {
throw new Exception("No Pipedrive social account found for team {$teamId}");
}
$this->info("Found Pipedrive account for team: {$team->name}");
$this->newLine();
// Test 1: OAuth Initialization
$this->testOAuthInitialization($socialAccount);
// Test 2: Basic CRUD Operations
$this->testBasicCrudOperations($socialAccount);
// Test 3: Field Metadata
$this->testFieldMetadata($socialAccount);
// Test 4: v2 API Support
$this->testV2ApiSupport($socialAccount);
// Test 5: Error Handling
$this->testErrorHandling($socialAccount);
}
private function testOAuthInitialization(SocialAccount $socialAccount): void
{
$this->info('Test 1: OAuth Initialization');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', true, "Client initialized successfully in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);
}
$this->newLine();
}
private function testBasicCrudOperations(SocialAccount $socialAccount): void
{
$this->info('Test 2: Basic CRUD Operations');
try {
// Get the raw token from database
$rawDbToken = $socialAccount->getProviderUserToken();
$this->info("DB token (length: " . strlen($rawDbToken) . ")");
$this->info("DB token preview: " . substr($rawDbToken, 0, 20) . "...");
// Check token expiry
$expires = $socialAccount->expires;
$this->info("Token expires at: " . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));
$this->info("Token is expired: " . ($expires && $expires < time() ? 'YES' : 'NO'));
// Test with the DB token directly
$this->info("\n--- Testing with DB token ---");
$config = new Configuration();
$config->setAccessToken($rawDbToken);
// Test 2a: Get Deals
$this->testGetDeals($config);
// Test 2b: Get Persons
$this->testGetPersons($config);
// Test 2c: Get Activities
$this->testGetActivities($config);
} catch (Exception $e) {
$this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testCurrentSystemApproach(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
// Simulate how current system makes requests
$token = $socialAccount->getProviderUserToken();
$url = 'https://api.pipedrive.com/api/v2/deals';
$headers = [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
];
$client = new \GuzzleHttp\Client();
$response = $client->get($url, [
'headers' => $headers,
'timeout' => 30,
]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$statusCode = $response->getStatusCode();
$this->recordResult('Current System v2 API', $statusCode === 200, "Status: {$statusCode} in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);
}
}
private function testGetDeals(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealsApi(null, $config);
// Official SDK uses getDealsSummary instead of getDeals
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', true, "Retrieved deals summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersons(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getPersons method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivities(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getActivities method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);
}
}
private function testFieldMetadata(SocialAccount $socialAccount): void
{
$this->info('Test 3: Field Metadata');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
// Test 3a: Get Deal Fields
$this->testGetDealFields($config);
// Test 3b: Get Person Fields
$this->testGetPersonFields($config);
// Test 3c: Get Activity Fields
$this->testGetActivityFields($config);
} catch (Exception $e) {
$this->recordResult('Field Metadata', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testGetDealFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealFieldsApi(null, $config);
$result = $apiInstance->getDealFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Deal Fields', true, "Retrieved {$count} deal fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersonFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new PersonFieldsApi(null, $config);
$result = $apiInstance->getPersonFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Person Fields', true, "Retrieved {$count} person fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivityFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new ActivityFieldsApi(null, $config);
$result = $apiInstance->getActivityFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Activity Fields', true, "Retrieved {$count} activity fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);
}
}
private function testV2ApiSupport(SocialAccount $socialAccount): void
{
$this->info('Test 4: v2 API Support (Cursor-based Pagination)');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$startTime = microtime(true);
// Test v2 deals endpoint with cursor pagination
// Note: The official SDK may or may not have v2 API support
// This test will determine if v2 is available
try {
$apiInstance = new DealsApi(null, $config);
$updatedSince = Carbon::now()->subDays(30)->toRfc3339String();
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
// Check if response has cursor information (v2 indicator)
// The response object doesn't have getAdditionalData method
// Just test if the call works - v2 API support is limited in official SDK v1
$this->recordResult('v2 API Support', false, "v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);
}
} catch (Exception $e) {
$this->recordResult('v2 API Support', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testErrorHandling(SocialAccount $socialAccount): void
{
$this->info('Test 5: Error Handling');
try {
// Test 5a: Invalid Token
$this->testInvalidToken();
// Test 5b: Non-existent Resource
$this->testNonExistentResource($socialAccount);
} catch (Exception $e) {
$this->recordResult('Error Handling', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testInvalidToken(): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken('invalid_token');
$client = new DealsApi(null, $config);
$client->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function testNonExistentResource(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
// Try to get a deal with a very high ID that likely doesn't exist
// Note: Official SDK doesn't have a simple getDeal method, so skip this
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function recordResult(string $testName, bool $success, string $message, float $elapsed): void
{
$this->results[] = [
'test' => $testName,
'success' => $success,
'message' => $message,
'elapsed' => $elapsed,
];
$status = $success ? '✅ PASS' : '❌ FAIL';
$this->line(" {$status} - {$testName}: {$message}");
}
private function displayResults(): void
{
$this->newLine();
$this->info('=== POC Test Results Summary ===');
$this->newLine();
$total = count($this->results);
$passed = count(array_filter($this->results, fn ($r) => $r['success']));
$failed = $total - $passed;
$this->line("Total Tests: {$total}");
$this->line("Passed: {$passed}");
$this->line("Failed: {$failed}");
$this->newLine();
$totalTime = array_sum(array_column($this->results, 'elapsed'));
$this->line("Total Execution Time: {$totalTime}ms");
$this->newLine();
if ($failed > 0) {
$this->warn('Failed Tests:');
foreach ($this->results as $result) {
if (! $result['success']) {
$this->line(" - {$result['test']}: {$result['message']}");
}
}
$this->newLine();
}
$this->info('=== Detailed Results ===');
foreach ($this->results as $result) {
$status = $result['success'] ? '✅' : '❌';
$this->line("{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}");
}
$this->newLine();
$this->info('=== Recommendations ===');
if ($passed === $total) {
$this->info('✅ All tests passed! The official SDK is viable for migration.');
$this->info('Next steps: Proceed with full migration planning.');
} else {
$this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');
$this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
49394
|
NULL
|
NULL
|
NULL
|
|
49398
|
1762
|
22
|
2026-05-18T06:40:27.601862+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086427601_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
1
51
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Jiminny\Component\Encryption\EncryptedTokenManager;
use Jiminny\Models\SocialAccount;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Pipedrive\versions\v1\Api\ActivitiesApi;
use Pipedrive\versions\v1\Api\ActivityFieldsApi;
use Pipedrive\versions\v1\Api\DealFieldsApi;
use Pipedrive\versions\v1\Api\DealsApi;
use Pipedrive\versions\v1\Api\PersonFieldsApi;
use Pipedrive\versions\v1\Api\PersonsApi;
use Pipedrive\versions\v1\Configuration;
class TestPipedriveOfficialSdkCommand extends Command
{
protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';
protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';
private array $results = [];
private EncryptedTokenManager $tokenManager;
public function __construct(EncryptedTokenManager $tokenManager)
{
parent::__construct();
$this->tokenManager = $tokenManager;
}
public function handle(): int
{
$teamId = (int) $this->argument('teamId');
$this->info("Starting Pipedrive Official SDK POC for Team ID: {$teamId}");
$this->newLine();
try {
$this->runTests($teamId);
} catch (Exception $e) {
$this->error("POC failed with error: {$e->getMessage()}");
$this->error($e->getTraceAsString());
return 1;
}
$this->displayResults();
return 0;
}
private function runTests(int $teamId): void
{
$team = Team::find($teamId);
if (! $team) {
throw new Exception("Team with ID {$teamId} not found");
}
$socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)
->whereHas('sociable', function ($query) use ($team) {
$query->where('team_id', $team->id);
})
->orderByDesc('expires') // Get the most recently expiring token
->first();
if (! $socialAccount) {
throw new Exception("No Pipedrive social account found for team {$teamId}");
}
$this->info("Found Pipedrive account for team: {$team->name}");
$this->newLine();
// Test 1: OAuth Initialization
$this->testOAuthInitialization($socialAccount);
// Test 2: Basic CRUD Operations
$this->testBasicCrudOperations($socialAccount);
// Test 3: Field Metadata
$this->testFieldMetadata($socialAccount);
// Test 4: v2 API Support
$this->testV2ApiSupport($socialAccount);
// Test 5: Error Handling
$this->testErrorHandling($socialAccount);
}
private function testOAuthInitialization(SocialAccount $socialAccount): void
{
$this->info('Test 1: OAuth Initialization');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', true, "Client initialized successfully in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);
}
$this->newLine();
}
private function testBasicCrudOperations(SocialAccount $socialAccount): void
{
$this->info('Test 2: Basic CRUD Operations');
try {
// Get the raw token from database
$rawDbToken = $socialAccount->getProviderUserToken();
$this->info("DB token (length: " . strlen($rawDbToken) . ")");
$this->info("DB token preview: " . substr($rawDbToken, 0, 20) . "...");
// Check token expiry
$expires = $socialAccount->expires;
$this->info("Token expires at: " . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));
$this->info("Token is expired: " . ($expires && $expires < time() ? 'YES' : 'NO'));
// Test with the DB token directly
$this->info("\n--- Testing with DB token ---");
$config = new Configuration();
$config->setAccessToken($rawDbToken);
// Test 2a: Get Deals
$this->testGetDeals($config);
// Test 2b: Get Persons
$this->testGetPersons($config);
// Test 2c: Get Activities
$this->testGetActivities($config);
} catch (Exception $e) {
$this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testCurrentSystemApproach(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
// Simulate how current system makes requests
$token = $socialAccount->getProviderUserToken();
$url = 'https://api.pipedrive.com/api/v2/deals';
$headers = [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
];
$client = new \GuzzleHttp\Client();
$response = $client->get($url, [
'headers' => $headers,
'timeout' => 30,
]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$statusCode = $response->getStatusCode();
$this->recordResult('Current System v2 API', $statusCode === 200, "Status: {$statusCode} in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);
}
}
private function testGetDeals(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealsApi(null, $config);
// Official SDK uses getDealsSummary instead of getDeals
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', true, "Retrieved deals summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersons(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getPersons method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivities(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getActivities method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);
}
}
private function testFieldMetadata(SocialAccount $socialAccount): void
{
$this->info('Test 3: Field Metadata');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
// Test 3a: Get Deal Fields
$this->testGetDealFields($config);
// Test 3b: Get Person Fields
$this->testGetPersonFields($config);
// Test 3c: Get Activity Fields
$this->testGetActivityFields($config);
} catch (Exception $e) {
$this->recordResult('Field Metadata', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testGetDealFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealFieldsApi(null, $config);
$result = $apiInstance->getDealFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Deal Fields', true, "Retrieved {$count} deal fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersonFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new PersonFieldsApi(null, $config);
$result = $apiInstance->getPersonFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Person Fields', true, "Retrieved {$count} person fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivityFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new ActivityFieldsApi(null, $config);
$result = $apiInstance->getActivityFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Activity Fields', true, "Retrieved {$count} activity fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);
}
}
private function testV2ApiSupport(SocialAccount $socialAccount): void
{
$this->info('Test 4: v2 API Support (Cursor-based Pagination)');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$startTime = microtime(true);
// Test v2 deals endpoint with cursor pagination
// Note: The official SDK may or may not have v2 API support
// This test will determine if v2 is available
try {
$apiInstance = new DealsApi(null, $config);
$updatedSince = Carbon::now()->subDays(30)->toRfc3339String();
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
// Check if response has cursor information (v2 indicator)
// The response object doesn't have getAdditionalData method
// Just test if the call works - v2 API support is limited in official SDK v1
$this->recordResult('v2 API Support', false, "v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);
}
} catch (Exception $e) {
$this->recordResult('v2 API Support', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testErrorHandling(SocialAccount $socialAccount): void
{
$this->info('Test 5: Error Handling');
try {
// Test 5a: Invalid Token
$this->testInvalidToken();
// Test 5b: Non-existent Resource
$this->testNonExistentResource($socialAccount);
} catch (Exception $e) {
$this->recordResult('Error Handling', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testInvalidToken(): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken('invalid_token');
$client = new DealsApi(null, $config);
$client->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function testNonExistentResource(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
// Try to get a deal with a very high ID that likely doesn't exist
// Note: Official SDK doesn't have a simple getDeal method, so skip this
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function recordResult(string $testName, bool $success, string $message, float $elapsed): void
{
$this->results[] = [
'test' => $testName,
'success' => $success,
'message' => $message,
'elapsed' => $elapsed,
];
$status = $success ? '✅ PASS' : '❌ FAIL';
$this->line(" {$status} - {$testName}: {$message}");
}
private function displayResults(): void
{
$this->newLine();
$this->info('=== POC Test Results Summary ===');
$this->newLine();
$total = count($this->results);
$passed = count(array_filter($this->results, fn ($r) => $r['success']));
$failed = $total - $passed;
$this->line("Total Tests: {$total}");
$this->line("Passed: {$passed}");
$this->line("Failed: {$failed}");
$this->newLine();
$totalTime = array_sum(array_column($this->results, 'elapsed'));
$this->line("Total Execution Time: {$totalTime}ms");
$this->newLine();
if ($failed > 0) {
$this->warn('Failed Tests:');
foreach ($this->results as $result) {
if (! $result['success']) {
$this->line(" - {$result['test']}: {$result['message']}");
}
}
$this->newLine();
}
$this->info('=== Detailed Results ===');
foreach ($this->results as $result) {
$status = $result['success'] ? '✅' : '❌';
$this->line("{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}");
}
$this->newLine();
$this->info('=== Recommendations ===');
if ($passed === $total) {
$this->info('✅ All tests passed! The official SDK is viable for migration.');
$this->info('Next steps: Proceed with full migration planning.');
} else {
$this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');
$this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"pipedrive-sdk-poc, menu","depth":5,"on_screen":true,"help_text":"Git Branch: pipedrive-sdk-poc","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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","role_description":"text entry area","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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"51","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"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\\Console\\Commands\\Crm;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Console\\Command;\nuse Jiminny\\Component\\Encryption\\EncryptedTokenManager;\nuse Jiminny\\Models\\SocialAccount;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Pipedrive\\versions\\v1\\Api\\ActivitiesApi;\nuse Pipedrive\\versions\\v1\\Api\\ActivityFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonsApi;\nuse Pipedrive\\versions\\v1\\Configuration;\n\nclass TestPipedriveOfficialSdkCommand extends Command\n{\n protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';\n\n protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';\n\n private array $results = [];\n\n private EncryptedTokenManager $tokenManager;\n\n public function __construct(EncryptedTokenManager $tokenManager)\n {\n parent::__construct();\n $this->tokenManager = $tokenManager;\n }\n\n public function handle(): int\n {\n $teamId = (int) $this->argument('teamId');\n\n $this->info(\"Starting Pipedrive Official SDK POC for Team ID: {$teamId}\");\n $this->newLine();\n\n try {\n $this->runTests($teamId);\n } catch (Exception $e) {\n $this->error(\"POC failed with error: {$e->getMessage()}\");\n $this->error($e->getTraceAsString());\n\n return 1;\n }\n\n $this->displayResults();\n\n return 0;\n }\n\n private function runTests(int $teamId): void\n {\n $team = Team::find($teamId);\n if (! $team) {\n throw new Exception(\"Team with ID {$teamId} not found\");\n }\n\n $socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)\n ->whereHas('sociable', function ($query) use ($team) {\n $query->where('team_id', $team->id);\n })\n ->orderByDesc('expires') // Get the most recently expiring token\n ->first();\n\n if (! $socialAccount) {\n throw new Exception(\"No Pipedrive social account found for team {$teamId}\");\n }\n\n $this->info(\"Found Pipedrive account for team: {$team->name}\");\n $this->newLine();\n\n // Test 1: OAuth Initialization\n $this->testOAuthInitialization($socialAccount);\n\n // Test 2: Basic CRUD Operations\n $this->testBasicCrudOperations($socialAccount);\n\n // Test 3: Field Metadata\n $this->testFieldMetadata($socialAccount);\n\n // Test 4: v2 API Support\n $this->testV2ApiSupport($socialAccount);\n\n // Test 5: Error Handling\n $this->testErrorHandling($socialAccount);\n }\n\n private function testOAuthInitialization(SocialAccount $socialAccount): void\n {\n $this->info('Test 1: OAuth Initialization');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $client = new DealsApi(null, $config);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', true, \"Client initialized successfully in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);\n }\n\n $this->newLine();\n }\n\n private function testBasicCrudOperations(SocialAccount $socialAccount): void\n {\n $this->info('Test 2: Basic CRUD Operations');\n\n try {\n // Get the raw token from database\n $rawDbToken = $socialAccount->getProviderUserToken();\n $this->info(\"DB token (length: \" . strlen($rawDbToken) . \")\");\n $this->info(\"DB token preview: \" . substr($rawDbToken, 0, 20) . \"...\");\n\n // Check token expiry\n $expires = $socialAccount->expires;\n $this->info(\"Token expires at: \" . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));\n $this->info(\"Token is expired: \" . ($expires && $expires < time() ? 'YES' : 'NO'));\n\n // Test with the DB token directly\n $this->info(\"\\n--- Testing with DB token ---\");\n $config = new Configuration();\n $config->setAccessToken($rawDbToken);\n\n // Test 2a: Get Deals\n $this->testGetDeals($config);\n\n // Test 2b: Get Persons\n $this->testGetPersons($config);\n\n // Test 2c: Get Activities\n $this->testGetActivities($config);\n } catch (Exception $e) {\n $this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testCurrentSystemApproach(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n // Simulate how current system makes requests\n $token = $socialAccount->getProviderUserToken();\n $url = 'https://api.pipedrive.com/api/v2/deals';\n\n $headers = [\n 'Authorization' => 'Bearer ' . $token,\n 'Accept' => 'application/json',\n ];\n\n $client = new \\GuzzleHttp\\Client();\n $response = $client->get($url, [\n 'headers' => $headers,\n 'timeout' => 30,\n ]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $statusCode = $response->getStatusCode();\n $this->recordResult('Current System v2 API', $statusCode === 200, \"Status: {$statusCode} in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetDeals(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealsApi(null, $config);\n // Official SDK uses getDealsSummary instead of getDeals\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', true, \"Retrieved deals summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersons(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getPersons method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivities(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getActivities method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testFieldMetadata(SocialAccount $socialAccount): void\n {\n $this->info('Test 3: Field Metadata');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n // Test 3a: Get Deal Fields\n $this->testGetDealFields($config);\n\n // Test 3b: Get Person Fields\n $this->testGetPersonFields($config);\n\n // Test 3c: Get Activity Fields\n $this->testGetActivityFields($config);\n } catch (Exception $e) {\n $this->recordResult('Field Metadata', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testGetDealFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealFieldsApi(null, $config);\n $result = $apiInstance->getDealFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Deal Fields', true, \"Retrieved {$count} deal fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersonFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonFieldsApi(null, $config);\n $result = $apiInstance->getPersonFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Person Fields', true, \"Retrieved {$count} person fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivityFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivityFieldsApi(null, $config);\n $result = $apiInstance->getActivityFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Activity Fields', true, \"Retrieved {$count} activity fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testV2ApiSupport(SocialAccount $socialAccount): void\n {\n $this->info('Test 4: v2 API Support (Cursor-based Pagination)');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $startTime = microtime(true);\n\n // Test v2 deals endpoint with cursor pagination\n // Note: The official SDK may or may not have v2 API support\n // This test will determine if v2 is available\n try {\n $apiInstance = new DealsApi(null, $config);\n $updatedSince = Carbon::now()->subDays(30)->toRfc3339String();\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n\n // Check if response has cursor information (v2 indicator)\n // The response object doesn't have getAdditionalData method\n // Just test if the call works - v2 API support is limited in official SDK v1\n $this->recordResult('v2 API Support', false, \"v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);\n }\n } catch (Exception $e) {\n $this->recordResult('v2 API Support', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testErrorHandling(SocialAccount $socialAccount): void\n {\n $this->info('Test 5: Error Handling');\n\n try {\n // Test 5a: Invalid Token\n $this->testInvalidToken();\n\n // Test 5b: Non-existent Resource\n $this->testNonExistentResource($socialAccount);\n } catch (Exception $e) {\n $this->recordResult('Error Handling', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testInvalidToken(): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken('invalid_token');\n $client = new DealsApi(null, $config);\n\n $client->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function testNonExistentResource(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n $client = new DealsApi(null, $config);\n\n // Try to get a deal with a very high ID that likely doesn't exist\n // Note: Official SDK doesn't have a simple getDeal method, so skip this\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function recordResult(string $testName, bool $success, string $message, float $elapsed): void\n {\n $this->results[] = [\n 'test' => $testName,\n 'success' => $success,\n 'message' => $message,\n 'elapsed' => $elapsed,\n ];\n\n $status = $success ? '✅ PASS' : '❌ FAIL';\n $this->line(\" {$status} - {$testName}: {$message}\");\n }\n\n private function displayResults(): void\n {\n $this->newLine();\n $this->info('=== POC Test Results Summary ===');\n $this->newLine();\n\n $total = count($this->results);\n $passed = count(array_filter($this->results, fn ($r) => $r['success']));\n $failed = $total - $passed;\n\n $this->line(\"Total Tests: {$total}\");\n $this->line(\"Passed: {$passed}\");\n $this->line(\"Failed: {$failed}\");\n $this->newLine();\n\n $totalTime = array_sum(array_column($this->results, 'elapsed'));\n $this->line(\"Total Execution Time: {$totalTime}ms\");\n $this->newLine();\n\n if ($failed > 0) {\n $this->warn('Failed Tests:');\n foreach ($this->results as $result) {\n if (! $result['success']) {\n $this->line(\" - {$result['test']}: {$result['message']}\");\n }\n }\n $this->newLine();\n }\n\n $this->info('=== Detailed Results ===');\n foreach ($this->results as $result) {\n $status = $result['success'] ? '✅' : '❌';\n $this->line(\"{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}\");\n }\n\n $this->newLine();\n $this->info('=== Recommendations ===');\n\n if ($passed === $total) {\n $this->info('✅ All tests passed! The official SDK is viable for migration.');\n $this->info('Next steps: Proceed with full migration planning.');\n } else {\n $this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');\n $this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Console\\Commands\\Crm;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Console\\Command;\nuse Jiminny\\Component\\Encryption\\EncryptedTokenManager;\nuse Jiminny\\Models\\SocialAccount;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Pipedrive\\versions\\v1\\Api\\ActivitiesApi;\nuse Pipedrive\\versions\\v1\\Api\\ActivityFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonsApi;\nuse Pipedrive\\versions\\v1\\Configuration;\n\nclass TestPipedriveOfficialSdkCommand extends Command\n{\n protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';\n\n protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';\n\n private array $results = [];\n\n private EncryptedTokenManager $tokenManager;\n\n public function __construct(EncryptedTokenManager $tokenManager)\n {\n parent::__construct();\n $this->tokenManager = $tokenManager;\n }\n\n public function handle(): int\n {\n $teamId = (int) $this->argument('teamId');\n\n $this->info(\"Starting Pipedrive Official SDK POC for Team ID: {$teamId}\");\n $this->newLine();\n\n try {\n $this->runTests($teamId);\n } catch (Exception $e) {\n $this->error(\"POC failed with error: {$e->getMessage()}\");\n $this->error($e->getTraceAsString());\n\n return 1;\n }\n\n $this->displayResults();\n\n return 0;\n }\n\n private function runTests(int $teamId): void\n {\n $team = Team::find($teamId);\n if (! $team) {\n throw new Exception(\"Team with ID {$teamId} not found\");\n }\n\n $socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)\n ->whereHas('sociable', function ($query) use ($team) {\n $query->where('team_id', $team->id);\n })\n ->orderByDesc('expires') // Get the most recently expiring token\n ->first();\n\n if (! $socialAccount) {\n throw new Exception(\"No Pipedrive social account found for team {$teamId}\");\n }\n\n $this->info(\"Found Pipedrive account for team: {$team->name}\");\n $this->newLine();\n\n // Test 1: OAuth Initialization\n $this->testOAuthInitialization($socialAccount);\n\n // Test 2: Basic CRUD Operations\n $this->testBasicCrudOperations($socialAccount);\n\n // Test 3: Field Metadata\n $this->testFieldMetadata($socialAccount);\n\n // Test 4: v2 API Support\n $this->testV2ApiSupport($socialAccount);\n\n // Test 5: Error Handling\n $this->testErrorHandling($socialAccount);\n }\n\n private function testOAuthInitialization(SocialAccount $socialAccount): void\n {\n $this->info('Test 1: OAuth Initialization');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $client = new DealsApi(null, $config);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', true, \"Client initialized successfully in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);\n }\n\n $this->newLine();\n }\n\n private function testBasicCrudOperations(SocialAccount $socialAccount): void\n {\n $this->info('Test 2: Basic CRUD Operations');\n\n try {\n // Get the raw token from database\n $rawDbToken = $socialAccount->getProviderUserToken();\n $this->info(\"DB token (length: \" . strlen($rawDbToken) . \")\");\n $this->info(\"DB token preview: \" . substr($rawDbToken, 0, 20) . \"...\");\n\n // Check token expiry\n $expires = $socialAccount->expires;\n $this->info(\"Token expires at: \" . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));\n $this->info(\"Token is expired: \" . ($expires && $expires < time() ? 'YES' : 'NO'));\n\n // Test with the DB token directly\n $this->info(\"\\n--- Testing with DB token ---\");\n $config = new Configuration();\n $config->setAccessToken($rawDbToken);\n\n // Test 2a: Get Deals\n $this->testGetDeals($config);\n\n // Test 2b: Get Persons\n $this->testGetPersons($config);\n\n // Test 2c: Get Activities\n $this->testGetActivities($config);\n } catch (Exception $e) {\n $this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testCurrentSystemApproach(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n // Simulate how current system makes requests\n $token = $socialAccount->getProviderUserToken();\n $url = 'https://api.pipedrive.com/api/v2/deals';\n\n $headers = [\n 'Authorization' => 'Bearer ' . $token,\n 'Accept' => 'application/json',\n ];\n\n $client = new \\GuzzleHttp\\Client();\n $response = $client->get($url, [\n 'headers' => $headers,\n 'timeout' => 30,\n ]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $statusCode = $response->getStatusCode();\n $this->recordResult('Current System v2 API', $statusCode === 200, \"Status: {$statusCode} in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetDeals(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealsApi(null, $config);\n // Official SDK uses getDealsSummary instead of getDeals\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', true, \"Retrieved deals summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersons(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getPersons method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivities(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getActivities method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testFieldMetadata(SocialAccount $socialAccount): void\n {\n $this->info('Test 3: Field Metadata');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n // Test 3a: Get Deal Fields\n $this->testGetDealFields($config);\n\n // Test 3b: Get Person Fields\n $this->testGetPersonFields($config);\n\n // Test 3c: Get Activity Fields\n $this->testGetActivityFields($config);\n } catch (Exception $e) {\n $this->recordResult('Field Metadata', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testGetDealFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealFieldsApi(null, $config);\n $result = $apiInstance->getDealFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Deal Fields', true, \"Retrieved {$count} deal fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersonFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonFieldsApi(null, $config);\n $result = $apiInstance->getPersonFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Person Fields', true, \"Retrieved {$count} person fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivityFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivityFieldsApi(null, $config);\n $result = $apiInstance->getActivityFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Activity Fields', true, \"Retrieved {$count} activity fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testV2ApiSupport(SocialAccount $socialAccount): void\n {\n $this->info('Test 4: v2 API Support (Cursor-based Pagination)');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $startTime = microtime(true);\n\n // Test v2 deals endpoint with cursor pagination\n // Note: The official SDK may or may not have v2 API support\n // This test will determine if v2 is available\n try {\n $apiInstance = new DealsApi(null, $config);\n $updatedSince = Carbon::now()->subDays(30)->toRfc3339String();\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n\n // Check if response has cursor information (v2 indicator)\n // The response object doesn't have getAdditionalData method\n // Just test if the call works - v2 API support is limited in official SDK v1\n $this->recordResult('v2 API Support', false, \"v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);\n }\n } catch (Exception $e) {\n $this->recordResult('v2 API Support', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testErrorHandling(SocialAccount $socialAccount): void\n {\n $this->info('Test 5: Error Handling');\n\n try {\n // Test 5a: Invalid Token\n $this->testInvalidToken();\n\n // Test 5b: Non-existent Resource\n $this->testNonExistentResource($socialAccount);\n } catch (Exception $e) {\n $this->recordResult('Error Handling', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testInvalidToken(): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken('invalid_token');\n $client = new DealsApi(null, $config);\n\n $client->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function testNonExistentResource(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n $client = new DealsApi(null, $config);\n\n // Try to get a deal with a very high ID that likely doesn't exist\n // Note: Official SDK doesn't have a simple getDeal method, so skip this\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function recordResult(string $testName, bool $success, string $message, float $elapsed): void\n {\n $this->results[] = [\n 'test' => $testName,\n 'success' => $success,\n 'message' => $message,\n 'elapsed' => $elapsed,\n ];\n\n $status = $success ? '✅ PASS' : '❌ FAIL';\n $this->line(\" {$status} - {$testName}: {$message}\");\n }\n\n private function displayResults(): void\n {\n $this->newLine();\n $this->info('=== POC Test Results Summary ===');\n $this->newLine();\n\n $total = count($this->results);\n $passed = count(array_filter($this->results, fn ($r) => $r['success']));\n $failed = $total - $passed;\n\n $this->line(\"Total Tests: {$total}\");\n $this->line(\"Passed: {$passed}\");\n $this->line(\"Failed: {$failed}\");\n $this->newLine();\n\n $totalTime = array_sum(array_column($this->results, 'elapsed'));\n $this->line(\"Total Execution Time: {$totalTime}ms\");\n $this->newLine();\n\n if ($failed > 0) {\n $this->warn('Failed Tests:');\n foreach ($this->results as $result) {\n if (! $result['success']) {\n $this->line(\" - {$result['test']}: {$result['message']}\");\n }\n }\n $this->newLine();\n }\n\n $this->info('=== Detailed Results ===');\n foreach ($this->results as $result) {\n $status = $result['success'] ? '✅' : '❌';\n $this->line(\"{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}\");\n }\n\n $this->newLine();\n $this->info('=== Recommendations ===');\n\n if ($passed === $total) {\n $this->info('✅ All tests passed! The official SDK is viable for migration.');\n $this->info('Next steps: Proceed with full migration planning.');\n } else {\n $this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');\n $this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
5365831007722038384
|
61370652661533572
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
1
51
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Jiminny\Component\Encryption\EncryptedTokenManager;
use Jiminny\Models\SocialAccount;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Pipedrive\versions\v1\Api\ActivitiesApi;
use Pipedrive\versions\v1\Api\ActivityFieldsApi;
use Pipedrive\versions\v1\Api\DealFieldsApi;
use Pipedrive\versions\v1\Api\DealsApi;
use Pipedrive\versions\v1\Api\PersonFieldsApi;
use Pipedrive\versions\v1\Api\PersonsApi;
use Pipedrive\versions\v1\Configuration;
class TestPipedriveOfficialSdkCommand extends Command
{
protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';
protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';
private array $results = [];
private EncryptedTokenManager $tokenManager;
public function __construct(EncryptedTokenManager $tokenManager)
{
parent::__construct();
$this->tokenManager = $tokenManager;
}
public function handle(): int
{
$teamId = (int) $this->argument('teamId');
$this->info("Starting Pipedrive Official SDK POC for Team ID: {$teamId}");
$this->newLine();
try {
$this->runTests($teamId);
} catch (Exception $e) {
$this->error("POC failed with error: {$e->getMessage()}");
$this->error($e->getTraceAsString());
return 1;
}
$this->displayResults();
return 0;
}
private function runTests(int $teamId): void
{
$team = Team::find($teamId);
if (! $team) {
throw new Exception("Team with ID {$teamId} not found");
}
$socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)
->whereHas('sociable', function ($query) use ($team) {
$query->where('team_id', $team->id);
})
->orderByDesc('expires') // Get the most recently expiring token
->first();
if (! $socialAccount) {
throw new Exception("No Pipedrive social account found for team {$teamId}");
}
$this->info("Found Pipedrive account for team: {$team->name}");
$this->newLine();
// Test 1: OAuth Initialization
$this->testOAuthInitialization($socialAccount);
// Test 2: Basic CRUD Operations
$this->testBasicCrudOperations($socialAccount);
// Test 3: Field Metadata
$this->testFieldMetadata($socialAccount);
// Test 4: v2 API Support
$this->testV2ApiSupport($socialAccount);
// Test 5: Error Handling
$this->testErrorHandling($socialAccount);
}
private function testOAuthInitialization(SocialAccount $socialAccount): void
{
$this->info('Test 1: OAuth Initialization');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', true, "Client initialized successfully in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);
}
$this->newLine();
}
private function testBasicCrudOperations(SocialAccount $socialAccount): void
{
$this->info('Test 2: Basic CRUD Operations');
try {
// Get the raw token from database
$rawDbToken = $socialAccount->getProviderUserToken();
$this->info("DB token (length: " . strlen($rawDbToken) . ")");
$this->info("DB token preview: " . substr($rawDbToken, 0, 20) . "...");
// Check token expiry
$expires = $socialAccount->expires;
$this->info("Token expires at: " . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));
$this->info("Token is expired: " . ($expires && $expires < time() ? 'YES' : 'NO'));
// Test with the DB token directly
$this->info("\n--- Testing with DB token ---");
$config = new Configuration();
$config->setAccessToken($rawDbToken);
// Test 2a: Get Deals
$this->testGetDeals($config);
// Test 2b: Get Persons
$this->testGetPersons($config);
// Test 2c: Get Activities
$this->testGetActivities($config);
} catch (Exception $e) {
$this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testCurrentSystemApproach(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
// Simulate how current system makes requests
$token = $socialAccount->getProviderUserToken();
$url = 'https://api.pipedrive.com/api/v2/deals';
$headers = [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
];
$client = new \GuzzleHttp\Client();
$response = $client->get($url, [
'headers' => $headers,
'timeout' => 30,
]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$statusCode = $response->getStatusCode();
$this->recordResult('Current System v2 API', $statusCode === 200, "Status: {$statusCode} in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);
}
}
private function testGetDeals(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealsApi(null, $config);
// Official SDK uses getDealsSummary instead of getDeals
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', true, "Retrieved deals summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersons(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getPersons method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivities(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getActivities method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);
}
}
private function testFieldMetadata(SocialAccount $socialAccount): void
{
$this->info('Test 3: Field Metadata');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
// Test 3a: Get Deal Fields
$this->testGetDealFields($config);
// Test 3b: Get Person Fields
$this->testGetPersonFields($config);
// Test 3c: Get Activity Fields
$this->testGetActivityFields($config);
} catch (Exception $e) {
$this->recordResult('Field Metadata', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testGetDealFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealFieldsApi(null, $config);
$result = $apiInstance->getDealFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Deal Fields', true, "Retrieved {$count} deal fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersonFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new PersonFieldsApi(null, $config);
$result = $apiInstance->getPersonFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Person Fields', true, "Retrieved {$count} person fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivityFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new ActivityFieldsApi(null, $config);
$result = $apiInstance->getActivityFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Activity Fields', true, "Retrieved {$count} activity fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);
}
}
private function testV2ApiSupport(SocialAccount $socialAccount): void
{
$this->info('Test 4: v2 API Support (Cursor-based Pagination)');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$startTime = microtime(true);
// Test v2 deals endpoint with cursor pagination
// Note: The official SDK may or may not have v2 API support
// This test will determine if v2 is available
try {
$apiInstance = new DealsApi(null, $config);
$updatedSince = Carbon::now()->subDays(30)->toRfc3339String();
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
// Check if response has cursor information (v2 indicator)
// The response object doesn't have getAdditionalData method
// Just test if the call works - v2 API support is limited in official SDK v1
$this->recordResult('v2 API Support', false, "v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);
}
} catch (Exception $e) {
$this->recordResult('v2 API Support', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testErrorHandling(SocialAccount $socialAccount): void
{
$this->info('Test 5: Error Handling');
try {
// Test 5a: Invalid Token
$this->testInvalidToken();
// Test 5b: Non-existent Resource
$this->testNonExistentResource($socialAccount);
} catch (Exception $e) {
$this->recordResult('Error Handling', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testInvalidToken(): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken('invalid_token');
$client = new DealsApi(null, $config);
$client->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function testNonExistentResource(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
// Try to get a deal with a very high ID that likely doesn't exist
// Note: Official SDK doesn't have a simple getDeal method, so skip this
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function recordResult(string $testName, bool $success, string $message, float $elapsed): void
{
$this->results[] = [
'test' => $testName,
'success' => $success,
'message' => $message,
'elapsed' => $elapsed,
];
$status = $success ? '✅ PASS' : '❌ FAIL';
$this->line(" {$status} - {$testName}: {$message}");
}
private function displayResults(): void
{
$this->newLine();
$this->info('=== POC Test Results Summary ===');
$this->newLine();
$total = count($this->results);
$passed = count(array_filter($this->results, fn ($r) => $r['success']));
$failed = $total - $passed;
$this->line("Total Tests: {$total}");
$this->line("Passed: {$passed}");
$this->line("Failed: {$failed}");
$this->newLine();
$totalTime = array_sum(array_column($this->results, 'elapsed'));
$this->line("Total Execution Time: {$totalTime}ms");
$this->newLine();
if ($failed > 0) {
$this->warn('Failed Tests:');
foreach ($this->results as $result) {
if (! $result['success']) {
$this->line(" - {$result['test']}: {$result['message']}");
}
}
$this->newLine();
}
$this->info('=== Detailed Results ===');
foreach ($this->results as $result) {
$status = $result['success'] ? '✅' : '❌';
$this->line("{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}");
}
$this->newLine();
$this->info('=== Recommendations ===');
if ($passed === $total) {
$this->info('✅ All tests passed! The official SDK is viable for migration.');
$this->info('Next steps: Proceed with full migration planning.');
} else {
$this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');
$this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
49399
|
1763
|
24
|
2026-05-18T06:40:46.940175+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086446940_m2.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
1
51
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Jiminny\Component\Encryption\EncryptedTokenManager;
use Jiminny\Models\SocialAccount;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Pipedrive\versions\v1\Api\ActivitiesApi;
use Pipedrive\versions\v1\Api\ActivityFieldsApi;
use Pipedrive\versions\v1\Api\DealFieldsApi;
use Pipedrive\versions\v1\Api\DealsApi;
use Pipedrive\versions\v1\Api\PersonFieldsApi;
use Pipedrive\versions\v1\Api\PersonsApi;
use Pipedrive\versions\v1\Configuration;
class TestPipedriveOfficialSdkCommand extends Command
{
protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';
protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';
private array $results = [];
private EncryptedTokenManager $tokenManager;
public function __construct(EncryptedTokenManager $tokenManager)
{
parent::__construct();
$this->tokenManager = $tokenManager;
}
public function handle(): int
{
$teamId = (int) $this->argument('teamId');
$this->info("Starting Pipedrive Official SDK POC for Team ID: {$teamId}");
$this->newLine();
try {
$this->runTests($teamId);
} catch (Exception $e) {
$this->error("POC failed with error: {$e->getMessage()}");
$this->error($e->getTraceAsString());
return 1;
}
$this->displayResults();
return 0;
}
private function runTests(int $teamId): void
{
$team = Team::find($teamId);
if (! $team) {
throw new Exception("Team with ID {$teamId} not found");
}
$socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)
->whereHas('sociable', function ($query) use ($team) {
$query->where('team_id', $team->id);
})
->orderByDesc('expires') // Get the most recently expiring token
->first();
if (! $socialAccount) {
throw new Exception("No Pipedrive social account found for team {$teamId}");
}
$this->info("Found Pipedrive account for team: {$team->name}");
$this->newLine();
// Test 1: OAuth Initialization
$this->testOAuthInitialization($socialAccount);
// Test 2: Basic CRUD Operations
$this->testBasicCrudOperations($socialAccount);
// Test 3: Field Metadata
$this->testFieldMetadata($socialAccount);
// Test 4: v2 API Support
$this->testV2ApiSupport($socialAccount);
// Test 5: Error Handling
$this->testErrorHandling($socialAccount);
}
private function testOAuthInitialization(SocialAccount $socialAccount): void
{
$this->info('Test 1: OAuth Initialization');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', true, "Client initialized successfully in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);
}
$this->newLine();
}
private function testBasicCrudOperations(SocialAccount $socialAccount): void
{
$this->info('Test 2: Basic CRUD Operations');
try {
// Get the raw token from database
$rawDbToken = $socialAccount->getProviderUserToken();
$this->info("DB token (length: " . strlen($rawDbToken) . ")");
$this->info("DB token preview: " . substr($rawDbToken, 0, 20) . "...");
// Check token expiry
$expires = $socialAccount->expires;
$this->info("Token expires at: " . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));
$this->info("Token is expired: " . ($expires && $expires < time() ? 'YES' : 'NO'));
// Test with the DB token directly
$this->info("\n--- Testing with DB token ---");
$config = new Configuration();
$config->setAccessToken($rawDbToken);
// Test 2a: Get Deals
$this->testGetDeals($config);
// Test 2b: Get Persons
$this->testGetPersons($config);
// Test 2c: Get Activities
$this->testGetActivities($config);
} catch (Exception $e) {
$this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testCurrentSystemApproach(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
// Simulate how current system makes requests
$token = $socialAccount->getProviderUserToken();
$url = 'https://api.pipedrive.com/api/v2/deals';
$headers = [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
];
$client = new \GuzzleHttp\Client();
$response = $client->get($url, [
'headers' => $headers,
'timeout' => 30,
]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$statusCode = $response->getStatusCode();
$this->recordResult('Current System v2 API', $statusCode === 200, "Status: {$statusCode} in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);
}
}
private function testGetDeals(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealsApi(null, $config);
// Official SDK uses getDealsSummary instead of getDeals
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', true, "Retrieved deals summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersons(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getPersons method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivities(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getActivities method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);
}
}
private function testFieldMetadata(SocialAccount $socialAccount): void
{
$this->info('Test 3: Field Metadata');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
// Test 3a: Get Deal Fields
$this->testGetDealFields($config);
// Test 3b: Get Person Fields
$this->testGetPersonFields($config);
// Test 3c: Get Activity Fields
$this->testGetActivityFields($config);
} catch (Exception $e) {
$this->recordResult('Field Metadata', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testGetDealFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealFieldsApi(null, $config);
$result = $apiInstance->getDealFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Deal Fields', true, "Retrieved {$count} deal fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersonFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new PersonFieldsApi(null, $config);
$result = $apiInstance->getPersonFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Person Fields', true, "Retrieved {$count} person fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivityFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new ActivityFieldsApi(null, $config);
$result = $apiInstance->getActivityFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Activity Fields', true, "Retrieved {$count} activity fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);
}
}
private function testV2ApiSupport(SocialAccount $socialAccount): void
{
$this->info('Test 4: v2 API Support (Cursor-based Pagination)');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$startTime = microtime(true);
// Test v2 deals endpoint with cursor pagination
// Note: The official SDK may or may not have v2 API support
// This test will determine if v2 is available
try {
$apiInstance = new DealsApi(null, $config);
$updatedSince = Carbon::now()->subDays(30)->toRfc3339String();
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
// Check if response has cursor information (v2 indicator)
// The response object doesn't have getAdditionalData method
// Just test if the call works - v2 API support is limited in official SDK v1
$this->recordResult('v2 API Support', false, "v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);
}
} catch (Exception $e) {
$this->recordResult('v2 API Support', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testErrorHandling(SocialAccount $socialAccount): void
{
$this->info('Test 5: Error Handling');
try {
// Test 5a: Invalid Token
$this->testInvalidToken();
// Test 5b: Non-existent Resource
$this->testNonExistentResource($socialAccount);
} catch (Exception $e) {
$this->recordResult('Error Handling', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testInvalidToken(): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken('invalid_token');
$client = new DealsApi(null, $config);
$client->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function testNonExistentResource(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
// Try to get a deal with a very high ID that likely doesn't exist
// Note: Official SDK doesn't have a simple getDeal method, so skip this
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function recordResult(string $testName, bool $success, string $message, float $elapsed): void
{
$this->results[] = [
'test' => $testName,
'success' => $success,
'message' => $message,
'elapsed' => $elapsed,
];
$status = $success ? '✅ PASS' : '❌ FAIL';
$this->line(" {$status} - {$testName}: {$message}");
}
private function displayResults(): void
{
$this->newLine();
$this->info('=== POC Test Results Summary ===');
$this->newLine();
$total = count($this->results);
$passed = count(array_filter($this->results, fn ($r) => $r['success']));
$failed = $total - $passed;
$this->line("Total Tests: {$total}");
$this->line("Passed: {$passed}");
$this->line("Failed: {$failed}");
$this->newLine();
$totalTime = array_sum(array_column($this->results, 'elapsed'));
$this->line("Total Execution Time: {$totalTime}ms");
$this->newLine();
if ($failed > 0) {
$this->warn('Failed Tests:');
foreach ($this->results as $result) {
if (! $result['success']) {
$this->line(" - {$result['test']}: {$result['message']}");
}
}
$this->newLine();
}
$this->info('=== Detailed Results ===');
foreach ($this->results as $result) {
$status = $result['success'] ? '✅' : '❌';
$this->line("{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}");
}
$this->newLine();
$this->info('=== Recommendations ===');
if ($passed === $total) {
$this->info('✅ All tests passed! The official SDK is viable for migration.');
$this->info('Next steps: Proceed with full migration planning.');
} else {
$this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');
$this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');
}
}
}
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.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"pipedrive-sdk-poc, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.05618351,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: pipedrive-sdk-poc","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.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","depth":4,"bounds":{"left":0.15724733,"top":0.2952913,"width":0.21476063,"height":0.7047087},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.57912236,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"51","depth":4,"bounds":{"left":0.5884308,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.6000665,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6090425,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"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.6163564,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"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\\Console\\Commands\\Crm;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Console\\Command;\nuse Jiminny\\Component\\Encryption\\EncryptedTokenManager;\nuse Jiminny\\Models\\SocialAccount;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Pipedrive\\versions\\v1\\Api\\ActivitiesApi;\nuse Pipedrive\\versions\\v1\\Api\\ActivityFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonsApi;\nuse Pipedrive\\versions\\v1\\Configuration;\n\nclass TestPipedriveOfficialSdkCommand extends Command\n{\n protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';\n\n protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';\n\n private array $results = [];\n\n private EncryptedTokenManager $tokenManager;\n\n public function __construct(EncryptedTokenManager $tokenManager)\n {\n parent::__construct();\n $this->tokenManager = $tokenManager;\n }\n\n public function handle(): int\n {\n $teamId = (int) $this->argument('teamId');\n\n $this->info(\"Starting Pipedrive Official SDK POC for Team ID: {$teamId}\");\n $this->newLine();\n\n try {\n $this->runTests($teamId);\n } catch (Exception $e) {\n $this->error(\"POC failed with error: {$e->getMessage()}\");\n $this->error($e->getTraceAsString());\n\n return 1;\n }\n\n $this->displayResults();\n\n return 0;\n }\n\n private function runTests(int $teamId): void\n {\n $team = Team::find($teamId);\n if (! $team) {\n throw new Exception(\"Team with ID {$teamId} not found\");\n }\n\n $socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)\n ->whereHas('sociable', function ($query) use ($team) {\n $query->where('team_id', $team->id);\n })\n ->orderByDesc('expires') // Get the most recently expiring token\n ->first();\n\n if (! $socialAccount) {\n throw new Exception(\"No Pipedrive social account found for team {$teamId}\");\n }\n\n $this->info(\"Found Pipedrive account for team: {$team->name}\");\n $this->newLine();\n\n // Test 1: OAuth Initialization\n $this->testOAuthInitialization($socialAccount);\n\n // Test 2: Basic CRUD Operations\n $this->testBasicCrudOperations($socialAccount);\n\n // Test 3: Field Metadata\n $this->testFieldMetadata($socialAccount);\n\n // Test 4: v2 API Support\n $this->testV2ApiSupport($socialAccount);\n\n // Test 5: Error Handling\n $this->testErrorHandling($socialAccount);\n }\n\n private function testOAuthInitialization(SocialAccount $socialAccount): void\n {\n $this->info('Test 1: OAuth Initialization');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $client = new DealsApi(null, $config);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', true, \"Client initialized successfully in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);\n }\n\n $this->newLine();\n }\n\n private function testBasicCrudOperations(SocialAccount $socialAccount): void\n {\n $this->info('Test 2: Basic CRUD Operations');\n\n try {\n // Get the raw token from database\n $rawDbToken = $socialAccount->getProviderUserToken();\n $this->info(\"DB token (length: \" . strlen($rawDbToken) . \")\");\n $this->info(\"DB token preview: \" . substr($rawDbToken, 0, 20) . \"...\");\n\n // Check token expiry\n $expires = $socialAccount->expires;\n $this->info(\"Token expires at: \" . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));\n $this->info(\"Token is expired: \" . ($expires && $expires < time() ? 'YES' : 'NO'));\n\n // Test with the DB token directly\n $this->info(\"\\n--- Testing with DB token ---\");\n $config = new Configuration();\n $config->setAccessToken($rawDbToken);\n\n // Test 2a: Get Deals\n $this->testGetDeals($config);\n\n // Test 2b: Get Persons\n $this->testGetPersons($config);\n\n // Test 2c: Get Activities\n $this->testGetActivities($config);\n } catch (Exception $e) {\n $this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testCurrentSystemApproach(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n // Simulate how current system makes requests\n $token = $socialAccount->getProviderUserToken();\n $url = 'https://api.pipedrive.com/api/v2/deals';\n\n $headers = [\n 'Authorization' => 'Bearer ' . $token,\n 'Accept' => 'application/json',\n ];\n\n $client = new \\GuzzleHttp\\Client();\n $response = $client->get($url, [\n 'headers' => $headers,\n 'timeout' => 30,\n ]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $statusCode = $response->getStatusCode();\n $this->recordResult('Current System v2 API', $statusCode === 200, \"Status: {$statusCode} in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetDeals(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealsApi(null, $config);\n // Official SDK uses getDealsSummary instead of getDeals\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', true, \"Retrieved deals summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersons(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getPersons method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivities(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getActivities method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testFieldMetadata(SocialAccount $socialAccount): void\n {\n $this->info('Test 3: Field Metadata');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n // Test 3a: Get Deal Fields\n $this->testGetDealFields($config);\n\n // Test 3b: Get Person Fields\n $this->testGetPersonFields($config);\n\n // Test 3c: Get Activity Fields\n $this->testGetActivityFields($config);\n } catch (Exception $e) {\n $this->recordResult('Field Metadata', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testGetDealFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealFieldsApi(null, $config);\n $result = $apiInstance->getDealFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Deal Fields', true, \"Retrieved {$count} deal fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersonFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonFieldsApi(null, $config);\n $result = $apiInstance->getPersonFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Person Fields', true, \"Retrieved {$count} person fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivityFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivityFieldsApi(null, $config);\n $result = $apiInstance->getActivityFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Activity Fields', true, \"Retrieved {$count} activity fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testV2ApiSupport(SocialAccount $socialAccount): void\n {\n $this->info('Test 4: v2 API Support (Cursor-based Pagination)');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $startTime = microtime(true);\n\n // Test v2 deals endpoint with cursor pagination\n // Note: The official SDK may or may not have v2 API support\n // This test will determine if v2 is available\n try {\n $apiInstance = new DealsApi(null, $config);\n $updatedSince = Carbon::now()->subDays(30)->toRfc3339String();\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n\n // Check if response has cursor information (v2 indicator)\n // The response object doesn't have getAdditionalData method\n // Just test if the call works - v2 API support is limited in official SDK v1\n $this->recordResult('v2 API Support', false, \"v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);\n }\n } catch (Exception $e) {\n $this->recordResult('v2 API Support', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testErrorHandling(SocialAccount $socialAccount): void\n {\n $this->info('Test 5: Error Handling');\n\n try {\n // Test 5a: Invalid Token\n $this->testInvalidToken();\n\n // Test 5b: Non-existent Resource\n $this->testNonExistentResource($socialAccount);\n } catch (Exception $e) {\n $this->recordResult('Error Handling', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testInvalidToken(): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken('invalid_token');\n $client = new DealsApi(null, $config);\n\n $client->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function testNonExistentResource(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n $client = new DealsApi(null, $config);\n\n // Try to get a deal with a very high ID that likely doesn't exist\n // Note: Official SDK doesn't have a simple getDeal method, so skip this\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function recordResult(string $testName, bool $success, string $message, float $elapsed): void\n {\n $this->results[] = [\n 'test' => $testName,\n 'success' => $success,\n 'message' => $message,\n 'elapsed' => $elapsed,\n ];\n\n $status = $success ? '✅ PASS' : '❌ FAIL';\n $this->line(\" {$status} - {$testName}: {$message}\");\n }\n\n private function displayResults(): void\n {\n $this->newLine();\n $this->info('=== POC Test Results Summary ===');\n $this->newLine();\n\n $total = count($this->results);\n $passed = count(array_filter($this->results, fn ($r) => $r['success']));\n $failed = $total - $passed;\n\n $this->line(\"Total Tests: {$total}\");\n $this->line(\"Passed: {$passed}\");\n $this->line(\"Failed: {$failed}\");\n $this->newLine();\n\n $totalTime = array_sum(array_column($this->results, 'elapsed'));\n $this->line(\"Total Execution Time: {$totalTime}ms\");\n $this->newLine();\n\n if ($failed > 0) {\n $this->warn('Failed Tests:');\n foreach ($this->results as $result) {\n if (! $result['success']) {\n $this->line(\" - {$result['test']}: {$result['message']}\");\n }\n }\n $this->newLine();\n }\n\n $this->info('=== Detailed Results ===');\n foreach ($this->results as $result) {\n $status = $result['success'] ? '✅' : '❌';\n $this->line(\"{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}\");\n }\n\n $this->newLine();\n $this->info('=== Recommendations ===');\n\n if ($passed === $total) {\n $this->info('✅ All tests passed! The official SDK is viable for migration.');\n $this->info('Next steps: Proceed with full migration planning.');\n } else {\n $this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');\n $this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Console\\Commands\\Crm;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Console\\Command;\nuse Jiminny\\Component\\Encryption\\EncryptedTokenManager;\nuse Jiminny\\Models\\SocialAccount;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Pipedrive\\versions\\v1\\Api\\ActivitiesApi;\nuse Pipedrive\\versions\\v1\\Api\\ActivityFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonsApi;\nuse Pipedrive\\versions\\v1\\Configuration;\n\nclass TestPipedriveOfficialSdkCommand extends Command\n{\n protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';\n\n protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';\n\n private array $results = [];\n\n private EncryptedTokenManager $tokenManager;\n\n public function __construct(EncryptedTokenManager $tokenManager)\n {\n parent::__construct();\n $this->tokenManager = $tokenManager;\n }\n\n public function handle(): int\n {\n $teamId = (int) $this->argument('teamId');\n\n $this->info(\"Starting Pipedrive Official SDK POC for Team ID: {$teamId}\");\n $this->newLine();\n\n try {\n $this->runTests($teamId);\n } catch (Exception $e) {\n $this->error(\"POC failed with error: {$e->getMessage()}\");\n $this->error($e->getTraceAsString());\n\n return 1;\n }\n\n $this->displayResults();\n\n return 0;\n }\n\n private function runTests(int $teamId): void\n {\n $team = Team::find($teamId);\n if (! $team) {\n throw new Exception(\"Team with ID {$teamId} not found\");\n }\n\n $socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)\n ->whereHas('sociable', function ($query) use ($team) {\n $query->where('team_id', $team->id);\n })\n ->orderByDesc('expires') // Get the most recently expiring token\n ->first();\n\n if (! $socialAccount) {\n throw new Exception(\"No Pipedrive social account found for team {$teamId}\");\n }\n\n $this->info(\"Found Pipedrive account for team: {$team->name}\");\n $this->newLine();\n\n // Test 1: OAuth Initialization\n $this->testOAuthInitialization($socialAccount);\n\n // Test 2: Basic CRUD Operations\n $this->testBasicCrudOperations($socialAccount);\n\n // Test 3: Field Metadata\n $this->testFieldMetadata($socialAccount);\n\n // Test 4: v2 API Support\n $this->testV2ApiSupport($socialAccount);\n\n // Test 5: Error Handling\n $this->testErrorHandling($socialAccount);\n }\n\n private function testOAuthInitialization(SocialAccount $socialAccount): void\n {\n $this->info('Test 1: OAuth Initialization');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $client = new DealsApi(null, $config);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', true, \"Client initialized successfully in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);\n }\n\n $this->newLine();\n }\n\n private function testBasicCrudOperations(SocialAccount $socialAccount): void\n {\n $this->info('Test 2: Basic CRUD Operations');\n\n try {\n // Get the raw token from database\n $rawDbToken = $socialAccount->getProviderUserToken();\n $this->info(\"DB token (length: \" . strlen($rawDbToken) . \")\");\n $this->info(\"DB token preview: \" . substr($rawDbToken, 0, 20) . \"...\");\n\n // Check token expiry\n $expires = $socialAccount->expires;\n $this->info(\"Token expires at: \" . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));\n $this->info(\"Token is expired: \" . ($expires && $expires < time() ? 'YES' : 'NO'));\n\n // Test with the DB token directly\n $this->info(\"\\n--- Testing with DB token ---\");\n $config = new Configuration();\n $config->setAccessToken($rawDbToken);\n\n // Test 2a: Get Deals\n $this->testGetDeals($config);\n\n // Test 2b: Get Persons\n $this->testGetPersons($config);\n\n // Test 2c: Get Activities\n $this->testGetActivities($config);\n } catch (Exception $e) {\n $this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testCurrentSystemApproach(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n // Simulate how current system makes requests\n $token = $socialAccount->getProviderUserToken();\n $url = 'https://api.pipedrive.com/api/v2/deals';\n\n $headers = [\n 'Authorization' => 'Bearer ' . $token,\n 'Accept' => 'application/json',\n ];\n\n $client = new \\GuzzleHttp\\Client();\n $response = $client->get($url, [\n 'headers' => $headers,\n 'timeout' => 30,\n ]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $statusCode = $response->getStatusCode();\n $this->recordResult('Current System v2 API', $statusCode === 200, \"Status: {$statusCode} in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetDeals(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealsApi(null, $config);\n // Official SDK uses getDealsSummary instead of getDeals\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', true, \"Retrieved deals summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersons(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getPersons method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivities(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getActivities method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testFieldMetadata(SocialAccount $socialAccount): void\n {\n $this->info('Test 3: Field Metadata');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n // Test 3a: Get Deal Fields\n $this->testGetDealFields($config);\n\n // Test 3b: Get Person Fields\n $this->testGetPersonFields($config);\n\n // Test 3c: Get Activity Fields\n $this->testGetActivityFields($config);\n } catch (Exception $e) {\n $this->recordResult('Field Metadata', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testGetDealFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealFieldsApi(null, $config);\n $result = $apiInstance->getDealFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Deal Fields', true, \"Retrieved {$count} deal fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersonFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonFieldsApi(null, $config);\n $result = $apiInstance->getPersonFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Person Fields', true, \"Retrieved {$count} person fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivityFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivityFieldsApi(null, $config);\n $result = $apiInstance->getActivityFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Activity Fields', true, \"Retrieved {$count} activity fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testV2ApiSupport(SocialAccount $socialAccount): void\n {\n $this->info('Test 4: v2 API Support (Cursor-based Pagination)');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $startTime = microtime(true);\n\n // Test v2 deals endpoint with cursor pagination\n // Note: The official SDK may or may not have v2 API support\n // This test will determine if v2 is available\n try {\n $apiInstance = new DealsApi(null, $config);\n $updatedSince = Carbon::now()->subDays(30)->toRfc3339String();\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n\n // Check if response has cursor information (v2 indicator)\n // The response object doesn't have getAdditionalData method\n // Just test if the call works - v2 API support is limited in official SDK v1\n $this->recordResult('v2 API Support', false, \"v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);\n }\n } catch (Exception $e) {\n $this->recordResult('v2 API Support', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testErrorHandling(SocialAccount $socialAccount): void\n {\n $this->info('Test 5: Error Handling');\n\n try {\n // Test 5a: Invalid Token\n $this->testInvalidToken();\n\n // Test 5b: Non-existent Resource\n $this->testNonExistentResource($socialAccount);\n } catch (Exception $e) {\n $this->recordResult('Error Handling', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testInvalidToken(): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken('invalid_token');\n $client = new DealsApi(null, $config);\n\n $client->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function testNonExistentResource(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n $client = new DealsApi(null, $config);\n\n // Try to get a deal with a very high ID that likely doesn't exist\n // Note: Official SDK doesn't have a simple getDeal method, so skip this\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function recordResult(string $testName, bool $success, string $message, float $elapsed): void\n {\n $this->results[] = [\n 'test' => $testName,\n 'success' => $success,\n 'message' => $message,\n 'elapsed' => $elapsed,\n ];\n\n $status = $success ? '✅ PASS' : '❌ FAIL';\n $this->line(\" {$status} - {$testName}: {$message}\");\n }\n\n private function displayResults(): void\n {\n $this->newLine();\n $this->info('=== POC Test Results Summary ===');\n $this->newLine();\n\n $total = count($this->results);\n $passed = count(array_filter($this->results, fn ($r) => $r['success']));\n $failed = $total - $passed;\n\n $this->line(\"Total Tests: {$total}\");\n $this->line(\"Passed: {$passed}\");\n $this->line(\"Failed: {$failed}\");\n $this->newLine();\n\n $totalTime = array_sum(array_column($this->results, 'elapsed'));\n $this->line(\"Total Execution Time: {$totalTime}ms\");\n $this->newLine();\n\n if ($failed > 0) {\n $this->warn('Failed Tests:');\n foreach ($this->results as $result) {\n if (! $result['success']) {\n $this->line(\" - {$result['test']}: {$result['message']}\");\n }\n }\n $this->newLine();\n }\n\n $this->info('=== Detailed Results ===');\n foreach ($this->results as $result) {\n $status = $result['success'] ? '✅' : '❌';\n $this->line(\"{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}\");\n }\n\n $this->newLine();\n $this->info('=== Recommendations ===');\n\n if ($passed === $total) {\n $this->info('✅ All tests passed! The official SDK is viable for migration.');\n $this->info('Next steps: Proceed with full migration planning.');\n } else {\n $this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');\n $this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
5365831007722038384
|
61370652661533572
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
1
51
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Jiminny\Component\Encryption\EncryptedTokenManager;
use Jiminny\Models\SocialAccount;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Pipedrive\versions\v1\Api\ActivitiesApi;
use Pipedrive\versions\v1\Api\ActivityFieldsApi;
use Pipedrive\versions\v1\Api\DealFieldsApi;
use Pipedrive\versions\v1\Api\DealsApi;
use Pipedrive\versions\v1\Api\PersonFieldsApi;
use Pipedrive\versions\v1\Api\PersonsApi;
use Pipedrive\versions\v1\Configuration;
class TestPipedriveOfficialSdkCommand extends Command
{
protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';
protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';
private array $results = [];
private EncryptedTokenManager $tokenManager;
public function __construct(EncryptedTokenManager $tokenManager)
{
parent::__construct();
$this->tokenManager = $tokenManager;
}
public function handle(): int
{
$teamId = (int) $this->argument('teamId');
$this->info("Starting Pipedrive Official SDK POC for Team ID: {$teamId}");
$this->newLine();
try {
$this->runTests($teamId);
} catch (Exception $e) {
$this->error("POC failed with error: {$e->getMessage()}");
$this->error($e->getTraceAsString());
return 1;
}
$this->displayResults();
return 0;
}
private function runTests(int $teamId): void
{
$team = Team::find($teamId);
if (! $team) {
throw new Exception("Team with ID {$teamId} not found");
}
$socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)
->whereHas('sociable', function ($query) use ($team) {
$query->where('team_id', $team->id);
})
->orderByDesc('expires') // Get the most recently expiring token
->first();
if (! $socialAccount) {
throw new Exception("No Pipedrive social account found for team {$teamId}");
}
$this->info("Found Pipedrive account for team: {$team->name}");
$this->newLine();
// Test 1: OAuth Initialization
$this->testOAuthInitialization($socialAccount);
// Test 2: Basic CRUD Operations
$this->testBasicCrudOperations($socialAccount);
// Test 3: Field Metadata
$this->testFieldMetadata($socialAccount);
// Test 4: v2 API Support
$this->testV2ApiSupport($socialAccount);
// Test 5: Error Handling
$this->testErrorHandling($socialAccount);
}
private function testOAuthInitialization(SocialAccount $socialAccount): void
{
$this->info('Test 1: OAuth Initialization');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', true, "Client initialized successfully in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);
}
$this->newLine();
}
private function testBasicCrudOperations(SocialAccount $socialAccount): void
{
$this->info('Test 2: Basic CRUD Operations');
try {
// Get the raw token from database
$rawDbToken = $socialAccount->getProviderUserToken();
$this->info("DB token (length: " . strlen($rawDbToken) . ")");
$this->info("DB token preview: " . substr($rawDbToken, 0, 20) . "...");
// Check token expiry
$expires = $socialAccount->expires;
$this->info("Token expires at: " . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));
$this->info("Token is expired: " . ($expires && $expires < time() ? 'YES' : 'NO'));
// Test with the DB token directly
$this->info("\n--- Testing with DB token ---");
$config = new Configuration();
$config->setAccessToken($rawDbToken);
// Test 2a: Get Deals
$this->testGetDeals($config);
// Test 2b: Get Persons
$this->testGetPersons($config);
// Test 2c: Get Activities
$this->testGetActivities($config);
} catch (Exception $e) {
$this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testCurrentSystemApproach(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
// Simulate how current system makes requests
$token = $socialAccount->getProviderUserToken();
$url = 'https://api.pipedrive.com/api/v2/deals';
$headers = [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
];
$client = new \GuzzleHttp\Client();
$response = $client->get($url, [
'headers' => $headers,
'timeout' => 30,
]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$statusCode = $response->getStatusCode();
$this->recordResult('Current System v2 API', $statusCode === 200, "Status: {$statusCode} in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);
}
}
private function testGetDeals(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealsApi(null, $config);
// Official SDK uses getDealsSummary instead of getDeals
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', true, "Retrieved deals summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersons(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getPersons method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivities(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getActivities method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);
}
}
private function testFieldMetadata(SocialAccount $socialAccount): void
{
$this->info('Test 3: Field Metadata');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
// Test 3a: Get Deal Fields
$this->testGetDealFields($config);
// Test 3b: Get Person Fields
$this->testGetPersonFields($config);
// Test 3c: Get Activity Fields
$this->testGetActivityFields($config);
} catch (Exception $e) {
$this->recordResult('Field Metadata', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testGetDealFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealFieldsApi(null, $config);
$result = $apiInstance->getDealFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Deal Fields', true, "Retrieved {$count} deal fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersonFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new PersonFieldsApi(null, $config);
$result = $apiInstance->getPersonFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Person Fields', true, "Retrieved {$count} person fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivityFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new ActivityFieldsApi(null, $config);
$result = $apiInstance->getActivityFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Activity Fields', true, "Retrieved {$count} activity fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);
}
}
private function testV2ApiSupport(SocialAccount $socialAccount): void
{
$this->info('Test 4: v2 API Support (Cursor-based Pagination)');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$startTime = microtime(true);
// Test v2 deals endpoint with cursor pagination
// Note: The official SDK may or may not have v2 API support
// This test will determine if v2 is available
try {
$apiInstance = new DealsApi(null, $config);
$updatedSince = Carbon::now()->subDays(30)->toRfc3339String();
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
// Check if response has cursor information (v2 indicator)
// The response object doesn't have getAdditionalData method
// Just test if the call works - v2 API support is limited in official SDK v1
$this->recordResult('v2 API Support', false, "v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);
}
} catch (Exception $e) {
$this->recordResult('v2 API Support', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testErrorHandling(SocialAccount $socialAccount): void
{
$this->info('Test 5: Error Handling');
try {
// Test 5a: Invalid Token
$this->testInvalidToken();
// Test 5b: Non-existent Resource
$this->testNonExistentResource($socialAccount);
} catch (Exception $e) {
$this->recordResult('Error Handling', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testInvalidToken(): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken('invalid_token');
$client = new DealsApi(null, $config);
$client->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function testNonExistentResource(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
// Try to get a deal with a very high ID that likely doesn't exist
// Note: Official SDK doesn't have a simple getDeal method, so skip this
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function recordResult(string $testName, bool $success, string $message, float $elapsed): void
{
$this->results[] = [
'test' => $testName,
'success' => $success,
'message' => $message,
'elapsed' => $elapsed,
];
$status = $success ? '✅ PASS' : '❌ FAIL';
$this->line(" {$status} - {$testName}: {$message}");
}
private function displayResults(): void
{
$this->newLine();
$this->info('=== POC Test Results Summary ===');
$this->newLine();
$total = count($this->results);
$passed = count(array_filter($this->results, fn ($r) => $r['success']));
$failed = $total - $passed;
$this->line("Total Tests: {$total}");
$this->line("Passed: {$passed}");
$this->line("Failed: {$failed}");
$this->newLine();
$totalTime = array_sum(array_column($this->results, 'elapsed'));
$this->line("Total Execution Time: {$totalTime}ms");
$this->newLine();
if ($failed > 0) {
$this->warn('Failed Tests:');
foreach ($this->results as $result) {
if (! $result['success']) {
$this->line(" - {$result['test']}: {$result['message']}");
}
}
$this->newLine();
}
$this->info('=== Detailed Results ===');
foreach ($this->results as $result) {
$status = $result['success'] ? '✅' : '❌';
$this->line("{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}");
}
$this->newLine();
$this->info('=== Recommendations ===');
if ($passed === $total) {
$this->info('✅ All tests passed! The official SDK is viable for migration.');
$this->info('Next steps: Proceed with full migration planning.');
} else {
$this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');
$this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
49394
|
NULL
|
NULL
|
NULL
|
|
49400
|
1763
|
25
|
2026-05-18T06:41:17.367410+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086477367_m2.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
1
51
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Jiminny\Component\Encryption\EncryptedTokenManager;
use Jiminny\Models\SocialAccount;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Pipedrive\versions\v1\Api\ActivitiesApi;
use Pipedrive\versions\v1\Api\ActivityFieldsApi;
use Pipedrive\versions\v1\Api\DealFieldsApi;
use Pipedrive\versions\v1\Api\DealsApi;
use Pipedrive\versions\v1\Api\PersonFieldsApi;
use Pipedrive\versions\v1\Api\PersonsApi;
use Pipedrive\versions\v1\Configuration;
class TestPipedriveOfficialSdkCommand extends Command
{
protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';
protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';
private array $results = [];
private EncryptedTokenManager $tokenManager;
public function __construct(EncryptedTokenManager $tokenManager)
{
parent::__construct();
$this->tokenManager = $tokenManager;
}
public function handle(): int
{
$teamId = (int) $this->argument('teamId');
$this->info("Starting Pipedrive Official SDK POC for Team ID: {$teamId}");
$this->newLine();
try {
$this->runTests($teamId);
} catch (Exception $e) {
$this->error("POC failed with error: {$e->getMessage()}");
$this->error($e->getTraceAsString());
return 1;
}
$this->displayResults();
return 0;
}
private function runTests(int $teamId): void
{
$team = Team::find($teamId);
if (! $team) {
throw new Exception("Team with ID {$teamId} not found");
}
$socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)
->whereHas('sociable', function ($query) use ($team) {
$query->where('team_id', $team->id);
})
->orderByDesc('expires') // Get the most recently expiring token
->first();
if (! $socialAccount) {
throw new Exception("No Pipedrive social account found for team {$teamId}");
}
$this->info("Found Pipedrive account for team: {$team->name}");
$this->newLine();
// Test 1: OAuth Initialization
$this->testOAuthInitialization($socialAccount);
// Test 2: Basic CRUD Operations
$this->testBasicCrudOperations($socialAccount);
// Test 3: Field Metadata
$this->testFieldMetadata($socialAccount);
// Test 4: v2 API Support
$this->testV2ApiSupport($socialAccount);
// Test 5: Error Handling
$this->testErrorHandling($socialAccount);
}
private function testOAuthInitialization(SocialAccount $socialAccount): void
{
$this->info('Test 1: OAuth Initialization');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', true, "Client initialized successfully in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);
}
$this->newLine();
}
private function testBasicCrudOperations(SocialAccount $socialAccount): void
{
$this->info('Test 2: Basic CRUD Operations');
try {
// Get the raw token from database
$rawDbToken = $socialAccount->getProviderUserToken();
$this->info("DB token (length: " . strlen($rawDbToken) . ")");
$this->info("DB token preview: " . substr($rawDbToken, 0, 20) . "...");
// Check token expiry
$expires = $socialAccount->expires;
$this->info("Token expires at: " . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));
$this->info("Token is expired: " . ($expires && $expires < time() ? 'YES' : 'NO'));
// Test with the DB token directly
$this->info("\n--- Testing with DB token ---");
$config = new Configuration();
$config->setAccessToken($rawDbToken);
// Test 2a: Get Deals
$this->testGetDeals($config);
// Test 2b: Get Persons
$this->testGetPersons($config);
// Test 2c: Get Activities
$this->testGetActivities($config);
} catch (Exception $e) {
$this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testCurrentSystemApproach(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
// Simulate how current system makes requests
$token = $socialAccount->getProviderUserToken();
$url = 'https://api.pipedrive.com/api/v2/deals';
$headers = [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
];
$client = new \GuzzleHttp\Client();
$response = $client->get($url, [
'headers' => $headers,
'timeout' => 30,
]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$statusCode = $response->getStatusCode();
$this->recordResult('Current System v2 API', $statusCode === 200, "Status: {$statusCode} in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);
}
}
private function testGetDeals(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealsApi(null, $config);
// Official SDK uses getDealsSummary instead of getDeals
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', true, "Retrieved deals summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersons(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getPersons method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivities(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getActivities method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);
}
}
private function testFieldMetadata(SocialAccount $socialAccount): void
{
$this->info('Test 3: Field Metadata');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
// Test 3a: Get Deal Fields
$this->testGetDealFields($config);
// Test 3b: Get Person Fields
$this->testGetPersonFields($config);
// Test 3c: Get Activity Fields
$this->testGetActivityFields($config);
} catch (Exception $e) {
$this->recordResult('Field Metadata', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testGetDealFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealFieldsApi(null, $config);
$result = $apiInstance->getDealFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Deal Fields', true, "Retrieved {$count} deal fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersonFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new PersonFieldsApi(null, $config);
$result = $apiInstance->getPersonFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Person Fields', true, "Retrieved {$count} person fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivityFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new ActivityFieldsApi(null, $config);
$result = $apiInstance->getActivityFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Activity Fields', true, "Retrieved {$count} activity fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);
}
}
private function testV2ApiSupport(SocialAccount $socialAccount): void
{
$this->info('Test 4: v2 API Support (Cursor-based Pagination)');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$startTime = microtime(true);
// Test v2 deals endpoint with cursor pagination
// Note: The official SDK may or may not have v2 API support
// This test will determine if v2 is available
try {
$apiInstance = new DealsApi(null, $config);
$updatedSince = Carbon::now()->subDays(30)->toRfc3339String();
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
// Check if response has cursor information (v2 indicator)
// The response object doesn't have getAdditionalData method
// Just test if the call works - v2 API support is limited in official SDK v1
$this->recordResult('v2 API Support', false, "v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);
}
} catch (Exception $e) {
$this->recordResult('v2 API Support', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testErrorHandling(SocialAccount $socialAccount): void
{
$this->info('Test 5: Error Handling');
try {
// Test 5a: Invalid Token
$this->testInvalidToken();
// Test 5b: Non-existent Resource
$this->testNonExistentResource($socialAccount);
} catch (Exception $e) {
$this->recordResult('Error Handling', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testInvalidToken(): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken('invalid_token');
$client = new DealsApi(null, $config);
$client->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function testNonExistentResource(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
// Try to get a deal with a very high ID that likely doesn't exist
// Note: Official SDK doesn't have a simple getDeal method, so skip this
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function recordResult(string $testName, bool $success, string $message, float $elapsed): void
{
$this->results[] = [
'test' => $testName,
'success' => $success,
'message' => $message,
'elapsed' => $elapsed,
];
$status = $success ? '✅ PASS' : '❌ FAIL';
$this->line(" {$status} - {$testName}: {$message}");
}
private function displayResults(): void
{
$this->newLine();
$this->info('=== POC Test Results Summary ===');
$this->newLine();
$total = count($this->results);
$passed = count(array_filter($this->results, fn ($r) => $r['success']));
$failed = $total - $passed;
$this->line("Total Tests: {$total}");
$this->line("Passed: {$passed}");
$this->line("Failed: {$failed}");
$this->newLine();
$totalTime = array_sum(array_column($this->results, 'elapsed'));
$this->line("Total Execution Time: {$totalTime}ms");
$this->newLine();
if ($failed > 0) {
$this->warn('Failed Tests:');
foreach ($this->results as $result) {
if (! $result['success']) {
$this->line(" - {$result['test']}: {$result['message']}");
}
}
$this->newLine();
}
$this->info('=== Detailed Results ===');
foreach ($this->results as $result) {
$status = $result['success'] ? '✅' : '❌';
$this->line("{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}");
}
$this->newLine();
$this->info('=== Recommendations ===');
if ($passed === $total) {
$this->info('✅ All tests passed! The official SDK is viable for migration.');
$this->info('Next steps: Proceed with full migration planning.');
} else {
$this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');
$this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');
}
}
}
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.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"pipedrive-sdk-poc, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.05618351,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: pipedrive-sdk-poc","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.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","depth":4,"bounds":{"left":0.15724733,"top":0.2952913,"width":0.21476063,"height":0.7047087},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.57912236,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"51","depth":4,"bounds":{"left":0.5884308,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.6000665,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6090425,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"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.6163564,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"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\\Console\\Commands\\Crm;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Console\\Command;\nuse Jiminny\\Component\\Encryption\\EncryptedTokenManager;\nuse Jiminny\\Models\\SocialAccount;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Pipedrive\\versions\\v1\\Api\\ActivitiesApi;\nuse Pipedrive\\versions\\v1\\Api\\ActivityFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonsApi;\nuse Pipedrive\\versions\\v1\\Configuration;\n\nclass TestPipedriveOfficialSdkCommand extends Command\n{\n protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';\n\n protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';\n\n private array $results = [];\n\n private EncryptedTokenManager $tokenManager;\n\n public function __construct(EncryptedTokenManager $tokenManager)\n {\n parent::__construct();\n $this->tokenManager = $tokenManager;\n }\n\n public function handle(): int\n {\n $teamId = (int) $this->argument('teamId');\n\n $this->info(\"Starting Pipedrive Official SDK POC for Team ID: {$teamId}\");\n $this->newLine();\n\n try {\n $this->runTests($teamId);\n } catch (Exception $e) {\n $this->error(\"POC failed with error: {$e->getMessage()}\");\n $this->error($e->getTraceAsString());\n\n return 1;\n }\n\n $this->displayResults();\n\n return 0;\n }\n\n private function runTests(int $teamId): void\n {\n $team = Team::find($teamId);\n if (! $team) {\n throw new Exception(\"Team with ID {$teamId} not found\");\n }\n\n $socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)\n ->whereHas('sociable', function ($query) use ($team) {\n $query->where('team_id', $team->id);\n })\n ->orderByDesc('expires') // Get the most recently expiring token\n ->first();\n\n if (! $socialAccount) {\n throw new Exception(\"No Pipedrive social account found for team {$teamId}\");\n }\n\n $this->info(\"Found Pipedrive account for team: {$team->name}\");\n $this->newLine();\n\n // Test 1: OAuth Initialization\n $this->testOAuthInitialization($socialAccount);\n\n // Test 2: Basic CRUD Operations\n $this->testBasicCrudOperations($socialAccount);\n\n // Test 3: Field Metadata\n $this->testFieldMetadata($socialAccount);\n\n // Test 4: v2 API Support\n $this->testV2ApiSupport($socialAccount);\n\n // Test 5: Error Handling\n $this->testErrorHandling($socialAccount);\n }\n\n private function testOAuthInitialization(SocialAccount $socialAccount): void\n {\n $this->info('Test 1: OAuth Initialization');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $client = new DealsApi(null, $config);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', true, \"Client initialized successfully in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);\n }\n\n $this->newLine();\n }\n\n private function testBasicCrudOperations(SocialAccount $socialAccount): void\n {\n $this->info('Test 2: Basic CRUD Operations');\n\n try {\n // Get the raw token from database\n $rawDbToken = $socialAccount->getProviderUserToken();\n $this->info(\"DB token (length: \" . strlen($rawDbToken) . \")\");\n $this->info(\"DB token preview: \" . substr($rawDbToken, 0, 20) . \"...\");\n\n // Check token expiry\n $expires = $socialAccount->expires;\n $this->info(\"Token expires at: \" . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));\n $this->info(\"Token is expired: \" . ($expires && $expires < time() ? 'YES' : 'NO'));\n\n // Test with the DB token directly\n $this->info(\"\\n--- Testing with DB token ---\");\n $config = new Configuration();\n $config->setAccessToken($rawDbToken);\n\n // Test 2a: Get Deals\n $this->testGetDeals($config);\n\n // Test 2b: Get Persons\n $this->testGetPersons($config);\n\n // Test 2c: Get Activities\n $this->testGetActivities($config);\n } catch (Exception $e) {\n $this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testCurrentSystemApproach(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n // Simulate how current system makes requests\n $token = $socialAccount->getProviderUserToken();\n $url = 'https://api.pipedrive.com/api/v2/deals';\n\n $headers = [\n 'Authorization' => 'Bearer ' . $token,\n 'Accept' => 'application/json',\n ];\n\n $client = new \\GuzzleHttp\\Client();\n $response = $client->get($url, [\n 'headers' => $headers,\n 'timeout' => 30,\n ]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $statusCode = $response->getStatusCode();\n $this->recordResult('Current System v2 API', $statusCode === 200, \"Status: {$statusCode} in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetDeals(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealsApi(null, $config);\n // Official SDK uses getDealsSummary instead of getDeals\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', true, \"Retrieved deals summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersons(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getPersons method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivities(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getActivities method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testFieldMetadata(SocialAccount $socialAccount): void\n {\n $this->info('Test 3: Field Metadata');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n // Test 3a: Get Deal Fields\n $this->testGetDealFields($config);\n\n // Test 3b: Get Person Fields\n $this->testGetPersonFields($config);\n\n // Test 3c: Get Activity Fields\n $this->testGetActivityFields($config);\n } catch (Exception $e) {\n $this->recordResult('Field Metadata', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testGetDealFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealFieldsApi(null, $config);\n $result = $apiInstance->getDealFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Deal Fields', true, \"Retrieved {$count} deal fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersonFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonFieldsApi(null, $config);\n $result = $apiInstance->getPersonFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Person Fields', true, \"Retrieved {$count} person fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivityFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivityFieldsApi(null, $config);\n $result = $apiInstance->getActivityFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Activity Fields', true, \"Retrieved {$count} activity fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testV2ApiSupport(SocialAccount $socialAccount): void\n {\n $this->info('Test 4: v2 API Support (Cursor-based Pagination)');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $startTime = microtime(true);\n\n // Test v2 deals endpoint with cursor pagination\n // Note: The official SDK may or may not have v2 API support\n // This test will determine if v2 is available\n try {\n $apiInstance = new DealsApi(null, $config);\n $updatedSince = Carbon::now()->subDays(30)->toRfc3339String();\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n\n // Check if response has cursor information (v2 indicator)\n // The response object doesn't have getAdditionalData method\n // Just test if the call works - v2 API support is limited in official SDK v1\n $this->recordResult('v2 API Support', false, \"v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);\n }\n } catch (Exception $e) {\n $this->recordResult('v2 API Support', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testErrorHandling(SocialAccount $socialAccount): void\n {\n $this->info('Test 5: Error Handling');\n\n try {\n // Test 5a: Invalid Token\n $this->testInvalidToken();\n\n // Test 5b: Non-existent Resource\n $this->testNonExistentResource($socialAccount);\n } catch (Exception $e) {\n $this->recordResult('Error Handling', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testInvalidToken(): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken('invalid_token');\n $client = new DealsApi(null, $config);\n\n $client->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function testNonExistentResource(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n $client = new DealsApi(null, $config);\n\n // Try to get a deal with a very high ID that likely doesn't exist\n // Note: Official SDK doesn't have a simple getDeal method, so skip this\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function recordResult(string $testName, bool $success, string $message, float $elapsed): void\n {\n $this->results[] = [\n 'test' => $testName,\n 'success' => $success,\n 'message' => $message,\n 'elapsed' => $elapsed,\n ];\n\n $status = $success ? '✅ PASS' : '❌ FAIL';\n $this->line(\" {$status} - {$testName}: {$message}\");\n }\n\n private function displayResults(): void\n {\n $this->newLine();\n $this->info('=== POC Test Results Summary ===');\n $this->newLine();\n\n $total = count($this->results);\n $passed = count(array_filter($this->results, fn ($r) => $r['success']));\n $failed = $total - $passed;\n\n $this->line(\"Total Tests: {$total}\");\n $this->line(\"Passed: {$passed}\");\n $this->line(\"Failed: {$failed}\");\n $this->newLine();\n\n $totalTime = array_sum(array_column($this->results, 'elapsed'));\n $this->line(\"Total Execution Time: {$totalTime}ms\");\n $this->newLine();\n\n if ($failed > 0) {\n $this->warn('Failed Tests:');\n foreach ($this->results as $result) {\n if (! $result['success']) {\n $this->line(\" - {$result['test']}: {$result['message']}\");\n }\n }\n $this->newLine();\n }\n\n $this->info('=== Detailed Results ===');\n foreach ($this->results as $result) {\n $status = $result['success'] ? '✅' : '❌';\n $this->line(\"{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}\");\n }\n\n $this->newLine();\n $this->info('=== Recommendations ===');\n\n if ($passed === $total) {\n $this->info('✅ All tests passed! The official SDK is viable for migration.');\n $this->info('Next steps: Proceed with full migration planning.');\n } else {\n $this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');\n $this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Console\\Commands\\Crm;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Console\\Command;\nuse Jiminny\\Component\\Encryption\\EncryptedTokenManager;\nuse Jiminny\\Models\\SocialAccount;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Pipedrive\\versions\\v1\\Api\\ActivitiesApi;\nuse Pipedrive\\versions\\v1\\Api\\ActivityFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonsApi;\nuse Pipedrive\\versions\\v1\\Configuration;\n\nclass TestPipedriveOfficialSdkCommand extends Command\n{\n protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';\n\n protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';\n\n private array $results = [];\n\n private EncryptedTokenManager $tokenManager;\n\n public function __construct(EncryptedTokenManager $tokenManager)\n {\n parent::__construct();\n $this->tokenManager = $tokenManager;\n }\n\n public function handle(): int\n {\n $teamId = (int) $this->argument('teamId');\n\n $this->info(\"Starting Pipedrive Official SDK POC for Team ID: {$teamId}\");\n $this->newLine();\n\n try {\n $this->runTests($teamId);\n } catch (Exception $e) {\n $this->error(\"POC failed with error: {$e->getMessage()}\");\n $this->error($e->getTraceAsString());\n\n return 1;\n }\n\n $this->displayResults();\n\n return 0;\n }\n\n private function runTests(int $teamId): void\n {\n $team = Team::find($teamId);\n if (! $team) {\n throw new Exception(\"Team with ID {$teamId} not found\");\n }\n\n $socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)\n ->whereHas('sociable', function ($query) use ($team) {\n $query->where('team_id', $team->id);\n })\n ->orderByDesc('expires') // Get the most recently expiring token\n ->first();\n\n if (! $socialAccount) {\n throw new Exception(\"No Pipedrive social account found for team {$teamId}\");\n }\n\n $this->info(\"Found Pipedrive account for team: {$team->name}\");\n $this->newLine();\n\n // Test 1: OAuth Initialization\n $this->testOAuthInitialization($socialAccount);\n\n // Test 2: Basic CRUD Operations\n $this->testBasicCrudOperations($socialAccount);\n\n // Test 3: Field Metadata\n $this->testFieldMetadata($socialAccount);\n\n // Test 4: v2 API Support\n $this->testV2ApiSupport($socialAccount);\n\n // Test 5: Error Handling\n $this->testErrorHandling($socialAccount);\n }\n\n private function testOAuthInitialization(SocialAccount $socialAccount): void\n {\n $this->info('Test 1: OAuth Initialization');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $client = new DealsApi(null, $config);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', true, \"Client initialized successfully in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);\n }\n\n $this->newLine();\n }\n\n private function testBasicCrudOperations(SocialAccount $socialAccount): void\n {\n $this->info('Test 2: Basic CRUD Operations');\n\n try {\n // Get the raw token from database\n $rawDbToken = $socialAccount->getProviderUserToken();\n $this->info(\"DB token (length: \" . strlen($rawDbToken) . \")\");\n $this->info(\"DB token preview: \" . substr($rawDbToken, 0, 20) . \"...\");\n\n // Check token expiry\n $expires = $socialAccount->expires;\n $this->info(\"Token expires at: \" . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));\n $this->info(\"Token is expired: \" . ($expires && $expires < time() ? 'YES' : 'NO'));\n\n // Test with the DB token directly\n $this->info(\"\\n--- Testing with DB token ---\");\n $config = new Configuration();\n $config->setAccessToken($rawDbToken);\n\n // Test 2a: Get Deals\n $this->testGetDeals($config);\n\n // Test 2b: Get Persons\n $this->testGetPersons($config);\n\n // Test 2c: Get Activities\n $this->testGetActivities($config);\n } catch (Exception $e) {\n $this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testCurrentSystemApproach(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n // Simulate how current system makes requests\n $token = $socialAccount->getProviderUserToken();\n $url = 'https://api.pipedrive.com/api/v2/deals';\n\n $headers = [\n 'Authorization' => 'Bearer ' . $token,\n 'Accept' => 'application/json',\n ];\n\n $client = new \\GuzzleHttp\\Client();\n $response = $client->get($url, [\n 'headers' => $headers,\n 'timeout' => 30,\n ]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $statusCode = $response->getStatusCode();\n $this->recordResult('Current System v2 API', $statusCode === 200, \"Status: {$statusCode} in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetDeals(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealsApi(null, $config);\n // Official SDK uses getDealsSummary instead of getDeals\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', true, \"Retrieved deals summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersons(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getPersons method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivities(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getActivities method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testFieldMetadata(SocialAccount $socialAccount): void\n {\n $this->info('Test 3: Field Metadata');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n // Test 3a: Get Deal Fields\n $this->testGetDealFields($config);\n\n // Test 3b: Get Person Fields\n $this->testGetPersonFields($config);\n\n // Test 3c: Get Activity Fields\n $this->testGetActivityFields($config);\n } catch (Exception $e) {\n $this->recordResult('Field Metadata', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testGetDealFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealFieldsApi(null, $config);\n $result = $apiInstance->getDealFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Deal Fields', true, \"Retrieved {$count} deal fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersonFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonFieldsApi(null, $config);\n $result = $apiInstance->getPersonFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Person Fields', true, \"Retrieved {$count} person fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivityFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivityFieldsApi(null, $config);\n $result = $apiInstance->getActivityFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Activity Fields', true, \"Retrieved {$count} activity fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testV2ApiSupport(SocialAccount $socialAccount): void\n {\n $this->info('Test 4: v2 API Support (Cursor-based Pagination)');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $startTime = microtime(true);\n\n // Test v2 deals endpoint with cursor pagination\n // Note: The official SDK may or may not have v2 API support\n // This test will determine if v2 is available\n try {\n $apiInstance = new DealsApi(null, $config);\n $updatedSince = Carbon::now()->subDays(30)->toRfc3339String();\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n\n // Check if response has cursor information (v2 indicator)\n // The response object doesn't have getAdditionalData method\n // Just test if the call works - v2 API support is limited in official SDK v1\n $this->recordResult('v2 API Support', false, \"v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);\n }\n } catch (Exception $e) {\n $this->recordResult('v2 API Support', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testErrorHandling(SocialAccount $socialAccount): void\n {\n $this->info('Test 5: Error Handling');\n\n try {\n // Test 5a: Invalid Token\n $this->testInvalidToken();\n\n // Test 5b: Non-existent Resource\n $this->testNonExistentResource($socialAccount);\n } catch (Exception $e) {\n $this->recordResult('Error Handling', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testInvalidToken(): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken('invalid_token');\n $client = new DealsApi(null, $config);\n\n $client->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function testNonExistentResource(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n $client = new DealsApi(null, $config);\n\n // Try to get a deal with a very high ID that likely doesn't exist\n // Note: Official SDK doesn't have a simple getDeal method, so skip this\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function recordResult(string $testName, bool $success, string $message, float $elapsed): void\n {\n $this->results[] = [\n 'test' => $testName,\n 'success' => $success,\n 'message' => $message,\n 'elapsed' => $elapsed,\n ];\n\n $status = $success ? '✅ PASS' : '❌ FAIL';\n $this->line(\" {$status} - {$testName}: {$message}\");\n }\n\n private function displayResults(): void\n {\n $this->newLine();\n $this->info('=== POC Test Results Summary ===');\n $this->newLine();\n\n $total = count($this->results);\n $passed = count(array_filter($this->results, fn ($r) => $r['success']));\n $failed = $total - $passed;\n\n $this->line(\"Total Tests: {$total}\");\n $this->line(\"Passed: {$passed}\");\n $this->line(\"Failed: {$failed}\");\n $this->newLine();\n\n $totalTime = array_sum(array_column($this->results, 'elapsed'));\n $this->line(\"Total Execution Time: {$totalTime}ms\");\n $this->newLine();\n\n if ($failed > 0) {\n $this->warn('Failed Tests:');\n foreach ($this->results as $result) {\n if (! $result['success']) {\n $this->line(\" - {$result['test']}: {$result['message']}\");\n }\n }\n $this->newLine();\n }\n\n $this->info('=== Detailed Results ===');\n foreach ($this->results as $result) {\n $status = $result['success'] ? '✅' : '❌';\n $this->line(\"{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}\");\n }\n\n $this->newLine();\n $this->info('=== Recommendations ===');\n\n if ($passed === $total) {\n $this->info('✅ All tests passed! The official SDK is viable for migration.');\n $this->info('Next steps: Proceed with full migration planning.');\n } else {\n $this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');\n $this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
5365831007722038384
|
61370652661533572
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
1
51
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Jiminny\Component\Encryption\EncryptedTokenManager;
use Jiminny\Models\SocialAccount;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Pipedrive\versions\v1\Api\ActivitiesApi;
use Pipedrive\versions\v1\Api\ActivityFieldsApi;
use Pipedrive\versions\v1\Api\DealFieldsApi;
use Pipedrive\versions\v1\Api\DealsApi;
use Pipedrive\versions\v1\Api\PersonFieldsApi;
use Pipedrive\versions\v1\Api\PersonsApi;
use Pipedrive\versions\v1\Configuration;
class TestPipedriveOfficialSdkCommand extends Command
{
protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';
protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';
private array $results = [];
private EncryptedTokenManager $tokenManager;
public function __construct(EncryptedTokenManager $tokenManager)
{
parent::__construct();
$this->tokenManager = $tokenManager;
}
public function handle(): int
{
$teamId = (int) $this->argument('teamId');
$this->info("Starting Pipedrive Official SDK POC for Team ID: {$teamId}");
$this->newLine();
try {
$this->runTests($teamId);
} catch (Exception $e) {
$this->error("POC failed with error: {$e->getMessage()}");
$this->error($e->getTraceAsString());
return 1;
}
$this->displayResults();
return 0;
}
private function runTests(int $teamId): void
{
$team = Team::find($teamId);
if (! $team) {
throw new Exception("Team with ID {$teamId} not found");
}
$socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)
->whereHas('sociable', function ($query) use ($team) {
$query->where('team_id', $team->id);
})
->orderByDesc('expires') // Get the most recently expiring token
->first();
if (! $socialAccount) {
throw new Exception("No Pipedrive social account found for team {$teamId}");
}
$this->info("Found Pipedrive account for team: {$team->name}");
$this->newLine();
// Test 1: OAuth Initialization
$this->testOAuthInitialization($socialAccount);
// Test 2: Basic CRUD Operations
$this->testBasicCrudOperations($socialAccount);
// Test 3: Field Metadata
$this->testFieldMetadata($socialAccount);
// Test 4: v2 API Support
$this->testV2ApiSupport($socialAccount);
// Test 5: Error Handling
$this->testErrorHandling($socialAccount);
}
private function testOAuthInitialization(SocialAccount $socialAccount): void
{
$this->info('Test 1: OAuth Initialization');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', true, "Client initialized successfully in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);
}
$this->newLine();
}
private function testBasicCrudOperations(SocialAccount $socialAccount): void
{
$this->info('Test 2: Basic CRUD Operations');
try {
// Get the raw token from database
$rawDbToken = $socialAccount->getProviderUserToken();
$this->info("DB token (length: " . strlen($rawDbToken) . ")");
$this->info("DB token preview: " . substr($rawDbToken, 0, 20) . "...");
// Check token expiry
$expires = $socialAccount->expires;
$this->info("Token expires at: " . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));
$this->info("Token is expired: " . ($expires && $expires < time() ? 'YES' : 'NO'));
// Test with the DB token directly
$this->info("\n--- Testing with DB token ---");
$config = new Configuration();
$config->setAccessToken($rawDbToken);
// Test 2a: Get Deals
$this->testGetDeals($config);
// Test 2b: Get Persons
$this->testGetPersons($config);
// Test 2c: Get Activities
$this->testGetActivities($config);
} catch (Exception $e) {
$this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testCurrentSystemApproach(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
// Simulate how current system makes requests
$token = $socialAccount->getProviderUserToken();
$url = 'https://api.pipedrive.com/api/v2/deals';
$headers = [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
];
$client = new \GuzzleHttp\Client();
$response = $client->get($url, [
'headers' => $headers,
'timeout' => 30,
]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$statusCode = $response->getStatusCode();
$this->recordResult('Current System v2 API', $statusCode === 200, "Status: {$statusCode} in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);
}
}
private function testGetDeals(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealsApi(null, $config);
// Official SDK uses getDealsSummary instead of getDeals
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', true, "Retrieved deals summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersons(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getPersons method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivities(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getActivities method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);
}
}
private function testFieldMetadata(SocialAccount $socialAccount): void
{
$this->info('Test 3: Field Metadata');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
// Test 3a: Get Deal Fields
$this->testGetDealFields($config);
// Test 3b: Get Person Fields
$this->testGetPersonFields($config);
// Test 3c: Get Activity Fields
$this->testGetActivityFields($config);
} catch (Exception $e) {
$this->recordResult('Field Metadata', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testGetDealFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealFieldsApi(null, $config);
$result = $apiInstance->getDealFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Deal Fields', true, "Retrieved {$count} deal fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersonFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new PersonFieldsApi(null, $config);
$result = $apiInstance->getPersonFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Person Fields', true, "Retrieved {$count} person fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivityFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new ActivityFieldsApi(null, $config);
$result = $apiInstance->getActivityFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Activity Fields', true, "Retrieved {$count} activity fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);
}
}
private function testV2ApiSupport(SocialAccount $socialAccount): void
{
$this->info('Test 4: v2 API Support (Cursor-based Pagination)');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$startTime = microtime(true);
// Test v2 deals endpoint with cursor pagination
// Note: The official SDK may or may not have v2 API support
// This test will determine if v2 is available
try {
$apiInstance = new DealsApi(null, $config);
$updatedSince = Carbon::now()->subDays(30)->toRfc3339String();
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
// Check if response has cursor information (v2 indicator)
// The response object doesn't have getAdditionalData method
// Just test if the call works - v2 API support is limited in official SDK v1
$this->recordResult('v2 API Support', false, "v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);
}
} catch (Exception $e) {
$this->recordResult('v2 API Support', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testErrorHandling(SocialAccount $socialAccount): void
{
$this->info('Test 5: Error Handling');
try {
// Test 5a: Invalid Token
$this->testInvalidToken();
// Test 5b: Non-existent Resource
$this->testNonExistentResource($socialAccount);
} catch (Exception $e) {
$this->recordResult('Error Handling', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testInvalidToken(): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken('invalid_token');
$client = new DealsApi(null, $config);
$client->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function testNonExistentResource(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
// Try to get a deal with a very high ID that likely doesn't exist
// Note: Official SDK doesn't have a simple getDeal method, so skip this
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function recordResult(string $testName, bool $success, string $message, float $elapsed): void
{
$this->results[] = [
'test' => $testName,
'success' => $success,
'message' => $message,
'elapsed' => $elapsed,
];
$status = $success ? '✅ PASS' : '❌ FAIL';
$this->line(" {$status} - {$testName}: {$message}");
}
private function displayResults(): void
{
$this->newLine();
$this->info('=== POC Test Results Summary ===');
$this->newLine();
$total = count($this->results);
$passed = count(array_filter($this->results, fn ($r) => $r['success']));
$failed = $total - $passed;
$this->line("Total Tests: {$total}");
$this->line("Passed: {$passed}");
$this->line("Failed: {$failed}");
$this->newLine();
$totalTime = array_sum(array_column($this->results, 'elapsed'));
$this->line("Total Execution Time: {$totalTime}ms");
$this->newLine();
if ($failed > 0) {
$this->warn('Failed Tests:');
foreach ($this->results as $result) {
if (! $result['success']) {
$this->line(" - {$result['test']}: {$result['message']}");
}
}
$this->newLine();
}
$this->info('=== Detailed Results ===');
foreach ($this->results as $result) {
$status = $result['success'] ? '✅' : '❌';
$this->line("{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}");
}
$this->newLine();
$this->info('=== Recommendations ===');
if ($passed === $total) {
$this->info('✅ All tests passed! The official SDK is viable for migration.');
$this->info('Next steps: Proceed with full migration planning.');
} else {
$this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');
$this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
49394
|
NULL
|
NULL
|
NULL
|
|
49401
|
1762
|
23
|
2026-05-18T06:41:22.045484+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086482045_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
1
51
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Jiminny\Component\Encryption\EncryptedTokenManager;
use Jiminny\Models\SocialAccount;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Pipedrive\versions\v1\Api\ActivitiesApi;
use Pipedrive\versions\v1\Api\ActivityFieldsApi;
use Pipedrive\versions\v1\Api\DealFieldsApi;
use Pipedrive\versions\v1\Api\DealsApi;
use Pipedrive\versions\v1\Api\PersonFieldsApi;
use Pipedrive\versions\v1\Api\PersonsApi;
use Pipedrive\versions\v1\Configuration;
class TestPipedriveOfficialSdkCommand extends Command
{
protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';
protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';
private array $results = [];
private EncryptedTokenManager $tokenManager;
public function __construct(EncryptedTokenManager $tokenManager)
{
parent::__construct();
$this->tokenManager = $tokenManager;
}
public function handle(): int
{
$teamId = (int) $this->argument('teamId');
$this->info("Starting Pipedrive Official SDK POC for Team ID: {$teamId}");
$this->newLine();
try {
$this->runTests($teamId);
} catch (Exception $e) {
$this->error("POC failed with error: {$e->getMessage()}");
$this->error($e->getTraceAsString());
return 1;
}
$this->displayResults();
return 0;
}
private function runTests(int $teamId): void
{
$team = Team::find($teamId);
if (! $team) {
throw new Exception("Team with ID {$teamId} not found");
}
$socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)
->whereHas('sociable', function ($query) use ($team) {
$query->where('team_id', $team->id);
})
->orderByDesc('expires') // Get the most recently expiring token
->first();
if (! $socialAccount) {
throw new Exception("No Pipedrive social account found for team {$teamId}");
}
$this->info("Found Pipedrive account for team: {$team->name}");
$this->newLine();
// Test 1: OAuth Initialization
$this->testOAuthInitialization($socialAccount);
// Test 2: Basic CRUD Operations
$this->testBasicCrudOperations($socialAccount);
// Test 3: Field Metadata
$this->testFieldMetadata($socialAccount);
// Test 4: v2 API Support
$this->testV2ApiSupport($socialAccount);
// Test 5: Error Handling
$this->testErrorHandling($socialAccount);
}
private function testOAuthInitialization(SocialAccount $socialAccount): void
{
$this->info('Test 1: OAuth Initialization');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', true, "Client initialized successfully in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);
}
$this->newLine();
}
private function testBasicCrudOperations(SocialAccount $socialAccount): void
{
$this->info('Test 2: Basic CRUD Operations');
try {
// Get the raw token from database
$rawDbToken = $socialAccount->getProviderUserToken();
$this->info("DB token (length: " . strlen($rawDbToken) . ")");
$this->info("DB token preview: " . substr($rawDbToken, 0, 20) . "...");
// Check token expiry
$expires = $socialAccount->expires;
$this->info("Token expires at: " . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));
$this->info("Token is expired: " . ($expires && $expires < time() ? 'YES' : 'NO'));
// Test with the DB token directly
$this->info("\n--- Testing with DB token ---");
$config = new Configuration();
$config->setAccessToken($rawDbToken);
// Test 2a: Get Deals
$this->testGetDeals($config);
// Test 2b: Get Persons
$this->testGetPersons($config);
// Test 2c: Get Activities
$this->testGetActivities($config);
} catch (Exception $e) {
$this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testCurrentSystemApproach(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
// Simulate how current system makes requests
$token = $socialAccount->getProviderUserToken();
$url = 'https://api.pipedrive.com/api/v2/deals';
$headers = [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
];
$client = new \GuzzleHttp\Client();
$response = $client->get($url, [
'headers' => $headers,
'timeout' => 30,
]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$statusCode = $response->getStatusCode();
$this->recordResult('Current System v2 API', $statusCode === 200, "Status: {$statusCode} in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);
}
}
private function testGetDeals(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealsApi(null, $config);
// Official SDK uses getDealsSummary instead of getDeals
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', true, "Retrieved deals summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersons(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getPersons method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivities(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getActivities method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);
}
}
private function testFieldMetadata(SocialAccount $socialAccount): void
{
$this->info('Test 3: Field Metadata');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
// Test 3a: Get Deal Fields
$this->testGetDealFields($config);
// Test 3b: Get Person Fields
$this->testGetPersonFields($config);
// Test 3c: Get Activity Fields
$this->testGetActivityFields($config);
} catch (Exception $e) {
$this->recordResult('Field Metadata', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testGetDealFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealFieldsApi(null, $config);
$result = $apiInstance->getDealFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Deal Fields', true, "Retrieved {$count} deal fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersonFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new PersonFieldsApi(null, $config);
$result = $apiInstance->getPersonFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Person Fields', true, "Retrieved {$count} person fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivityFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new ActivityFieldsApi(null, $config);
$result = $apiInstance->getActivityFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Activity Fields', true, "Retrieved {$count} activity fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);
}
}
private function testV2ApiSupport(SocialAccount $socialAccount): void
{
$this->info('Test 4: v2 API Support (Cursor-based Pagination)');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$startTime = microtime(true);
// Test v2 deals endpoint with cursor pagination
// Note: The official SDK may or may not have v2 API support
// This test will determine if v2 is available
try {
$apiInstance = new DealsApi(null, $config);
$updatedSince = Carbon::now()->subDays(30)->toRfc3339String();
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
// Check if response has cursor information (v2 indicator)
// The response object doesn't have getAdditionalData method
// Just test if the call works - v2 API support is limited in official SDK v1
$this->recordResult('v2 API Support', false, "v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);
}
} catch (Exception $e) {
$this->recordResult('v2 API Support', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testErrorHandling(SocialAccount $socialAccount): void
{
$this->info('Test 5: Error Handling');
try {
// Test 5a: Invalid Token
$this->testInvalidToken();
// Test 5b: Non-existent Resource
$this->testNonExistentResource($socialAccount);
} catch (Exception $e) {
$this->recordResult('Error Handling', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testInvalidToken(): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken('invalid_token');
$client = new DealsApi(null, $config);
$client->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function testNonExistentResource(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
// Try to get a deal with a very high ID that likely doesn't exist
// Note: Official SDK doesn't have a simple getDeal method, so skip this
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function recordResult(string $testName, bool $success, string $message, float $elapsed): void
{
$this->results[] = [
'test' => $testName,
'success' => $success,
'message' => $message,
'elapsed' => $elapsed,
];
$status = $success ? '✅ PASS' : '❌ FAIL';
$this->line(" {$status} - {$testName}: {$message}");
}
private function displayResults(): void
{
$this->newLine();
$this->info('=== POC Test Results Summary ===');
$this->newLine();
$total = count($this->results);
$passed = count(array_filter($this->results, fn ($r) => $r['success']));
$failed = $total - $passed;
$this->line("Total Tests: {$total}");
$this->line("Passed: {$passed}");
$this->line("Failed: {$failed}");
$this->newLine();
$totalTime = array_sum(array_column($this->results, 'elapsed'));
$this->line("Total Execution Time: {$totalTime}ms");
$this->newLine();
if ($failed > 0) {
$this->warn('Failed Tests:');
foreach ($this->results as $result) {
if (! $result['success']) {
$this->line(" - {$result['test']}: {$result['message']}");
}
}
$this->newLine();
}
$this->info('=== Detailed Results ===');
foreach ($this->results as $result) {
$status = $result['success'] ? '✅' : '❌';
$this->line("{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}");
}
$this->newLine();
$this->info('=== Recommendations ===');
if ($passed === $total) {
$this->info('✅ All tests passed! The official SDK is viable for migration.');
$this->info('Next steps: Proceed with full migration planning.');
} else {
$this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');
$this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"pipedrive-sdk-poc, menu","depth":5,"on_screen":true,"help_text":"Git Branch: pipedrive-sdk-poc","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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","role_description":"text entry area","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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"51","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"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\\Console\\Commands\\Crm;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Console\\Command;\nuse Jiminny\\Component\\Encryption\\EncryptedTokenManager;\nuse Jiminny\\Models\\SocialAccount;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Pipedrive\\versions\\v1\\Api\\ActivitiesApi;\nuse Pipedrive\\versions\\v1\\Api\\ActivityFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonsApi;\nuse Pipedrive\\versions\\v1\\Configuration;\n\nclass TestPipedriveOfficialSdkCommand extends Command\n{\n protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';\n\n protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';\n\n private array $results = [];\n\n private EncryptedTokenManager $tokenManager;\n\n public function __construct(EncryptedTokenManager $tokenManager)\n {\n parent::__construct();\n $this->tokenManager = $tokenManager;\n }\n\n public function handle(): int\n {\n $teamId = (int) $this->argument('teamId');\n\n $this->info(\"Starting Pipedrive Official SDK POC for Team ID: {$teamId}\");\n $this->newLine();\n\n try {\n $this->runTests($teamId);\n } catch (Exception $e) {\n $this->error(\"POC failed with error: {$e->getMessage()}\");\n $this->error($e->getTraceAsString());\n\n return 1;\n }\n\n $this->displayResults();\n\n return 0;\n }\n\n private function runTests(int $teamId): void\n {\n $team = Team::find($teamId);\n if (! $team) {\n throw new Exception(\"Team with ID {$teamId} not found\");\n }\n\n $socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)\n ->whereHas('sociable', function ($query) use ($team) {\n $query->where('team_id', $team->id);\n })\n ->orderByDesc('expires') // Get the most recently expiring token\n ->first();\n\n if (! $socialAccount) {\n throw new Exception(\"No Pipedrive social account found for team {$teamId}\");\n }\n\n $this->info(\"Found Pipedrive account for team: {$team->name}\");\n $this->newLine();\n\n // Test 1: OAuth Initialization\n $this->testOAuthInitialization($socialAccount);\n\n // Test 2: Basic CRUD Operations\n $this->testBasicCrudOperations($socialAccount);\n\n // Test 3: Field Metadata\n $this->testFieldMetadata($socialAccount);\n\n // Test 4: v2 API Support\n $this->testV2ApiSupport($socialAccount);\n\n // Test 5: Error Handling\n $this->testErrorHandling($socialAccount);\n }\n\n private function testOAuthInitialization(SocialAccount $socialAccount): void\n {\n $this->info('Test 1: OAuth Initialization');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $client = new DealsApi(null, $config);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', true, \"Client initialized successfully in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);\n }\n\n $this->newLine();\n }\n\n private function testBasicCrudOperations(SocialAccount $socialAccount): void\n {\n $this->info('Test 2: Basic CRUD Operations');\n\n try {\n // Get the raw token from database\n $rawDbToken = $socialAccount->getProviderUserToken();\n $this->info(\"DB token (length: \" . strlen($rawDbToken) . \")\");\n $this->info(\"DB token preview: \" . substr($rawDbToken, 0, 20) . \"...\");\n\n // Check token expiry\n $expires = $socialAccount->expires;\n $this->info(\"Token expires at: \" . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));\n $this->info(\"Token is expired: \" . ($expires && $expires < time() ? 'YES' : 'NO'));\n\n // Test with the DB token directly\n $this->info(\"\\n--- Testing with DB token ---\");\n $config = new Configuration();\n $config->setAccessToken($rawDbToken);\n\n // Test 2a: Get Deals\n $this->testGetDeals($config);\n\n // Test 2b: Get Persons\n $this->testGetPersons($config);\n\n // Test 2c: Get Activities\n $this->testGetActivities($config);\n } catch (Exception $e) {\n $this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testCurrentSystemApproach(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n // Simulate how current system makes requests\n $token = $socialAccount->getProviderUserToken();\n $url = 'https://api.pipedrive.com/api/v2/deals';\n\n $headers = [\n 'Authorization' => 'Bearer ' . $token,\n 'Accept' => 'application/json',\n ];\n\n $client = new \\GuzzleHttp\\Client();\n $response = $client->get($url, [\n 'headers' => $headers,\n 'timeout' => 30,\n ]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $statusCode = $response->getStatusCode();\n $this->recordResult('Current System v2 API', $statusCode === 200, \"Status: {$statusCode} in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetDeals(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealsApi(null, $config);\n // Official SDK uses getDealsSummary instead of getDeals\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', true, \"Retrieved deals summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersons(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getPersons method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivities(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getActivities method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testFieldMetadata(SocialAccount $socialAccount): void\n {\n $this->info('Test 3: Field Metadata');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n // Test 3a: Get Deal Fields\n $this->testGetDealFields($config);\n\n // Test 3b: Get Person Fields\n $this->testGetPersonFields($config);\n\n // Test 3c: Get Activity Fields\n $this->testGetActivityFields($config);\n } catch (Exception $e) {\n $this->recordResult('Field Metadata', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testGetDealFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealFieldsApi(null, $config);\n $result = $apiInstance->getDealFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Deal Fields', true, \"Retrieved {$count} deal fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersonFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonFieldsApi(null, $config);\n $result = $apiInstance->getPersonFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Person Fields', true, \"Retrieved {$count} person fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivityFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivityFieldsApi(null, $config);\n $result = $apiInstance->getActivityFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Activity Fields', true, \"Retrieved {$count} activity fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testV2ApiSupport(SocialAccount $socialAccount): void\n {\n $this->info('Test 4: v2 API Support (Cursor-based Pagination)');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $startTime = microtime(true);\n\n // Test v2 deals endpoint with cursor pagination\n // Note: The official SDK may or may not have v2 API support\n // This test will determine if v2 is available\n try {\n $apiInstance = new DealsApi(null, $config);\n $updatedSince = Carbon::now()->subDays(30)->toRfc3339String();\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n\n // Check if response has cursor information (v2 indicator)\n // The response object doesn't have getAdditionalData method\n // Just test if the call works - v2 API support is limited in official SDK v1\n $this->recordResult('v2 API Support', false, \"v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);\n }\n } catch (Exception $e) {\n $this->recordResult('v2 API Support', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testErrorHandling(SocialAccount $socialAccount): void\n {\n $this->info('Test 5: Error Handling');\n\n try {\n // Test 5a: Invalid Token\n $this->testInvalidToken();\n\n // Test 5b: Non-existent Resource\n $this->testNonExistentResource($socialAccount);\n } catch (Exception $e) {\n $this->recordResult('Error Handling', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testInvalidToken(): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken('invalid_token');\n $client = new DealsApi(null, $config);\n\n $client->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function testNonExistentResource(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n $client = new DealsApi(null, $config);\n\n // Try to get a deal with a very high ID that likely doesn't exist\n // Note: Official SDK doesn't have a simple getDeal method, so skip this\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function recordResult(string $testName, bool $success, string $message, float $elapsed): void\n {\n $this->results[] = [\n 'test' => $testName,\n 'success' => $success,\n 'message' => $message,\n 'elapsed' => $elapsed,\n ];\n\n $status = $success ? '✅ PASS' : '❌ FAIL';\n $this->line(\" {$status} - {$testName}: {$message}\");\n }\n\n private function displayResults(): void\n {\n $this->newLine();\n $this->info('=== POC Test Results Summary ===');\n $this->newLine();\n\n $total = count($this->results);\n $passed = count(array_filter($this->results, fn ($r) => $r['success']));\n $failed = $total - $passed;\n\n $this->line(\"Total Tests: {$total}\");\n $this->line(\"Passed: {$passed}\");\n $this->line(\"Failed: {$failed}\");\n $this->newLine();\n\n $totalTime = array_sum(array_column($this->results, 'elapsed'));\n $this->line(\"Total Execution Time: {$totalTime}ms\");\n $this->newLine();\n\n if ($failed > 0) {\n $this->warn('Failed Tests:');\n foreach ($this->results as $result) {\n if (! $result['success']) {\n $this->line(\" - {$result['test']}: {$result['message']}\");\n }\n }\n $this->newLine();\n }\n\n $this->info('=== Detailed Results ===');\n foreach ($this->results as $result) {\n $status = $result['success'] ? '✅' : '❌';\n $this->line(\"{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}\");\n }\n\n $this->newLine();\n $this->info('=== Recommendations ===');\n\n if ($passed === $total) {\n $this->info('✅ All tests passed! The official SDK is viable for migration.');\n $this->info('Next steps: Proceed with full migration planning.');\n } else {\n $this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');\n $this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Console\\Commands\\Crm;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Console\\Command;\nuse Jiminny\\Component\\Encryption\\EncryptedTokenManager;\nuse Jiminny\\Models\\SocialAccount;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Pipedrive\\versions\\v1\\Api\\ActivitiesApi;\nuse Pipedrive\\versions\\v1\\Api\\ActivityFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonsApi;\nuse Pipedrive\\versions\\v1\\Configuration;\n\nclass TestPipedriveOfficialSdkCommand extends Command\n{\n protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';\n\n protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';\n\n private array $results = [];\n\n private EncryptedTokenManager $tokenManager;\n\n public function __construct(EncryptedTokenManager $tokenManager)\n {\n parent::__construct();\n $this->tokenManager = $tokenManager;\n }\n\n public function handle(): int\n {\n $teamId = (int) $this->argument('teamId');\n\n $this->info(\"Starting Pipedrive Official SDK POC for Team ID: {$teamId}\");\n $this->newLine();\n\n try {\n $this->runTests($teamId);\n } catch (Exception $e) {\n $this->error(\"POC failed with error: {$e->getMessage()}\");\n $this->error($e->getTraceAsString());\n\n return 1;\n }\n\n $this->displayResults();\n\n return 0;\n }\n\n private function runTests(int $teamId): void\n {\n $team = Team::find($teamId);\n if (! $team) {\n throw new Exception(\"Team with ID {$teamId} not found\");\n }\n\n $socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)\n ->whereHas('sociable', function ($query) use ($team) {\n $query->where('team_id', $team->id);\n })\n ->orderByDesc('expires') // Get the most recently expiring token\n ->first();\n\n if (! $socialAccount) {\n throw new Exception(\"No Pipedrive social account found for team {$teamId}\");\n }\n\n $this->info(\"Found Pipedrive account for team: {$team->name}\");\n $this->newLine();\n\n // Test 1: OAuth Initialization\n $this->testOAuthInitialization($socialAccount);\n\n // Test 2: Basic CRUD Operations\n $this->testBasicCrudOperations($socialAccount);\n\n // Test 3: Field Metadata\n $this->testFieldMetadata($socialAccount);\n\n // Test 4: v2 API Support\n $this->testV2ApiSupport($socialAccount);\n\n // Test 5: Error Handling\n $this->testErrorHandling($socialAccount);\n }\n\n private function testOAuthInitialization(SocialAccount $socialAccount): void\n {\n $this->info('Test 1: OAuth Initialization');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $client = new DealsApi(null, $config);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', true, \"Client initialized successfully in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);\n }\n\n $this->newLine();\n }\n\n private function testBasicCrudOperations(SocialAccount $socialAccount): void\n {\n $this->info('Test 2: Basic CRUD Operations');\n\n try {\n // Get the raw token from database\n $rawDbToken = $socialAccount->getProviderUserToken();\n $this->info(\"DB token (length: \" . strlen($rawDbToken) . \")\");\n $this->info(\"DB token preview: \" . substr($rawDbToken, 0, 20) . \"...\");\n\n // Check token expiry\n $expires = $socialAccount->expires;\n $this->info(\"Token expires at: \" . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));\n $this->info(\"Token is expired: \" . ($expires && $expires < time() ? 'YES' : 'NO'));\n\n // Test with the DB token directly\n $this->info(\"\\n--- Testing with DB token ---\");\n $config = new Configuration();\n $config->setAccessToken($rawDbToken);\n\n // Test 2a: Get Deals\n $this->testGetDeals($config);\n\n // Test 2b: Get Persons\n $this->testGetPersons($config);\n\n // Test 2c: Get Activities\n $this->testGetActivities($config);\n } catch (Exception $e) {\n $this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testCurrentSystemApproach(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n // Simulate how current system makes requests\n $token = $socialAccount->getProviderUserToken();\n $url = 'https://api.pipedrive.com/api/v2/deals';\n\n $headers = [\n 'Authorization' => 'Bearer ' . $token,\n 'Accept' => 'application/json',\n ];\n\n $client = new \\GuzzleHttp\\Client();\n $response = $client->get($url, [\n 'headers' => $headers,\n 'timeout' => 30,\n ]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $statusCode = $response->getStatusCode();\n $this->recordResult('Current System v2 API', $statusCode === 200, \"Status: {$statusCode} in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetDeals(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealsApi(null, $config);\n // Official SDK uses getDealsSummary instead of getDeals\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', true, \"Retrieved deals summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersons(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getPersons method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivities(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getActivities method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testFieldMetadata(SocialAccount $socialAccount): void\n {\n $this->info('Test 3: Field Metadata');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n // Test 3a: Get Deal Fields\n $this->testGetDealFields($config);\n\n // Test 3b: Get Person Fields\n $this->testGetPersonFields($config);\n\n // Test 3c: Get Activity Fields\n $this->testGetActivityFields($config);\n } catch (Exception $e) {\n $this->recordResult('Field Metadata', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testGetDealFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealFieldsApi(null, $config);\n $result = $apiInstance->getDealFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Deal Fields', true, \"Retrieved {$count} deal fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersonFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonFieldsApi(null, $config);\n $result = $apiInstance->getPersonFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Person Fields', true, \"Retrieved {$count} person fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivityFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivityFieldsApi(null, $config);\n $result = $apiInstance->getActivityFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Activity Fields', true, \"Retrieved {$count} activity fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testV2ApiSupport(SocialAccount $socialAccount): void\n {\n $this->info('Test 4: v2 API Support (Cursor-based Pagination)');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $startTime = microtime(true);\n\n // Test v2 deals endpoint with cursor pagination\n // Note: The official SDK may or may not have v2 API support\n // This test will determine if v2 is available\n try {\n $apiInstance = new DealsApi(null, $config);\n $updatedSince = Carbon::now()->subDays(30)->toRfc3339String();\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n\n // Check if response has cursor information (v2 indicator)\n // The response object doesn't have getAdditionalData method\n // Just test if the call works - v2 API support is limited in official SDK v1\n $this->recordResult('v2 API Support', false, \"v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);\n }\n } catch (Exception $e) {\n $this->recordResult('v2 API Support', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testErrorHandling(SocialAccount $socialAccount): void\n {\n $this->info('Test 5: Error Handling');\n\n try {\n // Test 5a: Invalid Token\n $this->testInvalidToken();\n\n // Test 5b: Non-existent Resource\n $this->testNonExistentResource($socialAccount);\n } catch (Exception $e) {\n $this->recordResult('Error Handling', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testInvalidToken(): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken('invalid_token');\n $client = new DealsApi(null, $config);\n\n $client->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function testNonExistentResource(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n $client = new DealsApi(null, $config);\n\n // Try to get a deal with a very high ID that likely doesn't exist\n // Note: Official SDK doesn't have a simple getDeal method, so skip this\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function recordResult(string $testName, bool $success, string $message, float $elapsed): void\n {\n $this->results[] = [\n 'test' => $testName,\n 'success' => $success,\n 'message' => $message,\n 'elapsed' => $elapsed,\n ];\n\n $status = $success ? '✅ PASS' : '❌ FAIL';\n $this->line(\" {$status} - {$testName}: {$message}\");\n }\n\n private function displayResults(): void\n {\n $this->newLine();\n $this->info('=== POC Test Results Summary ===');\n $this->newLine();\n\n $total = count($this->results);\n $passed = count(array_filter($this->results, fn ($r) => $r['success']));\n $failed = $total - $passed;\n\n $this->line(\"Total Tests: {$total}\");\n $this->line(\"Passed: {$passed}\");\n $this->line(\"Failed: {$failed}\");\n $this->newLine();\n\n $totalTime = array_sum(array_column($this->results, 'elapsed'));\n $this->line(\"Total Execution Time: {$totalTime}ms\");\n $this->newLine();\n\n if ($failed > 0) {\n $this->warn('Failed Tests:');\n foreach ($this->results as $result) {\n if (! $result['success']) {\n $this->line(\" - {$result['test']}: {$result['message']}\");\n }\n }\n $this->newLine();\n }\n\n $this->info('=== Detailed Results ===');\n foreach ($this->results as $result) {\n $status = $result['success'] ? '✅' : '❌';\n $this->line(\"{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}\");\n }\n\n $this->newLine();\n $this->info('=== Recommendations ===');\n\n if ($passed === $total) {\n $this->info('✅ All tests passed! The official SDK is viable for migration.');\n $this->info('Next steps: Proceed with full migration planning.');\n } else {\n $this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');\n $this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
5365831007722038384
|
61370652661533572
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
1
51
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Jiminny\Component\Encryption\EncryptedTokenManager;
use Jiminny\Models\SocialAccount;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Pipedrive\versions\v1\Api\ActivitiesApi;
use Pipedrive\versions\v1\Api\ActivityFieldsApi;
use Pipedrive\versions\v1\Api\DealFieldsApi;
use Pipedrive\versions\v1\Api\DealsApi;
use Pipedrive\versions\v1\Api\PersonFieldsApi;
use Pipedrive\versions\v1\Api\PersonsApi;
use Pipedrive\versions\v1\Configuration;
class TestPipedriveOfficialSdkCommand extends Command
{
protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';
protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';
private array $results = [];
private EncryptedTokenManager $tokenManager;
public function __construct(EncryptedTokenManager $tokenManager)
{
parent::__construct();
$this->tokenManager = $tokenManager;
}
public function handle(): int
{
$teamId = (int) $this->argument('teamId');
$this->info("Starting Pipedrive Official SDK POC for Team ID: {$teamId}");
$this->newLine();
try {
$this->runTests($teamId);
} catch (Exception $e) {
$this->error("POC failed with error: {$e->getMessage()}");
$this->error($e->getTraceAsString());
return 1;
}
$this->displayResults();
return 0;
}
private function runTests(int $teamId): void
{
$team = Team::find($teamId);
if (! $team) {
throw new Exception("Team with ID {$teamId} not found");
}
$socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)
->whereHas('sociable', function ($query) use ($team) {
$query->where('team_id', $team->id);
})
->orderByDesc('expires') // Get the most recently expiring token
->first();
if (! $socialAccount) {
throw new Exception("No Pipedrive social account found for team {$teamId}");
}
$this->info("Found Pipedrive account for team: {$team->name}");
$this->newLine();
// Test 1: OAuth Initialization
$this->testOAuthInitialization($socialAccount);
// Test 2: Basic CRUD Operations
$this->testBasicCrudOperations($socialAccount);
// Test 3: Field Metadata
$this->testFieldMetadata($socialAccount);
// Test 4: v2 API Support
$this->testV2ApiSupport($socialAccount);
// Test 5: Error Handling
$this->testErrorHandling($socialAccount);
}
private function testOAuthInitialization(SocialAccount $socialAccount): void
{
$this->info('Test 1: OAuth Initialization');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', true, "Client initialized successfully in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);
}
$this->newLine();
}
private function testBasicCrudOperations(SocialAccount $socialAccount): void
{
$this->info('Test 2: Basic CRUD Operations');
try {
// Get the raw token from database
$rawDbToken = $socialAccount->getProviderUserToken();
$this->info("DB token (length: " . strlen($rawDbToken) . ")");
$this->info("DB token preview: " . substr($rawDbToken, 0, 20) . "...");
// Check token expiry
$expires = $socialAccount->expires;
$this->info("Token expires at: " . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));
$this->info("Token is expired: " . ($expires && $expires < time() ? 'YES' : 'NO'));
// Test with the DB token directly
$this->info("\n--- Testing with DB token ---");
$config = new Configuration();
$config->setAccessToken($rawDbToken);
// Test 2a: Get Deals
$this->testGetDeals($config);
// Test 2b: Get Persons
$this->testGetPersons($config);
// Test 2c: Get Activities
$this->testGetActivities($config);
} catch (Exception $e) {
$this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testCurrentSystemApproach(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
// Simulate how current system makes requests
$token = $socialAccount->getProviderUserToken();
$url = 'https://api.pipedrive.com/api/v2/deals';
$headers = [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
];
$client = new \GuzzleHttp\Client();
$response = $client->get($url, [
'headers' => $headers,
'timeout' => 30,
]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$statusCode = $response->getStatusCode();
$this->recordResult('Current System v2 API', $statusCode === 200, "Status: {$statusCode} in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);
}
}
private function testGetDeals(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealsApi(null, $config);
// Official SDK uses getDealsSummary instead of getDeals
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', true, "Retrieved deals summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersons(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getPersons method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivities(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getActivities method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);
}
}
private function testFieldMetadata(SocialAccount $socialAccount): void
{
$this->info('Test 3: Field Metadata');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
// Test 3a: Get Deal Fields
$this->testGetDealFields($config);
// Test 3b: Get Person Fields
$this->testGetPersonFields($config);
// Test 3c: Get Activity Fields
$this->testGetActivityFields($config);
} catch (Exception $e) {
$this->recordResult('Field Metadata', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testGetDealFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealFieldsApi(null, $config);
$result = $apiInstance->getDealFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Deal Fields', true, "Retrieved {$count} deal fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersonFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new PersonFieldsApi(null, $config);
$result = $apiInstance->getPersonFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Person Fields', true, "Retrieved {$count} person fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivityFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new ActivityFieldsApi(null, $config);
$result = $apiInstance->getActivityFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Activity Fields', true, "Retrieved {$count} activity fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);
}
}
private function testV2ApiSupport(SocialAccount $socialAccount): void
{
$this->info('Test 4: v2 API Support (Cursor-based Pagination)');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$startTime = microtime(true);
// Test v2 deals endpoint with cursor pagination
// Note: The official SDK may or may not have v2 API support
// This test will determine if v2 is available
try {
$apiInstance = new DealsApi(null, $config);
$updatedSince = Carbon::now()->subDays(30)->toRfc3339String();
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
// Check if response has cursor information (v2 indicator)
// The response object doesn't have getAdditionalData method
// Just test if the call works - v2 API support is limited in official SDK v1
$this->recordResult('v2 API Support', false, "v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);
}
} catch (Exception $e) {
$this->recordResult('v2 API Support', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testErrorHandling(SocialAccount $socialAccount): void
{
$this->info('Test 5: Error Handling');
try {
// Test 5a: Invalid Token
$this->testInvalidToken();
// Test 5b: Non-existent Resource
$this->testNonExistentResource($socialAccount);
} catch (Exception $e) {
$this->recordResult('Error Handling', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testInvalidToken(): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken('invalid_token');
$client = new DealsApi(null, $config);
$client->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function testNonExistentResource(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
// Try to get a deal with a very high ID that likely doesn't exist
// Note: Official SDK doesn't have a simple getDeal method, so skip this
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function recordResult(string $testName, bool $success, string $message, float $elapsed): void
{
$this->results[] = [
'test' => $testName,
'success' => $success,
'message' => $message,
'elapsed' => $elapsed,
];
$status = $success ? '✅ PASS' : '❌ FAIL';
$this->line(" {$status} - {$testName}: {$message}");
}
private function displayResults(): void
{
$this->newLine();
$this->info('=== POC Test Results Summary ===');
$this->newLine();
$total = count($this->results);
$passed = count(array_filter($this->results, fn ($r) => $r['success']));
$failed = $total - $passed;
$this->line("Total Tests: {$total}");
$this->line("Passed: {$passed}");
$this->line("Failed: {$failed}");
$this->newLine();
$totalTime = array_sum(array_column($this->results, 'elapsed'));
$this->line("Total Execution Time: {$totalTime}ms");
$this->newLine();
if ($failed > 0) {
$this->warn('Failed Tests:');
foreach ($this->results as $result) {
if (! $result['success']) {
$this->line(" - {$result['test']}: {$result['message']}");
}
}
$this->newLine();
}
$this->info('=== Detailed Results ===');
foreach ($this->results as $result) {
$status = $result['success'] ? '✅' : '❌';
$this->line("{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}");
}
$this->newLine();
$this->info('=== Recommendations ===');
if ($passed === $total) {
$this->info('✅ All tests passed! The official SDK is viable for migration.');
$this->info('Next steps: Proceed with full migration planning.');
} else {
$this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');
$this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
49398
|
NULL
|
NULL
|
NULL
|
|
49402
|
1763
|
26
|
2026-05-18T06:41:47.716461+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086507716_m2.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
1
51
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Jiminny\Component\Encryption\EncryptedTokenManager;
use Jiminny\Models\SocialAccount;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Pipedrive\versions\v1\Api\ActivitiesApi;
use Pipedrive\versions\v1\Api\ActivityFieldsApi;
use Pipedrive\versions\v1\Api\DealFieldsApi;
use Pipedrive\versions\v1\Api\DealsApi;
use Pipedrive\versions\v1\Api\PersonFieldsApi;
use Pipedrive\versions\v1\Api\PersonsApi;
use Pipedrive\versions\v1\Configuration;
class TestPipedriveOfficialSdkCommand extends Command
{
protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';
protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';
private array $results = [];
private EncryptedTokenManager $tokenManager;
public function __construct(EncryptedTokenManager $tokenManager)
{
parent::__construct();
$this->tokenManager = $tokenManager;
}
public function handle(): int
{
$teamId = (int) $this->argument('teamId');
$this->info("Starting Pipedrive Official SDK POC for Team ID: {$teamId}");
$this->newLine();
try {
$this->runTests($teamId);
} catch (Exception $e) {
$this->error("POC failed with error: {$e->getMessage()}");
$this->error($e->getTraceAsString());
return 1;
}
$this->displayResults();
return 0;
}
private function runTests(int $teamId): void
{
$team = Team::find($teamId);
if (! $team) {
throw new Exception("Team with ID {$teamId} not found");
}
$socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)
->whereHas('sociable', function ($query) use ($team) {
$query->where('team_id', $team->id);
})
->orderByDesc('expires') // Get the most recently expiring token
->first();
if (! $socialAccount) {
throw new Exception("No Pipedrive social account found for team {$teamId}");
}
$this->info("Found Pipedrive account for team: {$team->name}");
$this->newLine();
// Test 1: OAuth Initialization
$this->testOAuthInitialization($socialAccount);
// Test 2: Basic CRUD Operations
$this->testBasicCrudOperations($socialAccount);
// Test 3: Field Metadata
$this->testFieldMetadata($socialAccount);
// Test 4: v2 API Support
$this->testV2ApiSupport($socialAccount);
// Test 5: Error Handling
$this->testErrorHandling($socialAccount);
}
private function testOAuthInitialization(SocialAccount $socialAccount): void
{
$this->info('Test 1: OAuth Initialization');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', true, "Client initialized successfully in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);
}
$this->newLine();
}
private function testBasicCrudOperations(SocialAccount $socialAccount): void
{
$this->info('Test 2: Basic CRUD Operations');
try {
// Get the raw token from database
$rawDbToken = $socialAccount->getProviderUserToken();
$this->info("DB token (length: " . strlen($rawDbToken) . ")");
$this->info("DB token preview: " . substr($rawDbToken, 0, 20) . "...");
// Check token expiry
$expires = $socialAccount->expires;
$this->info("Token expires at: " . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));
$this->info("Token is expired: " . ($expires && $expires < time() ? 'YES' : 'NO'));
// Test with the DB token directly
$this->info("\n--- Testing with DB token ---");
$config = new Configuration();
$config->setAccessToken($rawDbToken);
// Test 2a: Get Deals
$this->testGetDeals($config);
// Test 2b: Get Persons
$this->testGetPersons($config);
// Test 2c: Get Activities
$this->testGetActivities($config);
} catch (Exception $e) {
$this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testCurrentSystemApproach(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
// Simulate how current system makes requests
$token = $socialAccount->getProviderUserToken();
$url = 'https://api.pipedrive.com/api/v2/deals';
$headers = [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
];
$client = new \GuzzleHttp\Client();
$response = $client->get($url, [
'headers' => $headers,
'timeout' => 30,
]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$statusCode = $response->getStatusCode();
$this->recordResult('Current System v2 API', $statusCode === 200, "Status: {$statusCode} in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);
}
}
private function testGetDeals(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealsApi(null, $config);
// Official SDK uses getDealsSummary instead of getDeals
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', true, "Retrieved deals summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersons(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getPersons method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivities(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getActivities method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);
}
}
private function testFieldMetadata(SocialAccount $socialAccount): void
{
$this->info('Test 3: Field Metadata');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
// Test 3a: Get Deal Fields
$this->testGetDealFields($config);
// Test 3b: Get Person Fields
$this->testGetPersonFields($config);
// Test 3c: Get Activity Fields
$this->testGetActivityFields($config);
} catch (Exception $e) {
$this->recordResult('Field Metadata', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testGetDealFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealFieldsApi(null, $config);
$result = $apiInstance->getDealFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Deal Fields', true, "Retrieved {$count} deal fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersonFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new PersonFieldsApi(null, $config);
$result = $apiInstance->getPersonFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Person Fields', true, "Retrieved {$count} person fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivityFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new ActivityFieldsApi(null, $config);
$result = $apiInstance->getActivityFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Activity Fields', true, "Retrieved {$count} activity fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);
}
}
private function testV2ApiSupport(SocialAccount $socialAccount): void
{
$this->info('Test 4: v2 API Support (Cursor-based Pagination)');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$startTime = microtime(true);
// Test v2 deals endpoint with cursor pagination
// Note: The official SDK may or may not have v2 API support
// This test will determine if v2 is available
try {
$apiInstance = new DealsApi(null, $config);
$updatedSince = Carbon::now()->subDays(30)->toRfc3339String();
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
// Check if response has cursor information (v2 indicator)
// The response object doesn't have getAdditionalData method
// Just test if the call works - v2 API support is limited in official SDK v1
$this->recordResult('v2 API Support', false, "v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);
}
} catch (Exception $e) {
$this->recordResult('v2 API Support', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testErrorHandling(SocialAccount $socialAccount): void
{
$this->info('Test 5: Error Handling');
try {
// Test 5a: Invalid Token
$this->testInvalidToken();
// Test 5b: Non-existent Resource
$this->testNonExistentResource($socialAccount);
} catch (Exception $e) {
$this->recordResult('Error Handling', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testInvalidToken(): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken('invalid_token');
$client = new DealsApi(null, $config);
$client->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function testNonExistentResource(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
// Try to get a deal with a very high ID that likely doesn't exist
// Note: Official SDK doesn't have a simple getDeal method, so skip this
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function recordResult(string $testName, bool $success, string $message, float $elapsed): void
{
$this->results[] = [
'test' => $testName,
'success' => $success,
'message' => $message,
'elapsed' => $elapsed,
];
$status = $success ? '✅ PASS' : '❌ FAIL';
$this->line(" {$status} - {$testName}: {$message}");
}
private function displayResults(): void
{
$this->newLine();
$this->info('=== POC Test Results Summary ===');
$this->newLine();
$total = count($this->results);
$passed = count(array_filter($this->results, fn ($r) => $r['success']));
$failed = $total - $passed;
$this->line("Total Tests: {$total}");
$this->line("Passed: {$passed}");
$this->line("Failed: {$failed}");
$this->newLine();
$totalTime = array_sum(array_column($this->results, 'elapsed'));
$this->line("Total Execution Time: {$totalTime}ms");
$this->newLine();
if ($failed > 0) {
$this->warn('Failed Tests:');
foreach ($this->results as $result) {
if (! $result['success']) {
$this->line(" - {$result['test']}: {$result['message']}");
}
}
$this->newLine();
}
$this->info('=== Detailed Results ===');
foreach ($this->results as $result) {
$status = $result['success'] ? '✅' : '❌';
$this->line("{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}");
}
$this->newLine();
$this->info('=== Recommendations ===');
if ($passed === $total) {
$this->info('✅ All tests passed! The official SDK is viable for migration.');
$this->info('Next steps: Proceed with full migration planning.');
} else {
$this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');
$this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');
}
}
}
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.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"pipedrive-sdk-poc, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.05618351,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: pipedrive-sdk-poc","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.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","depth":4,"bounds":{"left":0.15724733,"top":0.2952913,"width":0.21476063,"height":0.7047087},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.57912236,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"51","depth":4,"bounds":{"left":0.5884308,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.6000665,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6090425,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"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.6163564,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"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\\Console\\Commands\\Crm;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Console\\Command;\nuse Jiminny\\Component\\Encryption\\EncryptedTokenManager;\nuse Jiminny\\Models\\SocialAccount;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Pipedrive\\versions\\v1\\Api\\ActivitiesApi;\nuse Pipedrive\\versions\\v1\\Api\\ActivityFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonsApi;\nuse Pipedrive\\versions\\v1\\Configuration;\n\nclass TestPipedriveOfficialSdkCommand extends Command\n{\n protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';\n\n protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';\n\n private array $results = [];\n\n private EncryptedTokenManager $tokenManager;\n\n public function __construct(EncryptedTokenManager $tokenManager)\n {\n parent::__construct();\n $this->tokenManager = $tokenManager;\n }\n\n public function handle(): int\n {\n $teamId = (int) $this->argument('teamId');\n\n $this->info(\"Starting Pipedrive Official SDK POC for Team ID: {$teamId}\");\n $this->newLine();\n\n try {\n $this->runTests($teamId);\n } catch (Exception $e) {\n $this->error(\"POC failed with error: {$e->getMessage()}\");\n $this->error($e->getTraceAsString());\n\n return 1;\n }\n\n $this->displayResults();\n\n return 0;\n }\n\n private function runTests(int $teamId): void\n {\n $team = Team::find($teamId);\n if (! $team) {\n throw new Exception(\"Team with ID {$teamId} not found\");\n }\n\n $socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)\n ->whereHas('sociable', function ($query) use ($team) {\n $query->where('team_id', $team->id);\n })\n ->orderByDesc('expires') // Get the most recently expiring token\n ->first();\n\n if (! $socialAccount) {\n throw new Exception(\"No Pipedrive social account found for team {$teamId}\");\n }\n\n $this->info(\"Found Pipedrive account for team: {$team->name}\");\n $this->newLine();\n\n // Test 1: OAuth Initialization\n $this->testOAuthInitialization($socialAccount);\n\n // Test 2: Basic CRUD Operations\n $this->testBasicCrudOperations($socialAccount);\n\n // Test 3: Field Metadata\n $this->testFieldMetadata($socialAccount);\n\n // Test 4: v2 API Support\n $this->testV2ApiSupport($socialAccount);\n\n // Test 5: Error Handling\n $this->testErrorHandling($socialAccount);\n }\n\n private function testOAuthInitialization(SocialAccount $socialAccount): void\n {\n $this->info('Test 1: OAuth Initialization');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $client = new DealsApi(null, $config);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', true, \"Client initialized successfully in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);\n }\n\n $this->newLine();\n }\n\n private function testBasicCrudOperations(SocialAccount $socialAccount): void\n {\n $this->info('Test 2: Basic CRUD Operations');\n\n try {\n // Get the raw token from database\n $rawDbToken = $socialAccount->getProviderUserToken();\n $this->info(\"DB token (length: \" . strlen($rawDbToken) . \")\");\n $this->info(\"DB token preview: \" . substr($rawDbToken, 0, 20) . \"...\");\n\n // Check token expiry\n $expires = $socialAccount->expires;\n $this->info(\"Token expires at: \" . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));\n $this->info(\"Token is expired: \" . ($expires && $expires < time() ? 'YES' : 'NO'));\n\n // Test with the DB token directly\n $this->info(\"\\n--- Testing with DB token ---\");\n $config = new Configuration();\n $config->setAccessToken($rawDbToken);\n\n // Test 2a: Get Deals\n $this->testGetDeals($config);\n\n // Test 2b: Get Persons\n $this->testGetPersons($config);\n\n // Test 2c: Get Activities\n $this->testGetActivities($config);\n } catch (Exception $e) {\n $this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testCurrentSystemApproach(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n // Simulate how current system makes requests\n $token = $socialAccount->getProviderUserToken();\n $url = 'https://api.pipedrive.com/api/v2/deals';\n\n $headers = [\n 'Authorization' => 'Bearer ' . $token,\n 'Accept' => 'application/json',\n ];\n\n $client = new \\GuzzleHttp\\Client();\n $response = $client->get($url, [\n 'headers' => $headers,\n 'timeout' => 30,\n ]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $statusCode = $response->getStatusCode();\n $this->recordResult('Current System v2 API', $statusCode === 200, \"Status: {$statusCode} in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetDeals(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealsApi(null, $config);\n // Official SDK uses getDealsSummary instead of getDeals\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', true, \"Retrieved deals summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersons(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getPersons method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivities(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getActivities method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testFieldMetadata(SocialAccount $socialAccount): void\n {\n $this->info('Test 3: Field Metadata');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n // Test 3a: Get Deal Fields\n $this->testGetDealFields($config);\n\n // Test 3b: Get Person Fields\n $this->testGetPersonFields($config);\n\n // Test 3c: Get Activity Fields\n $this->testGetActivityFields($config);\n } catch (Exception $e) {\n $this->recordResult('Field Metadata', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testGetDealFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealFieldsApi(null, $config);\n $result = $apiInstance->getDealFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Deal Fields', true, \"Retrieved {$count} deal fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersonFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonFieldsApi(null, $config);\n $result = $apiInstance->getPersonFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Person Fields', true, \"Retrieved {$count} person fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivityFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivityFieldsApi(null, $config);\n $result = $apiInstance->getActivityFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Activity Fields', true, \"Retrieved {$count} activity fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testV2ApiSupport(SocialAccount $socialAccount): void\n {\n $this->info('Test 4: v2 API Support (Cursor-based Pagination)');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $startTime = microtime(true);\n\n // Test v2 deals endpoint with cursor pagination\n // Note: The official SDK may or may not have v2 API support\n // This test will determine if v2 is available\n try {\n $apiInstance = new DealsApi(null, $config);\n $updatedSince = Carbon::now()->subDays(30)->toRfc3339String();\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n\n // Check if response has cursor information (v2 indicator)\n // The response object doesn't have getAdditionalData method\n // Just test if the call works - v2 API support is limited in official SDK v1\n $this->recordResult('v2 API Support', false, \"v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);\n }\n } catch (Exception $e) {\n $this->recordResult('v2 API Support', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testErrorHandling(SocialAccount $socialAccount): void\n {\n $this->info('Test 5: Error Handling');\n\n try {\n // Test 5a: Invalid Token\n $this->testInvalidToken();\n\n // Test 5b: Non-existent Resource\n $this->testNonExistentResource($socialAccount);\n } catch (Exception $e) {\n $this->recordResult('Error Handling', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testInvalidToken(): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken('invalid_token');\n $client = new DealsApi(null, $config);\n\n $client->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function testNonExistentResource(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n $client = new DealsApi(null, $config);\n\n // Try to get a deal with a very high ID that likely doesn't exist\n // Note: Official SDK doesn't have a simple getDeal method, so skip this\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function recordResult(string $testName, bool $success, string $message, float $elapsed): void\n {\n $this->results[] = [\n 'test' => $testName,\n 'success' => $success,\n 'message' => $message,\n 'elapsed' => $elapsed,\n ];\n\n $status = $success ? '✅ PASS' : '❌ FAIL';\n $this->line(\" {$status} - {$testName}: {$message}\");\n }\n\n private function displayResults(): void\n {\n $this->newLine();\n $this->info('=== POC Test Results Summary ===');\n $this->newLine();\n\n $total = count($this->results);\n $passed = count(array_filter($this->results, fn ($r) => $r['success']));\n $failed = $total - $passed;\n\n $this->line(\"Total Tests: {$total}\");\n $this->line(\"Passed: {$passed}\");\n $this->line(\"Failed: {$failed}\");\n $this->newLine();\n\n $totalTime = array_sum(array_column($this->results, 'elapsed'));\n $this->line(\"Total Execution Time: {$totalTime}ms\");\n $this->newLine();\n\n if ($failed > 0) {\n $this->warn('Failed Tests:');\n foreach ($this->results as $result) {\n if (! $result['success']) {\n $this->line(\" - {$result['test']}: {$result['message']}\");\n }\n }\n $this->newLine();\n }\n\n $this->info('=== Detailed Results ===');\n foreach ($this->results as $result) {\n $status = $result['success'] ? '✅' : '❌';\n $this->line(\"{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}\");\n }\n\n $this->newLine();\n $this->info('=== Recommendations ===');\n\n if ($passed === $total) {\n $this->info('✅ All tests passed! The official SDK is viable for migration.');\n $this->info('Next steps: Proceed with full migration planning.');\n } else {\n $this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');\n $this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Console\\Commands\\Crm;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Console\\Command;\nuse Jiminny\\Component\\Encryption\\EncryptedTokenManager;\nuse Jiminny\\Models\\SocialAccount;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Pipedrive\\versions\\v1\\Api\\ActivitiesApi;\nuse Pipedrive\\versions\\v1\\Api\\ActivityFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonsApi;\nuse Pipedrive\\versions\\v1\\Configuration;\n\nclass TestPipedriveOfficialSdkCommand extends Command\n{\n protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';\n\n protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';\n\n private array $results = [];\n\n private EncryptedTokenManager $tokenManager;\n\n public function __construct(EncryptedTokenManager $tokenManager)\n {\n parent::__construct();\n $this->tokenManager = $tokenManager;\n }\n\n public function handle(): int\n {\n $teamId = (int) $this->argument('teamId');\n\n $this->info(\"Starting Pipedrive Official SDK POC for Team ID: {$teamId}\");\n $this->newLine();\n\n try {\n $this->runTests($teamId);\n } catch (Exception $e) {\n $this->error(\"POC failed with error: {$e->getMessage()}\");\n $this->error($e->getTraceAsString());\n\n return 1;\n }\n\n $this->displayResults();\n\n return 0;\n }\n\n private function runTests(int $teamId): void\n {\n $team = Team::find($teamId);\n if (! $team) {\n throw new Exception(\"Team with ID {$teamId} not found\");\n }\n\n $socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)\n ->whereHas('sociable', function ($query) use ($team) {\n $query->where('team_id', $team->id);\n })\n ->orderByDesc('expires') // Get the most recently expiring token\n ->first();\n\n if (! $socialAccount) {\n throw new Exception(\"No Pipedrive social account found for team {$teamId}\");\n }\n\n $this->info(\"Found Pipedrive account for team: {$team->name}\");\n $this->newLine();\n\n // Test 1: OAuth Initialization\n $this->testOAuthInitialization($socialAccount);\n\n // Test 2: Basic CRUD Operations\n $this->testBasicCrudOperations($socialAccount);\n\n // Test 3: Field Metadata\n $this->testFieldMetadata($socialAccount);\n\n // Test 4: v2 API Support\n $this->testV2ApiSupport($socialAccount);\n\n // Test 5: Error Handling\n $this->testErrorHandling($socialAccount);\n }\n\n private function testOAuthInitialization(SocialAccount $socialAccount): void\n {\n $this->info('Test 1: OAuth Initialization');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $client = new DealsApi(null, $config);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', true, \"Client initialized successfully in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);\n }\n\n $this->newLine();\n }\n\n private function testBasicCrudOperations(SocialAccount $socialAccount): void\n {\n $this->info('Test 2: Basic CRUD Operations');\n\n try {\n // Get the raw token from database\n $rawDbToken = $socialAccount->getProviderUserToken();\n $this->info(\"DB token (length: \" . strlen($rawDbToken) . \")\");\n $this->info(\"DB token preview: \" . substr($rawDbToken, 0, 20) . \"...\");\n\n // Check token expiry\n $expires = $socialAccount->expires;\n $this->info(\"Token expires at: \" . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));\n $this->info(\"Token is expired: \" . ($expires && $expires < time() ? 'YES' : 'NO'));\n\n // Test with the DB token directly\n $this->info(\"\\n--- Testing with DB token ---\");\n $config = new Configuration();\n $config->setAccessToken($rawDbToken);\n\n // Test 2a: Get Deals\n $this->testGetDeals($config);\n\n // Test 2b: Get Persons\n $this->testGetPersons($config);\n\n // Test 2c: Get Activities\n $this->testGetActivities($config);\n } catch (Exception $e) {\n $this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testCurrentSystemApproach(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n // Simulate how current system makes requests\n $token = $socialAccount->getProviderUserToken();\n $url = 'https://api.pipedrive.com/api/v2/deals';\n\n $headers = [\n 'Authorization' => 'Bearer ' . $token,\n 'Accept' => 'application/json',\n ];\n\n $client = new \\GuzzleHttp\\Client();\n $response = $client->get($url, [\n 'headers' => $headers,\n 'timeout' => 30,\n ]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $statusCode = $response->getStatusCode();\n $this->recordResult('Current System v2 API', $statusCode === 200, \"Status: {$statusCode} in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetDeals(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealsApi(null, $config);\n // Official SDK uses getDealsSummary instead of getDeals\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', true, \"Retrieved deals summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersons(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getPersons method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivities(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getActivities method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testFieldMetadata(SocialAccount $socialAccount): void\n {\n $this->info('Test 3: Field Metadata');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n // Test 3a: Get Deal Fields\n $this->testGetDealFields($config);\n\n // Test 3b: Get Person Fields\n $this->testGetPersonFields($config);\n\n // Test 3c: Get Activity Fields\n $this->testGetActivityFields($config);\n } catch (Exception $e) {\n $this->recordResult('Field Metadata', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testGetDealFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealFieldsApi(null, $config);\n $result = $apiInstance->getDealFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Deal Fields', true, \"Retrieved {$count} deal fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersonFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonFieldsApi(null, $config);\n $result = $apiInstance->getPersonFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Person Fields', true, \"Retrieved {$count} person fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivityFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivityFieldsApi(null, $config);\n $result = $apiInstance->getActivityFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Activity Fields', true, \"Retrieved {$count} activity fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testV2ApiSupport(SocialAccount $socialAccount): void\n {\n $this->info('Test 4: v2 API Support (Cursor-based Pagination)');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $startTime = microtime(true);\n\n // Test v2 deals endpoint with cursor pagination\n // Note: The official SDK may or may not have v2 API support\n // This test will determine if v2 is available\n try {\n $apiInstance = new DealsApi(null, $config);\n $updatedSince = Carbon::now()->subDays(30)->toRfc3339String();\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n\n // Check if response has cursor information (v2 indicator)\n // The response object doesn't have getAdditionalData method\n // Just test if the call works - v2 API support is limited in official SDK v1\n $this->recordResult('v2 API Support', false, \"v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);\n }\n } catch (Exception $e) {\n $this->recordResult('v2 API Support', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testErrorHandling(SocialAccount $socialAccount): void\n {\n $this->info('Test 5: Error Handling');\n\n try {\n // Test 5a: Invalid Token\n $this->testInvalidToken();\n\n // Test 5b: Non-existent Resource\n $this->testNonExistentResource($socialAccount);\n } catch (Exception $e) {\n $this->recordResult('Error Handling', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testInvalidToken(): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken('invalid_token');\n $client = new DealsApi(null, $config);\n\n $client->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function testNonExistentResource(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n $client = new DealsApi(null, $config);\n\n // Try to get a deal with a very high ID that likely doesn't exist\n // Note: Official SDK doesn't have a simple getDeal method, so skip this\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function recordResult(string $testName, bool $success, string $message, float $elapsed): void\n {\n $this->results[] = [\n 'test' => $testName,\n 'success' => $success,\n 'message' => $message,\n 'elapsed' => $elapsed,\n ];\n\n $status = $success ? '✅ PASS' : '❌ FAIL';\n $this->line(\" {$status} - {$testName}: {$message}\");\n }\n\n private function displayResults(): void\n {\n $this->newLine();\n $this->info('=== POC Test Results Summary ===');\n $this->newLine();\n\n $total = count($this->results);\n $passed = count(array_filter($this->results, fn ($r) => $r['success']));\n $failed = $total - $passed;\n\n $this->line(\"Total Tests: {$total}\");\n $this->line(\"Passed: {$passed}\");\n $this->line(\"Failed: {$failed}\");\n $this->newLine();\n\n $totalTime = array_sum(array_column($this->results, 'elapsed'));\n $this->line(\"Total Execution Time: {$totalTime}ms\");\n $this->newLine();\n\n if ($failed > 0) {\n $this->warn('Failed Tests:');\n foreach ($this->results as $result) {\n if (! $result['success']) {\n $this->line(\" - {$result['test']}: {$result['message']}\");\n }\n }\n $this->newLine();\n }\n\n $this->info('=== Detailed Results ===');\n foreach ($this->results as $result) {\n $status = $result['success'] ? '✅' : '❌';\n $this->line(\"{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}\");\n }\n\n $this->newLine();\n $this->info('=== Recommendations ===');\n\n if ($passed === $total) {\n $this->info('✅ All tests passed! The official SDK is viable for migration.');\n $this->info('Next steps: Proceed with full migration planning.');\n } else {\n $this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');\n $this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
5365831007722038384
|
61370652661533572
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
1
51
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Jiminny\Component\Encryption\EncryptedTokenManager;
use Jiminny\Models\SocialAccount;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Pipedrive\versions\v1\Api\ActivitiesApi;
use Pipedrive\versions\v1\Api\ActivityFieldsApi;
use Pipedrive\versions\v1\Api\DealFieldsApi;
use Pipedrive\versions\v1\Api\DealsApi;
use Pipedrive\versions\v1\Api\PersonFieldsApi;
use Pipedrive\versions\v1\Api\PersonsApi;
use Pipedrive\versions\v1\Configuration;
class TestPipedriveOfficialSdkCommand extends Command
{
protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';
protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';
private array $results = [];
private EncryptedTokenManager $tokenManager;
public function __construct(EncryptedTokenManager $tokenManager)
{
parent::__construct();
$this->tokenManager = $tokenManager;
}
public function handle(): int
{
$teamId = (int) $this->argument('teamId');
$this->info("Starting Pipedrive Official SDK POC for Team ID: {$teamId}");
$this->newLine();
try {
$this->runTests($teamId);
} catch (Exception $e) {
$this->error("POC failed with error: {$e->getMessage()}");
$this->error($e->getTraceAsString());
return 1;
}
$this->displayResults();
return 0;
}
private function runTests(int $teamId): void
{
$team = Team::find($teamId);
if (! $team) {
throw new Exception("Team with ID {$teamId} not found");
}
$socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)
->whereHas('sociable', function ($query) use ($team) {
$query->where('team_id', $team->id);
})
->orderByDesc('expires') // Get the most recently expiring token
->first();
if (! $socialAccount) {
throw new Exception("No Pipedrive social account found for team {$teamId}");
}
$this->info("Found Pipedrive account for team: {$team->name}");
$this->newLine();
// Test 1: OAuth Initialization
$this->testOAuthInitialization($socialAccount);
// Test 2: Basic CRUD Operations
$this->testBasicCrudOperations($socialAccount);
// Test 3: Field Metadata
$this->testFieldMetadata($socialAccount);
// Test 4: v2 API Support
$this->testV2ApiSupport($socialAccount);
// Test 5: Error Handling
$this->testErrorHandling($socialAccount);
}
private function testOAuthInitialization(SocialAccount $socialAccount): void
{
$this->info('Test 1: OAuth Initialization');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', true, "Client initialized successfully in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);
}
$this->newLine();
}
private function testBasicCrudOperations(SocialAccount $socialAccount): void
{
$this->info('Test 2: Basic CRUD Operations');
try {
// Get the raw token from database
$rawDbToken = $socialAccount->getProviderUserToken();
$this->info("DB token (length: " . strlen($rawDbToken) . ")");
$this->info("DB token preview: " . substr($rawDbToken, 0, 20) . "...");
// Check token expiry
$expires = $socialAccount->expires;
$this->info("Token expires at: " . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));
$this->info("Token is expired: " . ($expires && $expires < time() ? 'YES' : 'NO'));
// Test with the DB token directly
$this->info("\n--- Testing with DB token ---");
$config = new Configuration();
$config->setAccessToken($rawDbToken);
// Test 2a: Get Deals
$this->testGetDeals($config);
// Test 2b: Get Persons
$this->testGetPersons($config);
// Test 2c: Get Activities
$this->testGetActivities($config);
} catch (Exception $e) {
$this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testCurrentSystemApproach(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
// Simulate how current system makes requests
$token = $socialAccount->getProviderUserToken();
$url = 'https://api.pipedrive.com/api/v2/deals';
$headers = [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
];
$client = new \GuzzleHttp\Client();
$response = $client->get($url, [
'headers' => $headers,
'timeout' => 30,
]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$statusCode = $response->getStatusCode();
$this->recordResult('Current System v2 API', $statusCode === 200, "Status: {$statusCode} in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);
}
}
private function testGetDeals(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealsApi(null, $config);
// Official SDK uses getDealsSummary instead of getDeals
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', true, "Retrieved deals summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersons(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getPersons method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivities(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getActivities method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);
}
}
private function testFieldMetadata(SocialAccount $socialAccount): void
{
$this->info('Test 3: Field Metadata');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
// Test 3a: Get Deal Fields
$this->testGetDealFields($config);
// Test 3b: Get Person Fields
$this->testGetPersonFields($config);
// Test 3c: Get Activity Fields
$this->testGetActivityFields($config);
} catch (Exception $e) {
$this->recordResult('Field Metadata', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testGetDealFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealFieldsApi(null, $config);
$result = $apiInstance->getDealFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Deal Fields', true, "Retrieved {$count} deal fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersonFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new PersonFieldsApi(null, $config);
$result = $apiInstance->getPersonFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Person Fields', true, "Retrieved {$count} person fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivityFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new ActivityFieldsApi(null, $config);
$result = $apiInstance->getActivityFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Activity Fields', true, "Retrieved {$count} activity fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);
}
}
private function testV2ApiSupport(SocialAccount $socialAccount): void
{
$this->info('Test 4: v2 API Support (Cursor-based Pagination)');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$startTime = microtime(true);
// Test v2 deals endpoint with cursor pagination
// Note: The official SDK may or may not have v2 API support
// This test will determine if v2 is available
try {
$apiInstance = new DealsApi(null, $config);
$updatedSince = Carbon::now()->subDays(30)->toRfc3339String();
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
// Check if response has cursor information (v2 indicator)
// The response object doesn't have getAdditionalData method
// Just test if the call works - v2 API support is limited in official SDK v1
$this->recordResult('v2 API Support', false, "v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);
}
} catch (Exception $e) {
$this->recordResult('v2 API Support', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testErrorHandling(SocialAccount $socialAccount): void
{
$this->info('Test 5: Error Handling');
try {
// Test 5a: Invalid Token
$this->testInvalidToken();
// Test 5b: Non-existent Resource
$this->testNonExistentResource($socialAccount);
} catch (Exception $e) {
$this->recordResult('Error Handling', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testInvalidToken(): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken('invalid_token');
$client = new DealsApi(null, $config);
$client->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function testNonExistentResource(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
// Try to get a deal with a very high ID that likely doesn't exist
// Note: Official SDK doesn't have a simple getDeal method, so skip this
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function recordResult(string $testName, bool $success, string $message, float $elapsed): void
{
$this->results[] = [
'test' => $testName,
'success' => $success,
'message' => $message,
'elapsed' => $elapsed,
];
$status = $success ? '✅ PASS' : '❌ FAIL';
$this->line(" {$status} - {$testName}: {$message}");
}
private function displayResults(): void
{
$this->newLine();
$this->info('=== POC Test Results Summary ===');
$this->newLine();
$total = count($this->results);
$passed = count(array_filter($this->results, fn ($r) => $r['success']));
$failed = $total - $passed;
$this->line("Total Tests: {$total}");
$this->line("Passed: {$passed}");
$this->line("Failed: {$failed}");
$this->newLine();
$totalTime = array_sum(array_column($this->results, 'elapsed'));
$this->line("Total Execution Time: {$totalTime}ms");
$this->newLine();
if ($failed > 0) {
$this->warn('Failed Tests:');
foreach ($this->results as $result) {
if (! $result['success']) {
$this->line(" - {$result['test']}: {$result['message']}");
}
}
$this->newLine();
}
$this->info('=== Detailed Results ===');
foreach ($this->results as $result) {
$status = $result['success'] ? '✅' : '❌';
$this->line("{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}");
}
$this->newLine();
$this->info('=== Recommendations ===');
if ($passed === $total) {
$this->info('✅ All tests passed! The official SDK is viable for migration.');
$this->info('Next steps: Proceed with full migration planning.');
} else {
$this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');
$this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
49394
|
NULL
|
NULL
|
NULL
|
|
49403
|
1762
|
24
|
2026-05-18T06:41:52.265305+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086512265_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
1
51
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Jiminny\Component\Encryption\EncryptedTokenManager;
use Jiminny\Models\SocialAccount;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Pipedrive\versions\v1\Api\ActivitiesApi;
use Pipedrive\versions\v1\Api\ActivityFieldsApi;
use Pipedrive\versions\v1\Api\DealFieldsApi;
use Pipedrive\versions\v1\Api\DealsApi;
use Pipedrive\versions\v1\Api\PersonFieldsApi;
use Pipedrive\versions\v1\Api\PersonsApi;
use Pipedrive\versions\v1\Configuration;
class TestPipedriveOfficialSdkCommand extends Command
{
protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';
protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';
private array $results = [];
private EncryptedTokenManager $tokenManager;
public function __construct(EncryptedTokenManager $tokenManager)
{
parent::__construct();
$this->tokenManager = $tokenManager;
}
public function handle(): int
{
$teamId = (int) $this->argument('teamId');
$this->info("Starting Pipedrive Official SDK POC for Team ID: {$teamId}");
$this->newLine();
try {
$this->runTests($teamId);
} catch (Exception $e) {
$this->error("POC failed with error: {$e->getMessage()}");
$this->error($e->getTraceAsString());
return 1;
}
$this->displayResults();
return 0;
}
private function runTests(int $teamId): void
{
$team = Team::find($teamId);
if (! $team) {
throw new Exception("Team with ID {$teamId} not found");
}
$socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)
->whereHas('sociable', function ($query) use ($team) {
$query->where('team_id', $team->id);
})
->orderByDesc('expires') // Get the most recently expiring token
->first();
if (! $socialAccount) {
throw new Exception("No Pipedrive social account found for team {$teamId}");
}
$this->info("Found Pipedrive account for team: {$team->name}");
$this->newLine();
// Test 1: OAuth Initialization
$this->testOAuthInitialization($socialAccount);
// Test 2: Basic CRUD Operations
$this->testBasicCrudOperations($socialAccount);
// Test 3: Field Metadata
$this->testFieldMetadata($socialAccount);
// Test 4: v2 API Support
$this->testV2ApiSupport($socialAccount);
// Test 5: Error Handling
$this->testErrorHandling($socialAccount);
}
private function testOAuthInitialization(SocialAccount $socialAccount): void
{
$this->info('Test 1: OAuth Initialization');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', true, "Client initialized successfully in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);
}
$this->newLine();
}
private function testBasicCrudOperations(SocialAccount $socialAccount): void
{
$this->info('Test 2: Basic CRUD Operations');
try {
// Get the raw token from database
$rawDbToken = $socialAccount->getProviderUserToken();
$this->info("DB token (length: " . strlen($rawDbToken) . ")");
$this->info("DB token preview: " . substr($rawDbToken, 0, 20) . "...");
// Check token expiry
$expires = $socialAccount->expires;
$this->info("Token expires at: " . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));
$this->info("Token is expired: " . ($expires && $expires < time() ? 'YES' : 'NO'));
// Test with the DB token directly
$this->info("\n--- Testing with DB token ---");
$config = new Configuration();
$config->setAccessToken($rawDbToken);
// Test 2a: Get Deals
$this->testGetDeals($config);
// Test 2b: Get Persons
$this->testGetPersons($config);
// Test 2c: Get Activities
$this->testGetActivities($config);
} catch (Exception $e) {
$this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testCurrentSystemApproach(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
// Simulate how current system makes requests
$token = $socialAccount->getProviderUserToken();
$url = 'https://api.pipedrive.com/api/v2/deals';
$headers = [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
];
$client = new \GuzzleHttp\Client();
$response = $client->get($url, [
'headers' => $headers,
'timeout' => 30,
]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$statusCode = $response->getStatusCode();
$this->recordResult('Current System v2 API', $statusCode === 200, "Status: {$statusCode} in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);
}
}
private function testGetDeals(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealsApi(null, $config);
// Official SDK uses getDealsSummary instead of getDeals
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', true, "Retrieved deals summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersons(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getPersons method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivities(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getActivities method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);
}
}
private function testFieldMetadata(SocialAccount $socialAccount): void
{
$this->info('Test 3: Field Metadata');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
// Test 3a: Get Deal Fields
$this->testGetDealFields($config);
// Test 3b: Get Person Fields
$this->testGetPersonFields($config);
// Test 3c: Get Activity Fields
$this->testGetActivityFields($config);
} catch (Exception $e) {
$this->recordResult('Field Metadata', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testGetDealFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealFieldsApi(null, $config);
$result = $apiInstance->getDealFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Deal Fields', true, "Retrieved {$count} deal fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersonFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new PersonFieldsApi(null, $config);
$result = $apiInstance->getPersonFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Person Fields', true, "Retrieved {$count} person fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivityFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new ActivityFieldsApi(null, $config);
$result = $apiInstance->getActivityFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Activity Fields', true, "Retrieved {$count} activity fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);
}
}
private function testV2ApiSupport(SocialAccount $socialAccount): void
{
$this->info('Test 4: v2 API Support (Cursor-based Pagination)');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$startTime = microtime(true);
// Test v2 deals endpoint with cursor pagination
// Note: The official SDK may or may not have v2 API support
// This test will determine if v2 is available
try {
$apiInstance = new DealsApi(null, $config);
$updatedSince = Carbon::now()->subDays(30)->toRfc3339String();
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
// Check if response has cursor information (v2 indicator)
// The response object doesn't have getAdditionalData method
// Just test if the call works - v2 API support is limited in official SDK v1
$this->recordResult('v2 API Support', false, "v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);
}
} catch (Exception $e) {
$this->recordResult('v2 API Support', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testErrorHandling(SocialAccount $socialAccount): void
{
$this->info('Test 5: Error Handling');
try {
// Test 5a: Invalid Token
$this->testInvalidToken();
// Test 5b: Non-existent Resource
$this->testNonExistentResource($socialAccount);
} catch (Exception $e) {
$this->recordResult('Error Handling', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testInvalidToken(): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken('invalid_token');
$client = new DealsApi(null, $config);
$client->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function testNonExistentResource(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
// Try to get a deal with a very high ID that likely doesn't exist
// Note: Official SDK doesn't have a simple getDeal method, so skip this
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function recordResult(string $testName, bool $success, string $message, float $elapsed): void
{
$this->results[] = [
'test' => $testName,
'success' => $success,
'message' => $message,
'elapsed' => $elapsed,
];
$status = $success ? '✅ PASS' : '❌ FAIL';
$this->line(" {$status} - {$testName}: {$message}");
}
private function displayResults(): void
{
$this->newLine();
$this->info('=== POC Test Results Summary ===');
$this->newLine();
$total = count($this->results);
$passed = count(array_filter($this->results, fn ($r) => $r['success']));
$failed = $total - $passed;
$this->line("Total Tests: {$total}");
$this->line("Passed: {$passed}");
$this->line("Failed: {$failed}");
$this->newLine();
$totalTime = array_sum(array_column($this->results, 'elapsed'));
$this->line("Total Execution Time: {$totalTime}ms");
$this->newLine();
if ($failed > 0) {
$this->warn('Failed Tests:');
foreach ($this->results as $result) {
if (! $result['success']) {
$this->line(" - {$result['test']}: {$result['message']}");
}
}
$this->newLine();
}
$this->info('=== Detailed Results ===');
foreach ($this->results as $result) {
$status = $result['success'] ? '✅' : '❌';
$this->line("{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}");
}
$this->newLine();
$this->info('=== Recommendations ===');
if ($passed === $total) {
$this->info('✅ All tests passed! The official SDK is viable for migration.');
$this->info('Next steps: Proceed with full migration planning.');
} else {
$this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');
$this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"pipedrive-sdk-poc, menu","depth":5,"on_screen":true,"help_text":"Git Branch: pipedrive-sdk-poc","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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","role_description":"text entry area","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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"51","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"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\\Console\\Commands\\Crm;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Console\\Command;\nuse Jiminny\\Component\\Encryption\\EncryptedTokenManager;\nuse Jiminny\\Models\\SocialAccount;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Pipedrive\\versions\\v1\\Api\\ActivitiesApi;\nuse Pipedrive\\versions\\v1\\Api\\ActivityFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonsApi;\nuse Pipedrive\\versions\\v1\\Configuration;\n\nclass TestPipedriveOfficialSdkCommand extends Command\n{\n protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';\n\n protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';\n\n private array $results = [];\n\n private EncryptedTokenManager $tokenManager;\n\n public function __construct(EncryptedTokenManager $tokenManager)\n {\n parent::__construct();\n $this->tokenManager = $tokenManager;\n }\n\n public function handle(): int\n {\n $teamId = (int) $this->argument('teamId');\n\n $this->info(\"Starting Pipedrive Official SDK POC for Team ID: {$teamId}\");\n $this->newLine();\n\n try {\n $this->runTests($teamId);\n } catch (Exception $e) {\n $this->error(\"POC failed with error: {$e->getMessage()}\");\n $this->error($e->getTraceAsString());\n\n return 1;\n }\n\n $this->displayResults();\n\n return 0;\n }\n\n private function runTests(int $teamId): void\n {\n $team = Team::find($teamId);\n if (! $team) {\n throw new Exception(\"Team with ID {$teamId} not found\");\n }\n\n $socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)\n ->whereHas('sociable', function ($query) use ($team) {\n $query->where('team_id', $team->id);\n })\n ->orderByDesc('expires') // Get the most recently expiring token\n ->first();\n\n if (! $socialAccount) {\n throw new Exception(\"No Pipedrive social account found for team {$teamId}\");\n }\n\n $this->info(\"Found Pipedrive account for team: {$team->name}\");\n $this->newLine();\n\n // Test 1: OAuth Initialization\n $this->testOAuthInitialization($socialAccount);\n\n // Test 2: Basic CRUD Operations\n $this->testBasicCrudOperations($socialAccount);\n\n // Test 3: Field Metadata\n $this->testFieldMetadata($socialAccount);\n\n // Test 4: v2 API Support\n $this->testV2ApiSupport($socialAccount);\n\n // Test 5: Error Handling\n $this->testErrorHandling($socialAccount);\n }\n\n private function testOAuthInitialization(SocialAccount $socialAccount): void\n {\n $this->info('Test 1: OAuth Initialization');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $client = new DealsApi(null, $config);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', true, \"Client initialized successfully in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);\n }\n\n $this->newLine();\n }\n\n private function testBasicCrudOperations(SocialAccount $socialAccount): void\n {\n $this->info('Test 2: Basic CRUD Operations');\n\n try {\n // Get the raw token from database\n $rawDbToken = $socialAccount->getProviderUserToken();\n $this->info(\"DB token (length: \" . strlen($rawDbToken) . \")\");\n $this->info(\"DB token preview: \" . substr($rawDbToken, 0, 20) . \"...\");\n\n // Check token expiry\n $expires = $socialAccount->expires;\n $this->info(\"Token expires at: \" . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));\n $this->info(\"Token is expired: \" . ($expires && $expires < time() ? 'YES' : 'NO'));\n\n // Test with the DB token directly\n $this->info(\"\\n--- Testing with DB token ---\");\n $config = new Configuration();\n $config->setAccessToken($rawDbToken);\n\n // Test 2a: Get Deals\n $this->testGetDeals($config);\n\n // Test 2b: Get Persons\n $this->testGetPersons($config);\n\n // Test 2c: Get Activities\n $this->testGetActivities($config);\n } catch (Exception $e) {\n $this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testCurrentSystemApproach(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n // Simulate how current system makes requests\n $token = $socialAccount->getProviderUserToken();\n $url = 'https://api.pipedrive.com/api/v2/deals';\n\n $headers = [\n 'Authorization' => 'Bearer ' . $token,\n 'Accept' => 'application/json',\n ];\n\n $client = new \\GuzzleHttp\\Client();\n $response = $client->get($url, [\n 'headers' => $headers,\n 'timeout' => 30,\n ]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $statusCode = $response->getStatusCode();\n $this->recordResult('Current System v2 API', $statusCode === 200, \"Status: {$statusCode} in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetDeals(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealsApi(null, $config);\n // Official SDK uses getDealsSummary instead of getDeals\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', true, \"Retrieved deals summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersons(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getPersons method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivities(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getActivities method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testFieldMetadata(SocialAccount $socialAccount): void\n {\n $this->info('Test 3: Field Metadata');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n // Test 3a: Get Deal Fields\n $this->testGetDealFields($config);\n\n // Test 3b: Get Person Fields\n $this->testGetPersonFields($config);\n\n // Test 3c: Get Activity Fields\n $this->testGetActivityFields($config);\n } catch (Exception $e) {\n $this->recordResult('Field Metadata', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testGetDealFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealFieldsApi(null, $config);\n $result = $apiInstance->getDealFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Deal Fields', true, \"Retrieved {$count} deal fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersonFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonFieldsApi(null, $config);\n $result = $apiInstance->getPersonFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Person Fields', true, \"Retrieved {$count} person fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivityFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivityFieldsApi(null, $config);\n $result = $apiInstance->getActivityFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Activity Fields', true, \"Retrieved {$count} activity fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testV2ApiSupport(SocialAccount $socialAccount): void\n {\n $this->info('Test 4: v2 API Support (Cursor-based Pagination)');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $startTime = microtime(true);\n\n // Test v2 deals endpoint with cursor pagination\n // Note: The official SDK may or may not have v2 API support\n // This test will determine if v2 is available\n try {\n $apiInstance = new DealsApi(null, $config);\n $updatedSince = Carbon::now()->subDays(30)->toRfc3339String();\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n\n // Check if response has cursor information (v2 indicator)\n // The response object doesn't have getAdditionalData method\n // Just test if the call works - v2 API support is limited in official SDK v1\n $this->recordResult('v2 API Support', false, \"v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);\n }\n } catch (Exception $e) {\n $this->recordResult('v2 API Support', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testErrorHandling(SocialAccount $socialAccount): void\n {\n $this->info('Test 5: Error Handling');\n\n try {\n // Test 5a: Invalid Token\n $this->testInvalidToken();\n\n // Test 5b: Non-existent Resource\n $this->testNonExistentResource($socialAccount);\n } catch (Exception $e) {\n $this->recordResult('Error Handling', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testInvalidToken(): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken('invalid_token');\n $client = new DealsApi(null, $config);\n\n $client->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function testNonExistentResource(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n $client = new DealsApi(null, $config);\n\n // Try to get a deal with a very high ID that likely doesn't exist\n // Note: Official SDK doesn't have a simple getDeal method, so skip this\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function recordResult(string $testName, bool $success, string $message, float $elapsed): void\n {\n $this->results[] = [\n 'test' => $testName,\n 'success' => $success,\n 'message' => $message,\n 'elapsed' => $elapsed,\n ];\n\n $status = $success ? '✅ PASS' : '❌ FAIL';\n $this->line(\" {$status} - {$testName}: {$message}\");\n }\n\n private function displayResults(): void\n {\n $this->newLine();\n $this->info('=== POC Test Results Summary ===');\n $this->newLine();\n\n $total = count($this->results);\n $passed = count(array_filter($this->results, fn ($r) => $r['success']));\n $failed = $total - $passed;\n\n $this->line(\"Total Tests: {$total}\");\n $this->line(\"Passed: {$passed}\");\n $this->line(\"Failed: {$failed}\");\n $this->newLine();\n\n $totalTime = array_sum(array_column($this->results, 'elapsed'));\n $this->line(\"Total Execution Time: {$totalTime}ms\");\n $this->newLine();\n\n if ($failed > 0) {\n $this->warn('Failed Tests:');\n foreach ($this->results as $result) {\n if (! $result['success']) {\n $this->line(\" - {$result['test']}: {$result['message']}\");\n }\n }\n $this->newLine();\n }\n\n $this->info('=== Detailed Results ===');\n foreach ($this->results as $result) {\n $status = $result['success'] ? '✅' : '❌';\n $this->line(\"{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}\");\n }\n\n $this->newLine();\n $this->info('=== Recommendations ===');\n\n if ($passed === $total) {\n $this->info('✅ All tests passed! The official SDK is viable for migration.');\n $this->info('Next steps: Proceed with full migration planning.');\n } else {\n $this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');\n $this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Console\\Commands\\Crm;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Console\\Command;\nuse Jiminny\\Component\\Encryption\\EncryptedTokenManager;\nuse Jiminny\\Models\\SocialAccount;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Pipedrive\\versions\\v1\\Api\\ActivitiesApi;\nuse Pipedrive\\versions\\v1\\Api\\ActivityFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonsApi;\nuse Pipedrive\\versions\\v1\\Configuration;\n\nclass TestPipedriveOfficialSdkCommand extends Command\n{\n protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';\n\n protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';\n\n private array $results = [];\n\n private EncryptedTokenManager $tokenManager;\n\n public function __construct(EncryptedTokenManager $tokenManager)\n {\n parent::__construct();\n $this->tokenManager = $tokenManager;\n }\n\n public function handle(): int\n {\n $teamId = (int) $this->argument('teamId');\n\n $this->info(\"Starting Pipedrive Official SDK POC for Team ID: {$teamId}\");\n $this->newLine();\n\n try {\n $this->runTests($teamId);\n } catch (Exception $e) {\n $this->error(\"POC failed with error: {$e->getMessage()}\");\n $this->error($e->getTraceAsString());\n\n return 1;\n }\n\n $this->displayResults();\n\n return 0;\n }\n\n private function runTests(int $teamId): void\n {\n $team = Team::find($teamId);\n if (! $team) {\n throw new Exception(\"Team with ID {$teamId} not found\");\n }\n\n $socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)\n ->whereHas('sociable', function ($query) use ($team) {\n $query->where('team_id', $team->id);\n })\n ->orderByDesc('expires') // Get the most recently expiring token\n ->first();\n\n if (! $socialAccount) {\n throw new Exception(\"No Pipedrive social account found for team {$teamId}\");\n }\n\n $this->info(\"Found Pipedrive account for team: {$team->name}\");\n $this->newLine();\n\n // Test 1: OAuth Initialization\n $this->testOAuthInitialization($socialAccount);\n\n // Test 2: Basic CRUD Operations\n $this->testBasicCrudOperations($socialAccount);\n\n // Test 3: Field Metadata\n $this->testFieldMetadata($socialAccount);\n\n // Test 4: v2 API Support\n $this->testV2ApiSupport($socialAccount);\n\n // Test 5: Error Handling\n $this->testErrorHandling($socialAccount);\n }\n\n private function testOAuthInitialization(SocialAccount $socialAccount): void\n {\n $this->info('Test 1: OAuth Initialization');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $client = new DealsApi(null, $config);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', true, \"Client initialized successfully in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);\n }\n\n $this->newLine();\n }\n\n private function testBasicCrudOperations(SocialAccount $socialAccount): void\n {\n $this->info('Test 2: Basic CRUD Operations');\n\n try {\n // Get the raw token from database\n $rawDbToken = $socialAccount->getProviderUserToken();\n $this->info(\"DB token (length: \" . strlen($rawDbToken) . \")\");\n $this->info(\"DB token preview: \" . substr($rawDbToken, 0, 20) . \"...\");\n\n // Check token expiry\n $expires = $socialAccount->expires;\n $this->info(\"Token expires at: \" . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));\n $this->info(\"Token is expired: \" . ($expires && $expires < time() ? 'YES' : 'NO'));\n\n // Test with the DB token directly\n $this->info(\"\\n--- Testing with DB token ---\");\n $config = new Configuration();\n $config->setAccessToken($rawDbToken);\n\n // Test 2a: Get Deals\n $this->testGetDeals($config);\n\n // Test 2b: Get Persons\n $this->testGetPersons($config);\n\n // Test 2c: Get Activities\n $this->testGetActivities($config);\n } catch (Exception $e) {\n $this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testCurrentSystemApproach(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n // Simulate how current system makes requests\n $token = $socialAccount->getProviderUserToken();\n $url = 'https://api.pipedrive.com/api/v2/deals';\n\n $headers = [\n 'Authorization' => 'Bearer ' . $token,\n 'Accept' => 'application/json',\n ];\n\n $client = new \\GuzzleHttp\\Client();\n $response = $client->get($url, [\n 'headers' => $headers,\n 'timeout' => 30,\n ]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $statusCode = $response->getStatusCode();\n $this->recordResult('Current System v2 API', $statusCode === 200, \"Status: {$statusCode} in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetDeals(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealsApi(null, $config);\n // Official SDK uses getDealsSummary instead of getDeals\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', true, \"Retrieved deals summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersons(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getPersons method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivities(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n // Official SDK doesn't have a simple getActivities method\n // Skip this test for now as the API structure is different\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testFieldMetadata(SocialAccount $socialAccount): void\n {\n $this->info('Test 3: Field Metadata');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n // Test 3a: Get Deal Fields\n $this->testGetDealFields($config);\n\n // Test 3b: Get Person Fields\n $this->testGetPersonFields($config);\n\n // Test 3c: Get Activity Fields\n $this->testGetActivityFields($config);\n } catch (Exception $e) {\n $this->recordResult('Field Metadata', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testGetDealFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealFieldsApi(null, $config);\n $result = $apiInstance->getDealFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Deal Fields', true, \"Retrieved {$count} deal fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersonFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonFieldsApi(null, $config);\n $result = $apiInstance->getPersonFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Person Fields', true, \"Retrieved {$count} person fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivityFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivityFieldsApi(null, $config);\n $result = $apiInstance->getActivityFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Activity Fields', true, \"Retrieved {$count} activity fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testV2ApiSupport(SocialAccount $socialAccount): void\n {\n $this->info('Test 4: v2 API Support (Cursor-based Pagination)');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $startTime = microtime(true);\n\n // Test v2 deals endpoint with cursor pagination\n // Note: The official SDK may or may not have v2 API support\n // This test will determine if v2 is available\n try {\n $apiInstance = new DealsApi(null, $config);\n $updatedSince = Carbon::now()->subDays(30)->toRfc3339String();\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n\n // Check if response has cursor information (v2 indicator)\n // The response object doesn't have getAdditionalData method\n // Just test if the call works - v2 API support is limited in official SDK v1\n $this->recordResult('v2 API Support', false, \"v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);\n }\n } catch (Exception $e) {\n $this->recordResult('v2 API Support', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testErrorHandling(SocialAccount $socialAccount): void\n {\n $this->info('Test 5: Error Handling');\n\n try {\n // Test 5a: Invalid Token\n $this->testInvalidToken();\n\n // Test 5b: Non-existent Resource\n $this->testNonExistentResource($socialAccount);\n } catch (Exception $e) {\n $this->recordResult('Error Handling', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testInvalidToken(): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken('invalid_token');\n $client = new DealsApi(null, $config);\n\n $client->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function testNonExistentResource(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n $client = new DealsApi(null, $config);\n\n // Try to get a deal with a very high ID that likely doesn't exist\n // Note: Official SDK doesn't have a simple getDeal method, so skip this\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, \"Method not available in official SDK - API structure different\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function recordResult(string $testName, bool $success, string $message, float $elapsed): void\n {\n $this->results[] = [\n 'test' => $testName,\n 'success' => $success,\n 'message' => $message,\n 'elapsed' => $elapsed,\n ];\n\n $status = $success ? '✅ PASS' : '❌ FAIL';\n $this->line(\" {$status} - {$testName}: {$message}\");\n }\n\n private function displayResults(): void\n {\n $this->newLine();\n $this->info('=== POC Test Results Summary ===');\n $this->newLine();\n\n $total = count($this->results);\n $passed = count(array_filter($this->results, fn ($r) => $r['success']));\n $failed = $total - $passed;\n\n $this->line(\"Total Tests: {$total}\");\n $this->line(\"Passed: {$passed}\");\n $this->line(\"Failed: {$failed}\");\n $this->newLine();\n\n $totalTime = array_sum(array_column($this->results, 'elapsed'));\n $this->line(\"Total Execution Time: {$totalTime}ms\");\n $this->newLine();\n\n if ($failed > 0) {\n $this->warn('Failed Tests:');\n foreach ($this->results as $result) {\n if (! $result['success']) {\n $this->line(\" - {$result['test']}: {$result['message']}\");\n }\n }\n $this->newLine();\n }\n\n $this->info('=== Detailed Results ===');\n foreach ($this->results as $result) {\n $status = $result['success'] ? '✅' : '❌';\n $this->line(\"{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}\");\n }\n\n $this->newLine();\n $this->info('=== Recommendations ===');\n\n if ($passed === $total) {\n $this->info('✅ All tests passed! The official SDK is viable for migration.');\n $this->info('Next steps: Proceed with full migration planning.');\n } else {\n $this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');\n $this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
5365831007722038384
|
61370652661533572
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
1
51
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Jiminny\Component\Encryption\EncryptedTokenManager;
use Jiminny\Models\SocialAccount;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Pipedrive\versions\v1\Api\ActivitiesApi;
use Pipedrive\versions\v1\Api\ActivityFieldsApi;
use Pipedrive\versions\v1\Api\DealFieldsApi;
use Pipedrive\versions\v1\Api\DealsApi;
use Pipedrive\versions\v1\Api\PersonFieldsApi;
use Pipedrive\versions\v1\Api\PersonsApi;
use Pipedrive\versions\v1\Configuration;
class TestPipedriveOfficialSdkCommand extends Command
{
protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';
protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';
private array $results = [];
private EncryptedTokenManager $tokenManager;
public function __construct(EncryptedTokenManager $tokenManager)
{
parent::__construct();
$this->tokenManager = $tokenManager;
}
public function handle(): int
{
$teamId = (int) $this->argument('teamId');
$this->info("Starting Pipedrive Official SDK POC for Team ID: {$teamId}");
$this->newLine();
try {
$this->runTests($teamId);
} catch (Exception $e) {
$this->error("POC failed with error: {$e->getMessage()}");
$this->error($e->getTraceAsString());
return 1;
}
$this->displayResults();
return 0;
}
private function runTests(int $teamId): void
{
$team = Team::find($teamId);
if (! $team) {
throw new Exception("Team with ID {$teamId} not found");
}
$socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)
->whereHas('sociable', function ($query) use ($team) {
$query->where('team_id', $team->id);
})
->orderByDesc('expires') // Get the most recently expiring token
->first();
if (! $socialAccount) {
throw new Exception("No Pipedrive social account found for team {$teamId}");
}
$this->info("Found Pipedrive account for team: {$team->name}");
$this->newLine();
// Test 1: OAuth Initialization
$this->testOAuthInitialization($socialAccount);
// Test 2: Basic CRUD Operations
$this->testBasicCrudOperations($socialAccount);
// Test 3: Field Metadata
$this->testFieldMetadata($socialAccount);
// Test 4: v2 API Support
$this->testV2ApiSupport($socialAccount);
// Test 5: Error Handling
$this->testErrorHandling($socialAccount);
}
private function testOAuthInitialization(SocialAccount $socialAccount): void
{
$this->info('Test 1: OAuth Initialization');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', true, "Client initialized successfully in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);
}
$this->newLine();
}
private function testBasicCrudOperations(SocialAccount $socialAccount): void
{
$this->info('Test 2: Basic CRUD Operations');
try {
// Get the raw token from database
$rawDbToken = $socialAccount->getProviderUserToken();
$this->info("DB token (length: " . strlen($rawDbToken) . ")");
$this->info("DB token preview: " . substr($rawDbToken, 0, 20) . "...");
// Check token expiry
$expires = $socialAccount->expires;
$this->info("Token expires at: " . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));
$this->info("Token is expired: " . ($expires && $expires < time() ? 'YES' : 'NO'));
// Test with the DB token directly
$this->info("\n--- Testing with DB token ---");
$config = new Configuration();
$config->setAccessToken($rawDbToken);
// Test 2a: Get Deals
$this->testGetDeals($config);
// Test 2b: Get Persons
$this->testGetPersons($config);
// Test 2c: Get Activities
$this->testGetActivities($config);
} catch (Exception $e) {
$this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testCurrentSystemApproach(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
// Simulate how current system makes requests
$token = $socialAccount->getProviderUserToken();
$url = 'https://api.pipedrive.com/api/v2/deals';
$headers = [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
];
$client = new \GuzzleHttp\Client();
$response = $client->get($url, [
'headers' => $headers,
'timeout' => 30,
]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$statusCode = $response->getStatusCode();
$this->recordResult('Current System v2 API', $statusCode === 200, "Status: {$statusCode} in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);
}
}
private function testGetDeals(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealsApi(null, $config);
// Official SDK uses getDealsSummary instead of getDeals
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', true, "Retrieved deals summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersons(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getPersons method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivities(Configuration $config): void
{
$startTime = microtime(true);
try {
// Official SDK doesn't have a simple getActivities method
// Skip this test for now as the API structure is different
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);
}
}
private function testFieldMetadata(SocialAccount $socialAccount): void
{
$this->info('Test 3: Field Metadata');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
// Test 3a: Get Deal Fields
$this->testGetDealFields($config);
// Test 3b: Get Person Fields
$this->testGetPersonFields($config);
// Test 3c: Get Activity Fields
$this->testGetActivityFields($config);
} catch (Exception $e) {
$this->recordResult('Field Metadata', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testGetDealFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealFieldsApi(null, $config);
$result = $apiInstance->getDealFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Deal Fields', true, "Retrieved {$count} deal fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersonFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new PersonFieldsApi(null, $config);
$result = $apiInstance->getPersonFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Person Fields', true, "Retrieved {$count} person fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivityFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new ActivityFieldsApi(null, $config);
$result = $apiInstance->getActivityFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Activity Fields', true, "Retrieved {$count} activity fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);
}
}
private function testV2ApiSupport(SocialAccount $socialAccount): void
{
$this->info('Test 4: v2 API Support (Cursor-based Pagination)');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$startTime = microtime(true);
// Test v2 deals endpoint with cursor pagination
// Note: The official SDK may or may not have v2 API support
// This test will determine if v2 is available
try {
$apiInstance = new DealsApi(null, $config);
$updatedSince = Carbon::now()->subDays(30)->toRfc3339String();
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
// Check if response has cursor information (v2 indicator)
// The response object doesn't have getAdditionalData method
// Just test if the call works - v2 API support is limited in official SDK v1
$this->recordResult('v2 API Support', false, "v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);
}
} catch (Exception $e) {
$this->recordResult('v2 API Support', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testErrorHandling(SocialAccount $socialAccount): void
{
$this->info('Test 5: Error Handling');
try {
// Test 5a: Invalid Token
$this->testInvalidToken();
// Test 5b: Non-existent Resource
$this->testNonExistentResource($socialAccount);
} catch (Exception $e) {
$this->recordResult('Error Handling', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testInvalidToken(): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken('invalid_token');
$client = new DealsApi(null, $config);
$client->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function testNonExistentResource(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
// Try to get a deal with a very high ID that likely doesn't exist
// Note: Official SDK doesn't have a simple getDeal method, so skip this
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, "Method not available in official SDK - API structure different", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function recordResult(string $testName, bool $success, string $message, float $elapsed): void
{
$this->results[] = [
'test' => $testName,
'success' => $success,
'message' => $message,
'elapsed' => $elapsed,
];
$status = $success ? '✅ PASS' : '❌ FAIL';
$this->line(" {$status} - {$testName}: {$message}");
}
private function displayResults(): void
{
$this->newLine();
$this->info('=== POC Test Results Summary ===');
$this->newLine();
$total = count($this->results);
$passed = count(array_filter($this->results, fn ($r) => $r['success']));
$failed = $total - $passed;
$this->line("Total Tests: {$total}");
$this->line("Passed: {$passed}");
$this->line("Failed: {$failed}");
$this->newLine();
$totalTime = array_sum(array_column($this->results, 'elapsed'));
$this->line("Total Execution Time: {$totalTime}ms");
$this->newLine();
if ($failed > 0) {
$this->warn('Failed Tests:');
foreach ($this->results as $result) {
if (! $result['success']) {
$this->line(" - {$result['test']}: {$result['message']}");
}
}
$this->newLine();
}
$this->info('=== Detailed Results ===');
foreach ($this->results as $result) {
$status = $result['success'] ? '✅' : '❌';
$this->line("{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}");
}
$this->newLine();
$this->info('=== Recommendations ===');
if ($passed === $total) {
$this->info('✅ All tests passed! The official SDK is viable for migration.');
$this->info('Next steps: Proceed with full migration planning.');
} else {
$this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');
$this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
49398
|
NULL
|
NULL
|
NULL
|
|
49411
|
1765
|
1
|
2026-05-18T06:42:37.607757+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086557607_m2.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"pipedrive-sdk-poc, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.05618351,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: pipedrive-sdk-poc","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.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
7962651264686056251
|
-7483856360197085818
|
visual_change
|
hybrid
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
PhostormVIewINavicarecodeLaravelKeractorFV faVsco.js?° pipedrive-sdk-pocroledey© SyncProfileMetadata.phpCConvertLeadActivities.ong© SyncPlanhat.php©syncleammetadata.ong© TestPipedriveOfficialSdkCommand.phUpoateopponunityspecrications.onocreatePlaybookcreatedevent.ong>@ Dealinsights>@Dev> 0 Dialersc) IntearationApp/Service.php© LeadConverted.php© CreateSelfCoachedEvent.phpD DTOSC) CreateCommentedEvent.phoC) CreateSmsSentEvent.ohpC) PlanhatActivityListener.phoC Elasticsearch(C)AskAnvthinaPromotService.ohd› EnqagementStatsш GeckoExport(C)AutomatedReportsCommand.ohvphp api y2.ohoC) RequestGenerateReport.Job.oho/>D Livestream(C) AutomatedReportkesulconp© AutomatedReport.phpA console [STAGING]Mallboxes• MidratePlavbackithemes• M Plavbooksdectarelstrict_types-tyD Playlists> M Postmarknanespace Jininny console comnanos crm> M PronhetAuse…..v M Renorto© AutomatedReportsCommand.phpC) AutomatedRenortsRetentionPolicvCor14 Dclass syncubnects extends command© AutomatedReportsSendCommand.pht© CreateMockAskJiminnyReportResultC© DeleteReportCommand.php© GenerateMarketingReport.php© Team.php© Usage.php* The name and sianature of the console commandi>C Slack* ovar strind›_ Teams>C Tracks7301nrotected ssionature = crm:sunc-ohnects -team?, *--svnct'*w Transcription>C TwilioC Users> M Vocabularv* The console command descrintion.>Zoom*Avan strinaC) Command.oho© CreateDatabaseUsers.phpprotected $description = 'Sync remote CRM objects.';C)DatabaseTiableCount.oho© DeleteOIdAiCrmNotesCommand.ohv* Eyecuto the concole commandlCDeleteS?Leftoverscommand.ohn@ DevPostmanCommand.phpC) DiarizeViaAiParticinantldentificationCompublic function handle: void{...}@ EncrvntTokensCommand.nhn(C EnaaaementStatsReaenerateCommand.n( FeatureFlaasHelner nhr69 6t)protected function getStaggerDelavSeconds@: floatf...© FixCrossTenantlssues.php© FlushRolesPermissionsCache.php© GeneratelnternalWebhookToken.php© GroupSetDefaultLanguageCommand.phc74 6tprotected function getMaxDelavSeconds@: int{...}81 6t>protected function getLoqPrefix@: stringf...;l© HelperTruncateCoachingTables.php=custom.loglaravel.log4 SF [jiminny@localhost]« HS_local [jiminny@localhost]A console [PROD]© TestPipedriveOfficialSdkCommand.php x © Kernel.phpA console [EU]123135class TestPipedrive0fficialSdkCommand extends Commandprivate function testBasicCrudOperations(SocialAccount $socialAccount): voidschls->inrol string."DB token (Length:scrlen(srawudloken)")"):l$this->info( string: "DB token preview: " . substr(SrawDbToken, offset: 0,length// Check token expirysexolres = ssoclalAccounc->explres.$this->info( string: "Token expires at: " . ($expires ? Carbon::createFromTimesSthis->info string:"Token 1s expired: ". Sexpires ∞x Sexpires < timeo?"YTest with the D8 token directlySthis->info( string: "\n--- Testing with DB token ---")sconfio = new conflourationonsconf1o->serAccesstoken(srawdblloken):I/ Test 2a: Get DealsAccept Reject// Test 2a: Get Deals SummarySthis->testGetDeals(Sconfig);S1 te-7 test eef Reons (Sconf19);I/ Test 2b: Get Persons using official SDK methodsthis->testbetPersons0fficialsconf10)i// Test 2c: Get Activitiesscnis->rescbecacclviclesscontlopnTect 2c. Got Activities ucina official Clk methodSthis->testGetActivities0fficial($config):}catch (Exception $e) {$this->recordResult( testName: 'Basic CRUD Operations'.success: false, $e->get$this->newLineO;private function testCurrentSystemApproach(SocialAccount SsocialAccount): void{...}1 usageprivate function testGetDeals(Configuration Sconfig): void{...}1usade.orivate function testGetPersons(Confiauration Sconfio): voids.-1 usageorivate function testGetActivities(Confiauration Sconfio: void ...?CascadeDebugging PipedrivThought for 1s0 4 tasks done• Update POC to test official SDK methods properlyOTest actual CRUD operations with official SDKTest vz APl support it avallable3 Test error handlina properiv• TestPipedriveOfficialSckCommand.ohgThouahtserror while ediung lestripedriveurticlalsakcommano.onp ›Thoughts ›Read TestPipedriveOfficialSdkCommand.pho #L175-224lhoughtsE • TestPipedriveOfficialSdkCommand.php100% Lz• Mon 18 May 9:42:37AskJiminnyReportActivityServiceTest v+0 ..continue and test all in the command.+6 -656 tokens2272 files +6-5Ask anvthina (&4-L)÷ « CodeSWF.16* Reiect allAccent alli•.0W Windsurf TeamoPo 4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
49412
|
1764
|
0
|
2026-05-18T06:42:39.257993+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086559257_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"pipedrive-sdk-poc, menu","depth":5,"on_screen":true,"help_text":"Git Branch: pipedrive-sdk-poc","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3036442092684777152
|
-5740307667640416884
|
visual_change
|
hybrid
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
i Project: faVsco.js, menu
pipedrive-sdk-poc, menu
iTerm2ShelllEditViewSessionScriptsProfilesWindowHelp•• 0APP (-zsh)DOCKERLast login: Mon May 18 09:17:28 on ttys007O 81DEV (-zsh)O ₴2Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentsPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (pipedrive-sdk-poc) $ U‹ $0APP (-zsh)|83100% C47 8• Mon 18 May 9:42:38T₴1screenpipe*O &4APP...
|
49408
|
NULL
|
NULL
|
NULL
|
|
49413
|
1765
|
2
|
2026-05-18T06:43:04.842600+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086584842_m2.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"pipedrive-sdk-poc, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.05618351,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: pipedrive-sdk-poc","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.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","depth":4,"bounds":{"left":0.15724733,"top":0.2952913,"width":0.21476063,"height":0.7047087},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-7615602772621210147
|
-8678315669563599930
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}...
|
49411
|
NULL
|
NULL
|
NULL
|
|
49414
|
1764
|
1
|
2026-05-18T06:43:10.483083+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086590483_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
8043719072324535154
|
-8628527368849355612
|
idle
|
hybrid
|
NULL
|
Project: faVsco.js, menu
iTerm2Shell|EditViewSessi Project: faVsco.js, menu
iTerm2Shell|EditViewSessionScriptsProfilesWindowHelp• 0APP (-zsh)DOCKERLast login: Mon May 18 09:17:28 on ttys007₴81DEV (-zsh)O ₴2Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentsPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (pipedrive-sdk-poc) $ U‹ $0APP (-zsh)83100% C47 8• Mon 18 May 9:43:10T₴1screenpipe*O ₴4APP...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
49416
|
1765
|
3
|
2026-05-18T06:43:32.075606+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086612075_m2.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false}]...
|
8043719072324535154
|
-8628527368849355612
|
visual_change
|
hybrid
|
NULL
|
Project: faVsco.js, menu
Firefox•.•Platform Sprint Project: faVsco.js, menu
Firefox•.•Platform Sprint 4 Q2 - PlatfornVIewMistorbookmarksProtllesToolsWindowmelpny.atlassian.net/jira/software/c/projects/JY/boards/37100% C4 &• Mon 18 May 9:43:31()JY-20891 add support for seconda(UY-20891] Sidekick SMS issue - JJY-20915) Add environment-spedUsage | Windsurf+ (SRD-6853] Moxso - Potential deaPipelines - jiminny/appl Feed - jiminny - SentryfJY-209061 Review of Pipedrive SP Pipedrive API Reference and Docur•pipedrive/client-php: Pipedrive APfiiminny/infrastructurel.JY-20623Pull requests - jiminny/app(UY-20912) Fallback mechanism foS 1IY-209061 Review of Pinedrive SPersonal Access Tokens (Classic)+ New Tab00O JIMINNY@ For you© Recent|# Starred•$ Apps0, Spaces+...Recent( Service-Desk@ Jiminny (New) + ...10D Platform TeamII Capture TeamW Enterprise Stability I...Processing TeamWD SE Kanban= More spaces= FiltersC DashboardsC OperationsQ Search+ Create5 Ask RovoA& Confluence: Teams"= Customise sidebar|Spaces / Jiminny (New)Platform Team P+Summary& TimelineE BacklogII Active sprints8 Calendar Reports Testing Board List Z Forms E Components % Development % Code O Security & Releases A Deployments E Archived workitems E Docs @ Shortcuts ~Slack integration& Reporting CenterQ Search board00000EpicvTypev Quick filtersComplete sprintGroup: QueriesREADY FOR DEV 4IN DEV 3CODE REVIEW1BLOCKEDNotitv the user it a Panorama promots is.deleted but is used in AJ ReportAJ REPORTSBacklog…JY-206763..=€MCP › Enable users to get a list of callsand their detailsJIMINNY MCP CONNECTORIn Dev[JY-20833MCP > Enable the AI to know detailsabout the userJIMINNY MCP CONNECTORCode Review9..=0[ JY-208461.5 82 000 =0OA 3[HubSpot] Optimise CRM rematching ondelete hubspot accounts/contactsPLATFORM STABILITYIn QA# JY-207251.5 82 .000 = 8PO ACCEPTANCEDEPLOY 7Notify the user if a SS is deleted but isused in AJ ReportAJ REPORTSBacklog[ JY-206152.5 0000 = 0MCP > Enable users to get a list of dealsand their detailsJIMINNY MCP CONNECTORIn DevQ JY-2083520 /2 000 = 0Upgrade to PHP 8.5PHP 8.5 UPGRADEReady for QA1.5 82 •00 = 0Upgrade BE libraries - MayMAINTENANCEBacklog©Y-199581 .000=@Allow owner's role to be selected whensettina uo a trialiEIMPROVEMENT OF OUR EFFICIENCYIn Dev# JY-20613Upgrade Python and libraries - MayMAINTENANCEIn QAE JY-20881182 •0=11.5 = QImprove Activity Type suggestions (AUTO-DETECTED ACTIVITY TYPEBackloaI… JY-2041012 0000 =₴Au Panorama for Call Scoring in ODAUTOMATED AI SCORINGVeolovedI… JY-2036110.5 •0÷[Deadline 25 May] Migrate depricatedGemini s.l rlash Lue Preview modellMAINTENANCEDeployed[ JY-208801 Ý) •00 =|Setup test coverage for Prophet in SonarMAINTENANCEDeployedV JY-1995110•=2[Deadline 17 Junel Miarate depricatedGemini modelsMAINTENANCEDeployedE JY-202720...=1Sidekick SMS issueDeployedJY-2089172 00=0Update activity stage on opportunityuodateDeployed+* JY-20903, 0=@UndateActivitvSlasticSearchDocumentCdmmand...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
49417
|
1764
|
3
|
2026-05-18T06:43:33.735238+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086613735_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
iTerm2ShelllEditViewSessionScriptsProfilesWindowHe iTerm2ShelllEditViewSessionScriptsProfilesWindowHelp•• 0APP (-zsh)DOCKERLast login: Mon May 18 09:17:28 on ttys007O 81DEV (-zsh)O 82Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentsPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (pipedrive-sdk-poc) $ U‹ $0APP (-zsh)|83100% C47 8• Mon 18 May 9:43:33T₴1screenpipe*O &4APP...
|
NULL
|
4730714162020166078
|
NULL
|
visual_change
|
ocr
|
NULL
|
iTerm2ShelllEditViewSessionScriptsProfilesWindowHe iTerm2ShelllEditViewSessionScriptsProfilesWindowHelp•• 0APP (-zsh)DOCKERLast login: Mon May 18 09:17:28 on ttys007O 81DEV (-zsh)O 82Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentsPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (pipedrive-sdk-poc) $ U‹ $0APP (-zsh)|83100% C47 8• Mon 18 May 9:43:33T₴1screenpipe*O &4APP...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
49418
|
1764
|
4
|
2026-05-18T06:43:37.511115+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086617511_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
iTerm2Shell|EditViewSessionScriptsProfilesWindowHe iTerm2Shell|EditViewSessionScriptsProfilesWindowHelp• 0APP (-zsh)DOCKERLast login: Mon May 18 09:17:28 on ttys007₴81DEV (-zsh)O ₴2Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentsPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (pipedrive-sdk-poc) $ U‹ $0APP (-zsh)83100% C47 8• Mon 18 May 9:43:37T₴1screenpipe*O ₴4APP...
|
NULL
|
-8012899098312889318
|
NULL
|
click
|
ocr
|
NULL
|
iTerm2Shell|EditViewSessionScriptsProfilesWindowHe iTerm2Shell|EditViewSessionScriptsProfilesWindowHelp• 0APP (-zsh)DOCKERLast login: Mon May 18 09:17:28 on ttys007₴81DEV (-zsh)O ₴2Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentsPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (pipedrive-sdk-poc) $ U‹ $0APP (-zsh)83100% C47 8• Mon 18 May 9:43:37T₴1screenpipe*O ₴4APP...
|
49417
|
NULL
|
NULL
|
NULL
|
|
49419
|
1765
|
4
|
2026-05-18T06:43:37.511121+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086617511_m2.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhostormVIewINavicarecodeLaravelKeractorFV faVsco. PhostormVIewINavicarecodeLaravelKeractorFV faVsco.js?° pipedrive-sdk-pocProiectC ActivityController.ong© SyncProfileMetadata.phpCConvertLeadActivities.ong© SyncPlanhat.php©syncleammetadata.ong© TestPipedriveOfficialSdkCommand.phUpoateopponunityspecrications.onocreatePlaybookcreatedevent.ong>@ Dealinsights>@Dev> 0 Dialersc) IntearationApp/Service.php© LeadConverted.php© CreateSelfCoachedEvent.phpD DTOSC ElasticsearchC) CreateCommentedEvent.phoC) CreateSmsSentEvent.ohpC) PlanhatActivityListener.pho(C)AskAnvthinaPromotService.ohd› EnqagementStatsш GeckoExport(C)AutomatedReportsCommand.ohvphp api y2.ohoC) RequestGenerateReport.Job.oho/>D Livestream(C) AutomatedReportkesulconp© AutomatedReport.phpA console [STAGING]Mallboxes• MidratePlavbackithemes• M Plavbooksdectarelstrict_types-tyD Playlists> M Postmarknanespace Jininny console comnanos crm> M PronhetAuse…..v M Renorto© AutomatedReportsCommand.phpC) AutomatedRenortsRetentionPolicvCor14 Dclass syncubnects extends command© AutomatedReportsSendCommand.pht© CreateMockAskJiminnyReportResultC© DeleteReportCommand.php© GenerateMarketingReport.php© Team.php© Usage.php>C Slack* The name and sianature of the console commandi* ovar strind›_ Teams>C TracksC Transcription>C TwilioC Users> M Vocabularv7301nrotected ssionature = crm:sunc-ohnects -team?, ~--svnctr** The console command descrintion.>Zoom*Avan strinaC) Command.oho© CreateDatabaseUsers.ohpprotected $description = 'Sync remote CRM objects.';C)DatabaseTiableCount.oho© DeleteOIdAiCrmNotesCommand.ohv* Eyecuto the concole commandlCDeleteS?Leftoverscommand.ohn@ DevPostmanCommand.phppublic function handle: voidf...;C) DiarizeViaAiParticinantldentificationCom@ EncrvntTokensCommand.nhn(C EnaadementStatsRedenerateCommand.r( FeatureFlaasHelner nhr69 6t)protected function getStaggerDelavSeconds@: floatf...?© FixCrossTenantlssues.php© FlushRolesPermissionsCache.php© GeneratelnternalWebhookToken.php9 GrounSetDefaultLanguageCommand.php74 6tprotected function getMaxDelavSeconds@: int{...}81 6t>protected function getLoqPrefix@: stringf...;l© HelperTruncateCoachingTables.phpHelner Code will hoin INF to underctand vour Laravel ann code II Generate II Don't Show Anvmore (8 minutes aaol=custom.loglaravel.logA SF [jiminny@localhost]« HS_local [jiminny@localhost]A console [PROD]© TestPipedriveOfficialSdkCommand.php x © Kernel.phpA console [EU]class TestPipedrive0fficialSdkCommand extends Command private tunction testbasiccruduperations (soclaLAccount ssoclaLAccount vola-rawubloken = ssoclaLaccount->cecrrovlderuserlokenorSthis->info( string: "DB token (Length:"")"Sthis->info( string: "DB token preview: ". substr(SrawDbToken. offset: 0,lengthcheck token expirySexpires = SsocialAccount->expires:sthas->inrol string:"oken exoires at.Sexoires? carbon::createrromiimesSthis->info strina:"Token is expired:(Sexnires ss Sexpires ‹ timeo) ? 'v// Test with the DB token directlySthis->infod strinaTesting with DB token ---").Sconfio = new Confiquration0:Scontia->se+AcceccToken(SrawlhToken):/ Test 2a: Get DealsReject// Test 2a: Get Deals Summarysunls->restberuealsscontlo// Test 2b: Get PersonsSthis->testGetPersons(Sconfiq)II Test 2c:sthis->testbetacrivitlesscontz0)} catch (Exception Se) {sthis->recordResult testName:'Basic CRUD Operations".success: false. Se->aetno usadedprivate function testCurrentSystemApproach(SocialAccount SsocialAccount): voidf..?private function testGetDeals(Configuration Sconfig): void{...}private function testGetPersons(Configuration Sconfig): void{...}private function testGetActivities(Configuration Sconfiq): void{...}1 usageprivate function testFieldMetadata(SocialAccount SsocialAccount): void{...}privatq functigns tgst' Accept File &- Cogfiauaatiion, Sconfig) or voigi → . }100% Lz• Mon 18 May 9:43:37AskJiminnyReportActivityServiceTest v+0 ..continue and test all in the command.wDebugging PipedriveThought for 1s0 4 tasks done1 Update Poc to test ofticial SDK methods properly• Test v2 API support if available3 Test error handlina properiv• TestPipedriveOfficialSdkCommand.phpThouahtserror while ediung lestripedriveurticlalsakcommano.onp ›Thoughts ›Read TestPipedriveOfficialSdkCommand.pho #L175-224lhoughts@ TestPipedriveOfficialSdkCommand.phpThoughts >•TestPipedriveOfficialSdkCommand.phpDoad ToctDinodrivoOfficia|CdkCommand nhn #ll 220.270Thouahts>Read [EMAIL] #L3/9-408lallteinie•TestPipedriveOfficialSdkCommand.phpTestPipedriveOfficialSdkCommand.php+6 -6757 tokens+5 -5+16 -6681 tokens* Reiect allAccent alliAsk anvthina (&4-L<> CodeSWF.16WN Windsurf Toams 127-1UTF.8Po 4 spaces...
|
NULL
|
-356708856042595059
|
NULL
|
click
|
ocr
|
NULL
|
PhostormVIewINavicarecodeLaravelKeractorFV faVsco. PhostormVIewINavicarecodeLaravelKeractorFV faVsco.js?° pipedrive-sdk-pocProiectC ActivityController.ong© SyncProfileMetadata.phpCConvertLeadActivities.ong© SyncPlanhat.php©syncleammetadata.ong© TestPipedriveOfficialSdkCommand.phUpoateopponunityspecrications.onocreatePlaybookcreatedevent.ong>@ Dealinsights>@Dev> 0 Dialersc) IntearationApp/Service.php© LeadConverted.php© CreateSelfCoachedEvent.phpD DTOSC ElasticsearchC) CreateCommentedEvent.phoC) CreateSmsSentEvent.ohpC) PlanhatActivityListener.pho(C)AskAnvthinaPromotService.ohd› EnqagementStatsш GeckoExport(C)AutomatedReportsCommand.ohvphp api y2.ohoC) RequestGenerateReport.Job.oho/>D Livestream(C) AutomatedReportkesulconp© AutomatedReport.phpA console [STAGING]Mallboxes• MidratePlavbackithemes• M Plavbooksdectarelstrict_types-tyD Playlists> M Postmarknanespace Jininny console comnanos crm> M PronhetAuse…..v M Renorto© AutomatedReportsCommand.phpC) AutomatedRenortsRetentionPolicvCor14 Dclass syncubnects extends command© AutomatedReportsSendCommand.pht© CreateMockAskJiminnyReportResultC© DeleteReportCommand.php© GenerateMarketingReport.php© Team.php© Usage.php>C Slack* The name and sianature of the console commandi* ovar strind›_ Teams>C TracksC Transcription>C TwilioC Users> M Vocabularv7301nrotected ssionature = crm:sunc-ohnects -team?, ~--svnctr** The console command descrintion.>Zoom*Avan strinaC) Command.oho© CreateDatabaseUsers.ohpprotected $description = 'Sync remote CRM objects.';C)DatabaseTiableCount.oho© DeleteOIdAiCrmNotesCommand.ohv* Eyecuto the concole commandlCDeleteS?Leftoverscommand.ohn@ DevPostmanCommand.phppublic function handle: voidf...;C) DiarizeViaAiParticinantldentificationCom@ EncrvntTokensCommand.nhn(C EnaadementStatsRedenerateCommand.r( FeatureFlaasHelner nhr69 6t)protected function getStaggerDelavSeconds@: floatf...?© FixCrossTenantlssues.php© FlushRolesPermissionsCache.php© GeneratelnternalWebhookToken.php9 GrounSetDefaultLanguageCommand.php74 6tprotected function getMaxDelavSeconds@: int{...}81 6t>protected function getLoqPrefix@: stringf...;l© HelperTruncateCoachingTables.phpHelner Code will hoin INF to underctand vour Laravel ann code II Generate II Don't Show Anvmore (8 minutes aaol=custom.loglaravel.logA SF [jiminny@localhost]« HS_local [jiminny@localhost]A console [PROD]© TestPipedriveOfficialSdkCommand.php x © Kernel.phpA console [EU]class TestPipedrive0fficialSdkCommand extends Command private tunction testbasiccruduperations (soclaLAccount ssoclaLAccount vola-rawubloken = ssoclaLaccount->cecrrovlderuserlokenorSthis->info( string: "DB token (Length:"")"Sthis->info( string: "DB token preview: ". substr(SrawDbToken. offset: 0,lengthcheck token expirySexpires = SsocialAccount->expires:sthas->inrol string:"oken exoires at.Sexoires? carbon::createrromiimesSthis->info strina:"Token is expired:(Sexnires ss Sexpires ‹ timeo) ? 'v// Test with the DB token directlySthis->infod strinaTesting with DB token ---").Sconfio = new Confiquration0:Scontia->se+AcceccToken(SrawlhToken):/ Test 2a: Get DealsReject// Test 2a: Get Deals Summarysunls->restberuealsscontlo// Test 2b: Get PersonsSthis->testGetPersons(Sconfiq)II Test 2c:sthis->testbetacrivitlesscontz0)} catch (Exception Se) {sthis->recordResult testName:'Basic CRUD Operations".success: false. Se->aetno usadedprivate function testCurrentSystemApproach(SocialAccount SsocialAccount): voidf..?private function testGetDeals(Configuration Sconfig): void{...}private function testGetPersons(Configuration Sconfig): void{...}private function testGetActivities(Configuration Sconfiq): void{...}1 usageprivate function testFieldMetadata(SocialAccount SsocialAccount): void{...}privatq functigns tgst' Accept File &- Cogfiauaatiion, Sconfig) or voigi → . }100% Lz• Mon 18 May 9:43:37AskJiminnyReportActivityServiceTest v+0 ..continue and test all in the command.wDebugging PipedriveThought for 1s0 4 tasks done1 Update Poc to test ofticial SDK methods properly• Test v2 API support if available3 Test error handlina properiv• TestPipedriveOfficialSdkCommand.phpThouahtserror while ediung lestripedriveurticlalsakcommano.onp ›Thoughts ›Read TestPipedriveOfficialSdkCommand.pho #L175-224lhoughts@ TestPipedriveOfficialSdkCommand.phpThoughts >•TestPipedriveOfficialSdkCommand.phpDoad ToctDinodrivoOfficia|CdkCommand nhn #ll 220.270Thouahts>Read [EMAIL] #L3/9-408lallteinie•TestPipedriveOfficialSdkCommand.phpTestPipedriveOfficialSdkCommand.php+6 -6757 tokens+5 -5+16 -6681 tokens* Reiect allAccent alliAsk anvthina (&4-L<> CodeSWF.16WN Windsurf Toams 127-1UTF.8Po 4 spaces...
|
49416
|
NULL
|
NULL
|
NULL
|
|
49420
|
1764
|
5
|
2026-05-18T06:43:42.839686+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086622839_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
1
51
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Jiminny\Component\Encryption\EncryptedTokenManager;
use Jiminny\Models\SocialAccount;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Pipedrive\versions\v1\Api\ActivitiesApi;
use Pipedrive\versions\v1\Api\ActivityFieldsApi;
use Pipedrive\versions\v1\Api\DealFieldsApi;
use Pipedrive\versions\v1\Api\DealsApi;
use Pipedrive\versions\v1\Api\PersonFieldsApi;
use Pipedrive\versions\v1\Api\PersonsApi;
use Pipedrive\versions\v1\Configuration;
class TestPipedriveOfficialSdkCommand extends Command
{
protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';
protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';
private array $results = [];
private EncryptedTokenManager $tokenManager;
public function __construct(EncryptedTokenManager $tokenManager)
{
parent::__construct();
$this->tokenManager = $tokenManager;
}
public function handle(): int
{
$teamId = (int) $this->argument('teamId');
$this->info("Starting Pipedrive Official SDK POC for Team ID: {$teamId}");
$this->newLine();
try {
$this->runTests($teamId);
} catch (Exception $e) {
$this->error("POC failed with error: {$e->getMessage()}");
$this->error($e->getTraceAsString());
return 1;
}
$this->displayResults();
return 0;
}
private function runTests(int $teamId): void
{
$team = Team::find($teamId);
if (! $team) {
throw new Exception("Team with ID {$teamId} not found");
}
$socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)
->whereHas('sociable', function ($query) use ($team) {
$query->where('team_id', $team->id);
})
->orderByDesc('expires') // Get the most recently expiring token
->first();
if (! $socialAccount) {
throw new Exception("No Pipedrive social account found for team {$teamId}");
}
$this->info("Found Pipedrive account for team: {$team->name}");
$this->newLine();
// Test 1: OAuth Initialization
$this->testOAuthInitialization($socialAccount);
// Test 2: Basic CRUD Operations
$this->testBasicCrudOperations($socialAccount);
// Test 3: Field Metadata
$this->testFieldMetadata($socialAccount);
// Test 4: v2 API Support
$this->testV2ApiSupport($socialAccount);
// Test 5: Error Handling
$this->testErrorHandling($socialAccount);
}
private function testOAuthInitialization(SocialAccount $socialAccount): void
{
$this->info('Test 1: OAuth Initialization');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', true, "Client initialized successfully in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);
}
$this->newLine();
}
private function testBasicCrudOperations(SocialAccount $socialAccount): void
{
$this->info('Test 2: Basic CRUD Operations');
try {
// Get the raw token from database
$rawDbToken = $socialAccount->getProviderUserToken();
$this->info("DB token (length: " . strlen($rawDbToken) . ")");
$this->info("DB token preview: " . substr($rawDbToken, 0, 20) . "...");
// Check token expiry
$expires = $socialAccount->expires;
$this->info("Token expires at: " . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));
$this->info("Token is expired: " . ($expires && $expires < time() ? 'YES' : 'NO'));
// Test with the DB token directly
$this->info("\n--- Testing with DB token ---");
$config = new Configuration();
$config->setAccessToken($rawDbToken);
// Test 2a: Get Deals Summary
$this->testGetDeals($config);
// Test 2b: Get Persons
$this->testGetPersons($config);
// Test 2c: Get Activities
$this->testGetActivities($config);
} catch (Exception $e) {
$this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testCurrentSystemApproach(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
// Simulate how current system makes requests
$token = $socialAccount->getProviderUserToken();
$url = 'https://api.pipedrive.com/api/v2/deals';
$headers = [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
];
$client = new \GuzzleHttp\Client();
$response = $client->get($url, [
'headers' => $headers,
'timeout' => 30,
]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$statusCode = $response->getStatusCode();
$this->recordResult('Current System v2 API', $statusCode === 200, "Status: {$statusCode} in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);
}
}
private function testGetDeals(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealsApi(null, $config);
// Official SDK uses getDealsSummary instead of getDeals
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', true, "Retrieved deals summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersons(Configuration $config): void
{
$this->info('Test 2b: Get Persons');
$startTime = microtime(true);
try {
$apiInstance = new PersonsApi(null, $config);
// Official SDK uses getPersons method
$result = $apiInstance->getPersons(['limit' => 10]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', true, "Retrieved persons in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivities(Configuration $config): void
{
$this->info('Test 2c: Get Activities');
$startTime = microtime(true);
try {
$apiInstance = new ActivitiesApi(null, $config);
// Official SDK uses getActivities method
$result = $apiInstance->getActivities(['limit' => 10]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', true, "Retrieved activities in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);
}
}
private function testFieldMetadata(SocialAccount $socialAccount): void
{
$this->info('Test 3: Field Metadata');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
// Test 3a: Get Deal Fields
$this->testGetDealFields($config);
// Test 3b: Get Person Fields
$this->testGetPersonFields($config);
// Test 3c: Get Activity Fields
$this->testGetActivityFields($config);
} catch (Exception $e) {
$this->recordResult('Field Metadata', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testGetDealFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealFieldsApi(null, $config);
$result = $apiInstance->getDealFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Deal Fields', true, "Retrieved {$count} deal fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersonFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new PersonFieldsApi(null, $config);
$result = $apiInstance->getPersonFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Person Fields', true, "Retrieved {$count} person fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivityFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new ActivityFieldsApi(null, $config);
$result = $apiInstance->getActivityFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Activity Fields', true, "Retrieved {$count} activity fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);
}
}
private function testV2ApiSupport(SocialAccount $socialAccount): void
{
$this->info('Test 4: v2 API Support (Cursor-based Pagination)');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$startTime = microtime(true);
// Test v2 deals endpoint with cursor pagination
// Note: The official SDK may or may not have v2 API support
// This test will determine if v2 is available
try {
$apiInstance = new DealsApi(null, $config);
$updatedSince = Carbon::now()->subDays(30)->toRfc3339String();
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
// Check if response has cursor information (v2 indicator)
// The response object doesn't have getAdditionalData method
// Just test if the call works - v2 API support is limited in official SDK v1
$this->recordResult('v2 API Support', false, "v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);
}
} catch (Exception $e) {
$this->recordResult('v2 API Support', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testErrorHandling(SocialAccount $socialAccount): void
{
$this->info('Test 5: Error Handling');
try {
// Test 5a: Invalid Token
$this->testInvalidToken();
// Test 5b: Non-existent Resource
$this->testNonExistentResource($socialAccount);
} catch (Exception $e) {
$this->recordResult('Error Handling', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testInvalidToken(): void
{
$this->info('Test 5a: Invalid Token');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken('invalid_token');
$client = new DealsApi(null, $config);
$client->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function testNonExistentResource(SocialAccount $socialAccount): void
{
$this->info('Test 5b: Non-existent Resource');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
// Try to get a deal with a very high ID that likely doesn't exist
// Official SDK has getDealsSummary which works without specific ID
// Instead, let's test with an invalid field ID
try {
$fieldClient = new DealFieldsApi(null, $config);
$fieldClient->getDealField(999999999); // Very high ID that likely doesn't exist
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, $e->getMessage(), $elapsed);
}
}
private function recordResult(string $testName, bool $success, string $message, float $elapsed): void
{
$this->results[] = [
'test' => $testName,
'success' => $success,
'message' => $message,
'elapsed' => $elapsed,
];
$status = $success ? '✅ PASS' : '❌ FAIL';
$this->line(" {$status} - {$testName}: {$message}");
}
private function displayResults(): void
{
$this->newLine();
$this->info('=== POC Test Results Summary ===');
$this->newLine();
$total = count($this->results);
$passed = count(array_filter($this->results, fn ($r) => $r['success']));
$failed = $total - $passed;
$this->line("Total Tests: {$total}");
$this->line("Passed: {$passed}");
$this->line("Failed: {$failed}");
$this->newLine();
$totalTime = array_sum(array_column($this->results, 'elapsed'));
$this->line("Total Execution Time: {$totalTime}ms");
$this->newLine();
if ($failed > 0) {
$this->warn('Failed Tests:');
foreach ($this->results as $result) {
if (! $result['success']) {
$this->line(" - {$result['test']}: {$result['message']}");
}
}
$this->newLine();
}
$this->info('=== Detailed Results ===');
foreach ($this->results as $result) {
$status = $result['success'] ? '✅' : '❌';
$this->line("{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}");
}
$this->newLine();
$this->info('=== Recommendations ===');
if ($passed === $total) {
$this->info('✅ All tests passed! The official SDK is viable for migration.');
$this->info('Next steps: Proceed with full migration planning.');
} else {
$this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');
$this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"pipedrive-sdk-poc, menu","depth":5,"on_screen":true,"help_text":"Git Branch: pipedrive-sdk-poc","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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","role_description":"text entry area","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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":false,"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},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"51","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.02013889,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"on_screen":false,"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},"on_screen":false,"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\\Console\\Commands\\Crm;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Console\\Command;\nuse Jiminny\\Component\\Encryption\\EncryptedTokenManager;\nuse Jiminny\\Models\\SocialAccount;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Pipedrive\\versions\\v1\\Api\\ActivitiesApi;\nuse Pipedrive\\versions\\v1\\Api\\ActivityFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonsApi;\nuse Pipedrive\\versions\\v1\\Configuration;\n\nclass TestPipedriveOfficialSdkCommand extends Command\n{\n protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';\n\n protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';\n\n private array $results = [];\n\n private EncryptedTokenManager $tokenManager;\n\n public function __construct(EncryptedTokenManager $tokenManager)\n {\n parent::__construct();\n $this->tokenManager = $tokenManager;\n }\n\n public function handle(): int\n {\n $teamId = (int) $this->argument('teamId');\n\n $this->info(\"Starting Pipedrive Official SDK POC for Team ID: {$teamId}\");\n $this->newLine();\n\n try {\n $this->runTests($teamId);\n } catch (Exception $e) {\n $this->error(\"POC failed with error: {$e->getMessage()}\");\n $this->error($e->getTraceAsString());\n\n return 1;\n }\n\n $this->displayResults();\n\n return 0;\n }\n\n private function runTests(int $teamId): void\n {\n $team = Team::find($teamId);\n if (! $team) {\n throw new Exception(\"Team with ID {$teamId} not found\");\n }\n\n $socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)\n ->whereHas('sociable', function ($query) use ($team) {\n $query->where('team_id', $team->id);\n })\n ->orderByDesc('expires') // Get the most recently expiring token\n ->first();\n\n if (! $socialAccount) {\n throw new Exception(\"No Pipedrive social account found for team {$teamId}\");\n }\n\n $this->info(\"Found Pipedrive account for team: {$team->name}\");\n $this->newLine();\n\n // Test 1: OAuth Initialization\n $this->testOAuthInitialization($socialAccount);\n\n // Test 2: Basic CRUD Operations\n $this->testBasicCrudOperations($socialAccount);\n\n // Test 3: Field Metadata\n $this->testFieldMetadata($socialAccount);\n\n // Test 4: v2 API Support\n $this->testV2ApiSupport($socialAccount);\n\n // Test 5: Error Handling\n $this->testErrorHandling($socialAccount);\n }\n\n private function testOAuthInitialization(SocialAccount $socialAccount): void\n {\n $this->info('Test 1: OAuth Initialization');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $client = new DealsApi(null, $config);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', true, \"Client initialized successfully in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);\n }\n\n $this->newLine();\n }\n\n private function testBasicCrudOperations(SocialAccount $socialAccount): void\n {\n $this->info('Test 2: Basic CRUD Operations');\n\n try {\n // Get the raw token from database\n $rawDbToken = $socialAccount->getProviderUserToken();\n $this->info(\"DB token (length: \" . strlen($rawDbToken) . \")\");\n $this->info(\"DB token preview: \" . substr($rawDbToken, 0, 20) . \"...\");\n\n // Check token expiry\n $expires = $socialAccount->expires;\n $this->info(\"Token expires at: \" . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));\n $this->info(\"Token is expired: \" . ($expires && $expires < time() ? 'YES' : 'NO'));\n\n // Test with the DB token directly\n $this->info(\"\\n--- Testing with DB token ---\");\n $config = new Configuration();\n $config->setAccessToken($rawDbToken);\n\n // Test 2a: Get Deals Summary\n $this->testGetDeals($config);\n\n // Test 2b: Get Persons\n $this->testGetPersons($config);\n\n // Test 2c: Get Activities\n $this->testGetActivities($config);\n } catch (Exception $e) {\n $this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testCurrentSystemApproach(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n // Simulate how current system makes requests\n $token = $socialAccount->getProviderUserToken();\n $url = 'https://api.pipedrive.com/api/v2/deals';\n\n $headers = [\n 'Authorization' => 'Bearer ' . $token,\n 'Accept' => 'application/json',\n ];\n\n $client = new \\GuzzleHttp\\Client();\n $response = $client->get($url, [\n 'headers' => $headers,\n 'timeout' => 30,\n ]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $statusCode = $response->getStatusCode();\n $this->recordResult('Current System v2 API', $statusCode === 200, \"Status: {$statusCode} in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetDeals(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealsApi(null, $config);\n // Official SDK uses getDealsSummary instead of getDeals\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', true, \"Retrieved deals summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersons(Configuration $config): void\n {\n $this->info('Test 2b: Get Persons');\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonsApi(null, $config);\n // Official SDK uses getPersons method\n $result = $apiInstance->getPersons(['limit' => 10]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', true, \"Retrieved persons in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivities(Configuration $config): void\n {\n $this->info('Test 2c: Get Activities');\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivitiesApi(null, $config);\n // Official SDK uses getActivities method\n $result = $apiInstance->getActivities(['limit' => 10]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', true, \"Retrieved activities in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testFieldMetadata(SocialAccount $socialAccount): void\n {\n $this->info('Test 3: Field Metadata');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n // Test 3a: Get Deal Fields\n $this->testGetDealFields($config);\n\n // Test 3b: Get Person Fields\n $this->testGetPersonFields($config);\n\n // Test 3c: Get Activity Fields\n $this->testGetActivityFields($config);\n } catch (Exception $e) {\n $this->recordResult('Field Metadata', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testGetDealFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealFieldsApi(null, $config);\n $result = $apiInstance->getDealFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Deal Fields', true, \"Retrieved {$count} deal fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersonFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonFieldsApi(null, $config);\n $result = $apiInstance->getPersonFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Person Fields', true, \"Retrieved {$count} person fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivityFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivityFieldsApi(null, $config);\n $result = $apiInstance->getActivityFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Activity Fields', true, \"Retrieved {$count} activity fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testV2ApiSupport(SocialAccount $socialAccount): void\n {\n $this->info('Test 4: v2 API Support (Cursor-based Pagination)');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $startTime = microtime(true);\n\n // Test v2 deals endpoint with cursor pagination\n // Note: The official SDK may or may not have v2 API support\n // This test will determine if v2 is available\n try {\n $apiInstance = new DealsApi(null, $config);\n $updatedSince = Carbon::now()->subDays(30)->toRfc3339String();\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n\n // Check if response has cursor information (v2 indicator)\n // The response object doesn't have getAdditionalData method\n // Just test if the call works - v2 API support is limited in official SDK v1\n $this->recordResult('v2 API Support', false, \"v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);\n }\n } catch (Exception $e) {\n $this->recordResult('v2 API Support', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testErrorHandling(SocialAccount $socialAccount): void\n {\n $this->info('Test 5: Error Handling');\n\n try {\n // Test 5a: Invalid Token\n $this->testInvalidToken();\n\n // Test 5b: Non-existent Resource\n $this->testNonExistentResource($socialAccount);\n } catch (Exception $e) {\n $this->recordResult('Error Handling', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testInvalidToken(): void\n {\n $this->info('Test 5a: Invalid Token');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken('invalid_token');\n $client = new DealsApi(null, $config);\n\n $client->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function testNonExistentResource(SocialAccount $socialAccount): void\n {\n $this->info('Test 5b: Non-existent Resource');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n $client = new DealsApi(null, $config);\n\n // Try to get a deal with a very high ID that likely doesn't exist\n // Official SDK has getDealsSummary which works without specific ID\n // Instead, let's test with an invalid field ID\n try {\n $fieldClient = new DealFieldsApi(null, $config);\n $fieldClient->getDealField(999999999); // Very high ID that likely doesn't exist\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function recordResult(string $testName, bool $success, string $message, float $elapsed): void\n {\n $this->results[] = [\n 'test' => $testName,\n 'success' => $success,\n 'message' => $message,\n 'elapsed' => $elapsed,\n ];\n\n $status = $success ? '✅ PASS' : '❌ FAIL';\n $this->line(\" {$status} - {$testName}: {$message}\");\n }\n\n private function displayResults(): void\n {\n $this->newLine();\n $this->info('=== POC Test Results Summary ===');\n $this->newLine();\n\n $total = count($this->results);\n $passed = count(array_filter($this->results, fn ($r) => $r['success']));\n $failed = $total - $passed;\n\n $this->line(\"Total Tests: {$total}\");\n $this->line(\"Passed: {$passed}\");\n $this->line(\"Failed: {$failed}\");\n $this->newLine();\n\n $totalTime = array_sum(array_column($this->results, 'elapsed'));\n $this->line(\"Total Execution Time: {$totalTime}ms\");\n $this->newLine();\n\n if ($failed > 0) {\n $this->warn('Failed Tests:');\n foreach ($this->results as $result) {\n if (! $result['success']) {\n $this->line(\" - {$result['test']}: {$result['message']}\");\n }\n }\n $this->newLine();\n }\n\n $this->info('=== Detailed Results ===');\n foreach ($this->results as $result) {\n $status = $result['success'] ? '✅' : '❌';\n $this->line(\"{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}\");\n }\n\n $this->newLine();\n $this->info('=== Recommendations ===');\n\n if ($passed === $total) {\n $this->info('✅ All tests passed! The official SDK is viable for migration.');\n $this->info('Next steps: Proceed with full migration planning.');\n } else {\n $this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');\n $this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Console\\Commands\\Crm;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Console\\Command;\nuse Jiminny\\Component\\Encryption\\EncryptedTokenManager;\nuse Jiminny\\Models\\SocialAccount;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Pipedrive\\versions\\v1\\Api\\ActivitiesApi;\nuse Pipedrive\\versions\\v1\\Api\\ActivityFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonsApi;\nuse Pipedrive\\versions\\v1\\Configuration;\n\nclass TestPipedriveOfficialSdkCommand extends Command\n{\n protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';\n\n protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';\n\n private array $results = [];\n\n private EncryptedTokenManager $tokenManager;\n\n public function __construct(EncryptedTokenManager $tokenManager)\n {\n parent::__construct();\n $this->tokenManager = $tokenManager;\n }\n\n public function handle(): int\n {\n $teamId = (int) $this->argument('teamId');\n\n $this->info(\"Starting Pipedrive Official SDK POC for Team ID: {$teamId}\");\n $this->newLine();\n\n try {\n $this->runTests($teamId);\n } catch (Exception $e) {\n $this->error(\"POC failed with error: {$e->getMessage()}\");\n $this->error($e->getTraceAsString());\n\n return 1;\n }\n\n $this->displayResults();\n\n return 0;\n }\n\n private function runTests(int $teamId): void\n {\n $team = Team::find($teamId);\n if (! $team) {\n throw new Exception(\"Team with ID {$teamId} not found\");\n }\n\n $socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)\n ->whereHas('sociable', function ($query) use ($team) {\n $query->where('team_id', $team->id);\n })\n ->orderByDesc('expires') // Get the most recently expiring token\n ->first();\n\n if (! $socialAccount) {\n throw new Exception(\"No Pipedrive social account found for team {$teamId}\");\n }\n\n $this->info(\"Found Pipedrive account for team: {$team->name}\");\n $this->newLine();\n\n // Test 1: OAuth Initialization\n $this->testOAuthInitialization($socialAccount);\n\n // Test 2: Basic CRUD Operations\n $this->testBasicCrudOperations($socialAccount);\n\n // Test 3: Field Metadata\n $this->testFieldMetadata($socialAccount);\n\n // Test 4: v2 API Support\n $this->testV2ApiSupport($socialAccount);\n\n // Test 5: Error Handling\n $this->testErrorHandling($socialAccount);\n }\n\n private function testOAuthInitialization(SocialAccount $socialAccount): void\n {\n $this->info('Test 1: OAuth Initialization');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $client = new DealsApi(null, $config);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', true, \"Client initialized successfully in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);\n }\n\n $this->newLine();\n }\n\n private function testBasicCrudOperations(SocialAccount $socialAccount): void\n {\n $this->info('Test 2: Basic CRUD Operations');\n\n try {\n // Get the raw token from database\n $rawDbToken = $socialAccount->getProviderUserToken();\n $this->info(\"DB token (length: \" . strlen($rawDbToken) . \")\");\n $this->info(\"DB token preview: \" . substr($rawDbToken, 0, 20) . \"...\");\n\n // Check token expiry\n $expires = $socialAccount->expires;\n $this->info(\"Token expires at: \" . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));\n $this->info(\"Token is expired: \" . ($expires && $expires < time() ? 'YES' : 'NO'));\n\n // Test with the DB token directly\n $this->info(\"\\n--- Testing with DB token ---\");\n $config = new Configuration();\n $config->setAccessToken($rawDbToken);\n\n // Test 2a: Get Deals Summary\n $this->testGetDeals($config);\n\n // Test 2b: Get Persons\n $this->testGetPersons($config);\n\n // Test 2c: Get Activities\n $this->testGetActivities($config);\n } catch (Exception $e) {\n $this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testCurrentSystemApproach(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n // Simulate how current system makes requests\n $token = $socialAccount->getProviderUserToken();\n $url = 'https://api.pipedrive.com/api/v2/deals';\n\n $headers = [\n 'Authorization' => 'Bearer ' . $token,\n 'Accept' => 'application/json',\n ];\n\n $client = new \\GuzzleHttp\\Client();\n $response = $client->get($url, [\n 'headers' => $headers,\n 'timeout' => 30,\n ]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $statusCode = $response->getStatusCode();\n $this->recordResult('Current System v2 API', $statusCode === 200, \"Status: {$statusCode} in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetDeals(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealsApi(null, $config);\n // Official SDK uses getDealsSummary instead of getDeals\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', true, \"Retrieved deals summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersons(Configuration $config): void\n {\n $this->info('Test 2b: Get Persons');\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonsApi(null, $config);\n // Official SDK uses getPersons method\n $result = $apiInstance->getPersons(['limit' => 10]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', true, \"Retrieved persons in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivities(Configuration $config): void\n {\n $this->info('Test 2c: Get Activities');\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivitiesApi(null, $config);\n // Official SDK uses getActivities method\n $result = $apiInstance->getActivities(['limit' => 10]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', true, \"Retrieved activities in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testFieldMetadata(SocialAccount $socialAccount): void\n {\n $this->info('Test 3: Field Metadata');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n // Test 3a: Get Deal Fields\n $this->testGetDealFields($config);\n\n // Test 3b: Get Person Fields\n $this->testGetPersonFields($config);\n\n // Test 3c: Get Activity Fields\n $this->testGetActivityFields($config);\n } catch (Exception $e) {\n $this->recordResult('Field Metadata', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testGetDealFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealFieldsApi(null, $config);\n $result = $apiInstance->getDealFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Deal Fields', true, \"Retrieved {$count} deal fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersonFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonFieldsApi(null, $config);\n $result = $apiInstance->getPersonFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Person Fields', true, \"Retrieved {$count} person fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivityFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivityFieldsApi(null, $config);\n $result = $apiInstance->getActivityFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Activity Fields', true, \"Retrieved {$count} activity fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testV2ApiSupport(SocialAccount $socialAccount): void\n {\n $this->info('Test 4: v2 API Support (Cursor-based Pagination)');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $startTime = microtime(true);\n\n // Test v2 deals endpoint with cursor pagination\n // Note: The official SDK may or may not have v2 API support\n // This test will determine if v2 is available\n try {\n $apiInstance = new DealsApi(null, $config);\n $updatedSince = Carbon::now()->subDays(30)->toRfc3339String();\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n\n // Check if response has cursor information (v2 indicator)\n // The response object doesn't have getAdditionalData method\n // Just test if the call works - v2 API support is limited in official SDK v1\n $this->recordResult('v2 API Support', false, \"v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);\n }\n } catch (Exception $e) {\n $this->recordResult('v2 API Support', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testErrorHandling(SocialAccount $socialAccount): void\n {\n $this->info('Test 5: Error Handling');\n\n try {\n // Test 5a: Invalid Token\n $this->testInvalidToken();\n\n // Test 5b: Non-existent Resource\n $this->testNonExistentResource($socialAccount);\n } catch (Exception $e) {\n $this->recordResult('Error Handling', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testInvalidToken(): void\n {\n $this->info('Test 5a: Invalid Token');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken('invalid_token');\n $client = new DealsApi(null, $config);\n\n $client->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function testNonExistentResource(SocialAccount $socialAccount): void\n {\n $this->info('Test 5b: Non-existent Resource');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n $client = new DealsApi(null, $config);\n\n // Try to get a deal with a very high ID that likely doesn't exist\n // Official SDK has getDealsSummary which works without specific ID\n // Instead, let's test with an invalid field ID\n try {\n $fieldClient = new DealFieldsApi(null, $config);\n $fieldClient->getDealField(999999999); // Very high ID that likely doesn't exist\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function recordResult(string $testName, bool $success, string $message, float $elapsed): void\n {\n $this->results[] = [\n 'test' => $testName,\n 'success' => $success,\n 'message' => $message,\n 'elapsed' => $elapsed,\n ];\n\n $status = $success ? '✅ PASS' : '❌ FAIL';\n $this->line(\" {$status} - {$testName}: {$message}\");\n }\n\n private function displayResults(): void\n {\n $this->newLine();\n $this->info('=== POC Test Results Summary ===');\n $this->newLine();\n\n $total = count($this->results);\n $passed = count(array_filter($this->results, fn ($r) => $r['success']));\n $failed = $total - $passed;\n\n $this->line(\"Total Tests: {$total}\");\n $this->line(\"Passed: {$passed}\");\n $this->line(\"Failed: {$failed}\");\n $this->newLine();\n\n $totalTime = array_sum(array_column($this->results, 'elapsed'));\n $this->line(\"Total Execution Time: {$totalTime}ms\");\n $this->newLine();\n\n if ($failed > 0) {\n $this->warn('Failed Tests:');\n foreach ($this->results as $result) {\n if (! $result['success']) {\n $this->line(\" - {$result['test']}: {$result['message']}\");\n }\n }\n $this->newLine();\n }\n\n $this->info('=== Detailed Results ===');\n foreach ($this->results as $result) {\n $status = $result['success'] ? '✅' : '❌';\n $this->line(\"{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}\");\n }\n\n $this->newLine();\n $this->info('=== Recommendations ===');\n\n if ($passed === $total) {\n $this->info('✅ All tests passed! The official SDK is viable for migration.');\n $this->info('Next steps: Proceed with full migration planning.');\n } else {\n $this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');\n $this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-7605369110515316950
|
25623949098764932
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
1
51
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Jiminny\Component\Encryption\EncryptedTokenManager;
use Jiminny\Models\SocialAccount;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Pipedrive\versions\v1\Api\ActivitiesApi;
use Pipedrive\versions\v1\Api\ActivityFieldsApi;
use Pipedrive\versions\v1\Api\DealFieldsApi;
use Pipedrive\versions\v1\Api\DealsApi;
use Pipedrive\versions\v1\Api\PersonFieldsApi;
use Pipedrive\versions\v1\Api\PersonsApi;
use Pipedrive\versions\v1\Configuration;
class TestPipedriveOfficialSdkCommand extends Command
{
protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';
protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';
private array $results = [];
private EncryptedTokenManager $tokenManager;
public function __construct(EncryptedTokenManager $tokenManager)
{
parent::__construct();
$this->tokenManager = $tokenManager;
}
public function handle(): int
{
$teamId = (int) $this->argument('teamId');
$this->info("Starting Pipedrive Official SDK POC for Team ID: {$teamId}");
$this->newLine();
try {
$this->runTests($teamId);
} catch (Exception $e) {
$this->error("POC failed with error: {$e->getMessage()}");
$this->error($e->getTraceAsString());
return 1;
}
$this->displayResults();
return 0;
}
private function runTests(int $teamId): void
{
$team = Team::find($teamId);
if (! $team) {
throw new Exception("Team with ID {$teamId} not found");
}
$socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)
->whereHas('sociable', function ($query) use ($team) {
$query->where('team_id', $team->id);
})
->orderByDesc('expires') // Get the most recently expiring token
->first();
if (! $socialAccount) {
throw new Exception("No Pipedrive social account found for team {$teamId}");
}
$this->info("Found Pipedrive account for team: {$team->name}");
$this->newLine();
// Test 1: OAuth Initialization
$this->testOAuthInitialization($socialAccount);
// Test 2: Basic CRUD Operations
$this->testBasicCrudOperations($socialAccount);
// Test 3: Field Metadata
$this->testFieldMetadata($socialAccount);
// Test 4: v2 API Support
$this->testV2ApiSupport($socialAccount);
// Test 5: Error Handling
$this->testErrorHandling($socialAccount);
}
private function testOAuthInitialization(SocialAccount $socialAccount): void
{
$this->info('Test 1: OAuth Initialization');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', true, "Client initialized successfully in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);
}
$this->newLine();
}
private function testBasicCrudOperations(SocialAccount $socialAccount): void
{
$this->info('Test 2: Basic CRUD Operations');
try {
// Get the raw token from database
$rawDbToken = $socialAccount->getProviderUserToken();
$this->info("DB token (length: " . strlen($rawDbToken) . ")");
$this->info("DB token preview: " . substr($rawDbToken, 0, 20) . "...");
// Check token expiry
$expires = $socialAccount->expires;
$this->info("Token expires at: " . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));
$this->info("Token is expired: " . ($expires && $expires < time() ? 'YES' : 'NO'));
// Test with the DB token directly
$this->info("\n--- Testing with DB token ---");
$config = new Configuration();
$config->setAccessToken($rawDbToken);
// Test 2a: Get Deals Summary
$this->testGetDeals($config);
// Test 2b: Get Persons
$this->testGetPersons($config);
// Test 2c: Get Activities
$this->testGetActivities($config);
} catch (Exception $e) {
$this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testCurrentSystemApproach(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
// Simulate how current system makes requests
$token = $socialAccount->getProviderUserToken();
$url = 'https://api.pipedrive.com/api/v2/deals';
$headers = [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
];
$client = new \GuzzleHttp\Client();
$response = $client->get($url, [
'headers' => $headers,
'timeout' => 30,
]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$statusCode = $response->getStatusCode();
$this->recordResult('Current System v2 API', $statusCode === 200, "Status: {$statusCode} in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);
}
}
private function testGetDeals(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealsApi(null, $config);
// Official SDK uses getDealsSummary instead of getDeals
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', true, "Retrieved deals summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersons(Configuration $config): void
{
$this->info('Test 2b: Get Persons');
$startTime = microtime(true);
try {
$apiInstance = new PersonsApi(null, $config);
// Official SDK uses getPersons method
$result = $apiInstance->getPersons(['limit' => 10]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', true, "Retrieved persons in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivities(Configuration $config): void
{
$this->info('Test 2c: Get Activities');
$startTime = microtime(true);
try {
$apiInstance = new ActivitiesApi(null, $config);
// Official SDK uses getActivities method
$result = $apiInstance->getActivities(['limit' => 10]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', true, "Retrieved activities in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);
}
}
private function testFieldMetadata(SocialAccount $socialAccount): void
{
$this->info('Test 3: Field Metadata');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
// Test 3a: Get Deal Fields
$this->testGetDealFields($config);
// Test 3b: Get Person Fields
$this->testGetPersonFields($config);
// Test 3c: Get Activity Fields
$this->testGetActivityFields($config);
} catch (Exception $e) {
$this->recordResult('Field Metadata', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testGetDealFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealFieldsApi(null, $config);
$result = $apiInstance->getDealFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Deal Fields', true, "Retrieved {$count} deal fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersonFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new PersonFieldsApi(null, $config);
$result = $apiInstance->getPersonFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Person Fields', true, "Retrieved {$count} person fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivityFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new ActivityFieldsApi(null, $config);
$result = $apiInstance->getActivityFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Activity Fields', true, "Retrieved {$count} activity fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);
}
}
private function testV2ApiSupport(SocialAccount $socialAccount): void
{
$this->info('Test 4: v2 API Support (Cursor-based Pagination)');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$startTime = microtime(true);
// Test v2 deals endpoint with cursor pagination
// Note: The official SDK may or may not have v2 API support
// This test will determine if v2 is available
try {
$apiInstance = new DealsApi(null, $config);
$updatedSince = Carbon::now()->subDays(30)->toRfc3339String();
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
// Check if response has cursor information (v2 indicator)
// The response object doesn't have getAdditionalData method
// Just test if the call works - v2 API support is limited in official SDK v1
$this->recordResult('v2 API Support', false, "v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);
}
} catch (Exception $e) {
$this->recordResult('v2 API Support', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testErrorHandling(SocialAccount $socialAccount): void
{
$this->info('Test 5: Error Handling');
try {
// Test 5a: Invalid Token
$this->testInvalidToken();
// Test 5b: Non-existent Resource
$this->testNonExistentResource($socialAccount);
} catch (Exception $e) {
$this->recordResult('Error Handling', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testInvalidToken(): void
{
$this->info('Test 5a: Invalid Token');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken('invalid_token');
$client = new DealsApi(null, $config);
$client->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function testNonExistentResource(SocialAccount $socialAccount): void
{
$this->info('Test 5b: Non-existent Resource');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
// Try to get a deal with a very high ID that likely doesn't exist
// Official SDK has getDealsSummary which works without specific ID
// Instead, let's test with an invalid field ID
try {
$fieldClient = new DealFieldsApi(null, $config);
$fieldClient->getDealField(999999999); // Very high ID that likely doesn't exist
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, $e->getMessage(), $elapsed);
}
}
private function recordResult(string $testName, bool $success, string $message, float $elapsed): void
{
$this->results[] = [
'test' => $testName,
'success' => $success,
'message' => $message,
'elapsed' => $elapsed,
];
$status = $success ? '✅ PASS' : '❌ FAIL';
$this->line(" {$status} - {$testName}: {$message}");
}
private function displayResults(): void
{
$this->newLine();
$this->info('=== POC Test Results Summary ===');
$this->newLine();
$total = count($this->results);
$passed = count(array_filter($this->results, fn ($r) => $r['success']));
$failed = $total - $passed;
$this->line("Total Tests: {$total}");
$this->line("Passed: {$passed}");
$this->line("Failed: {$failed}");
$this->newLine();
$totalTime = array_sum(array_column($this->results, 'elapsed'));
$this->line("Total Execution Time: {$totalTime}ms");
$this->newLine();
if ($failed > 0) {
$this->warn('Failed Tests:');
foreach ($this->results as $result) {
if (! $result['success']) {
$this->line(" - {$result['test']}: {$result['message']}");
}
}
$this->newLine();
}
$this->info('=== Detailed Results ===');
foreach ($this->results as $result) {
$status = $result['success'] ? '✅' : '❌';
$this->line("{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}");
}
$this->newLine();
$this->info('=== Recommendations ===');
if ($passed === $total) {
$this->info('✅ All tests passed! The official SDK is viable for migration.');
$this->info('Next steps: Proceed with full migration planning.');
} else {
$this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');
$this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
49422
|
1765
|
6
|
2026-05-18T06:43:46.505933+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086626505_m2.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhostormVIewINavicarecodeLaravelKeractorFV faVsco. PhostormVIewINavicarecodeLaravelKeractorFV faVsco.js?° pipedrive-sdk-pocProiectC ActivityController.ong© SyncProfileMetadata.phpCConvertLeadActivities.ong© SyncPlanhat.php©syncleammeradata.ong© TestPipedriveOfficialSdkCommand.phUpoateopponunityspecrications.onocreatePlaybookcreatedevent.ong>@ Dealinsights>@Dev> 0 Dialersc) IntearationApp/Service.php© LeadConverted.php© CreateSelfCoachedEvent.phpD DTOSC ElasticsearchC) CreateCommentedEvent.phoC) CreateSmsSentEvent.ohpC) PlanhatActivityListener.pho(C)AskAnvthinaPromotService.ohd› EnqagementStatsш GeckoExport(C)AutomatedReportsCommand.ohvphp api y2.ohoC) RequestGenerateReport.Job.oho/>D Livestream(C) AutomatedReportkesulconp© AutomatedReport.phpA console [STAGING]Mallboxes• MidratePlavbackithemes• M Plavbooksdectarelstrict_types-ty,Playlists> M Postmarknanespace Jininny console comnanos crm> M PronhetAuse…..v M Renorto© AutomatedReportsCommand.phpC) AutomatedRenortsRetentionPolicvCor14 Dclass syncubnects extends command© AutomatedReportsSendCommand.pht© CreateMockAskJiminnyReportResultC© DeleteReportCommand.php© GenerateMarketingReport.php© Team.php© Usage.php>C Slack* The name and sianature of the console commandi* ovar strind›_ Teams>C TracksC Transcription>C TwilioC Users> M Vocabularv7301nrotected ssionature = crm:sunc-ohnects -team?, ~--svnctr** The console command descrintion.>Zoom*Avan strinaC) Command.oho© CreateDatabaseUsers.ohpprotected $description = 'Sync remote CRM objects.';C)DatabaseTiableCount.oho© DeleteOIdAiCrmNotesCommand.ohv* Eyecuto the concole commandlCDeleteS?Leftoverscommand.ohn@ DevPostmanCommand.phppublic function handle: voidf...;C) DiarizeViaAiParticinantldentificationCom@ EncrvntTokensCommand.nhn(C EnaaaementStatsReaenerateCommand.n( FeatureFlaasHelner nhr69 6t)protected function getStaggerDelavSeconds@: floatf...© FixCrossTenantlssues.php© FlushRolesPermissionsCache.php© GeneratelnternalWebhookToken.php9 GrounSetDefaultLanguageCommand.php74 6tprotected function getMaxDelavSeconds@: int{...}81 6t>protected function getlogPrefix@: stringf...;© HelperTruncateCoachingTables.phpHelner Code will hoin INF to underctand vour Laravel ann code II Generate II Don't Show Anvmore (8 minutes aaol=custom.log=laravel.log4 SF [jiminny@localhost]« HS_local [jiminny@localhost]A console [PROD]© TestPipedriveOfficialSdkCommand.php x © Kernel.phpA console [EU]class TestPipedrive0fficialSdkCommand extends Command117private tunction testbasiceruduperations (soclaLaccount esoclaLaccount): voia132||133134138-rawubloken = ssoclaLaccount->cecrrovlderuserlokenorSthis->info( string: "DB token (Length:Sthis->info( string: "DB token preview: ". substr(SrawDbToken, offset: 0,lengthcheck token expirySexpires = SsocialAccount->expires:sthas->inrol string:"oken exoires at."Sexoires? carbon::createrromimesSthis->infod"Token is expired:(Sexnires ss Sexpires ‹ timeo) ? 'v// Test with the DB token directlySthis->infod strinaTesting wilth D8 token ---").Sconfio = new Confiquration0:Scontia->se+AcceccToken(SrawlhToken):/ Test 2a: Get DealsReject// Test 2a: Get Deals Summarysunls->restberuealsscontlo// Test 2b: Get PersonsSthis->testGetPersons(Sconfiq)/I Test 2c: Get Activitiessthis->testbetacrivitlescont10)} catch (Exception Se) {sthis->recordResult testName:'Basic CRUD Operations".success: false. Se->aet100% Sz• Mon 18 May 9:43:46AskJiminnyReportActivityServiceTest v+0 ..continue and test alllin the commandlDebugging Pipedriv0 / 4 tasks do• Update POC to test official SDK methods properlyTest vz APl support if avallable6 Test error handlina properis• TestPipedriveOfficialSdkCommand.phpError while editino lestripedriveurticlalsokcommano.onp• TestPnand.oho #L3/9-408• TestPipedriveOfficialSdkCommand.php• Testcommand docker• docker exec dockerlamn 1 nhn artisanminnv.test-ninedrive-official-cdk 10+6 -6+20 -14+16 -6152181198216Run *d Skioprivate function testCurrentSystemApproach(SocialAccount SsocialAccount): voidf...?private function testGetDeals(Configuration Sconfig): void{...}private function testGetPersons(Configuration $config): void{...}private function testGetActivities(Configuration Sconfig): void{...}1 usageprivate function testFieldMetadata(SocialAccount SsocialAccount): void{...}privata funation tgst' Accept File &- Cogfiauaatiion, Sconfig) or voigi → . }Ask anvthina (&4b÷ « CodeSWF.16* Reiect allAccent alliWN Windsurf Teams127-1UTE.8io 4 spaces...
|
NULL
|
8063380989875096772
|
NULL
|
visual_change
|
ocr
|
NULL
|
PhostormVIewINavicarecodeLaravelKeractorFV faVsco. PhostormVIewINavicarecodeLaravelKeractorFV faVsco.js?° pipedrive-sdk-pocProiectC ActivityController.ong© SyncProfileMetadata.phpCConvertLeadActivities.ong© SyncPlanhat.php©syncleammeradata.ong© TestPipedriveOfficialSdkCommand.phUpoateopponunityspecrications.onocreatePlaybookcreatedevent.ong>@ Dealinsights>@Dev> 0 Dialersc) IntearationApp/Service.php© LeadConverted.php© CreateSelfCoachedEvent.phpD DTOSC ElasticsearchC) CreateCommentedEvent.phoC) CreateSmsSentEvent.ohpC) PlanhatActivityListener.pho(C)AskAnvthinaPromotService.ohd› EnqagementStatsш GeckoExport(C)AutomatedReportsCommand.ohvphp api y2.ohoC) RequestGenerateReport.Job.oho/>D Livestream(C) AutomatedReportkesulconp© AutomatedReport.phpA console [STAGING]Mallboxes• MidratePlavbackithemes• M Plavbooksdectarelstrict_types-ty,Playlists> M Postmarknanespace Jininny console comnanos crm> M PronhetAuse…..v M Renorto© AutomatedReportsCommand.phpC) AutomatedRenortsRetentionPolicvCor14 Dclass syncubnects extends command© AutomatedReportsSendCommand.pht© CreateMockAskJiminnyReportResultC© DeleteReportCommand.php© GenerateMarketingReport.php© Team.php© Usage.php>C Slack* The name and sianature of the console commandi* ovar strind›_ Teams>C TracksC Transcription>C TwilioC Users> M Vocabularv7301nrotected ssionature = crm:sunc-ohnects -team?, ~--svnctr** The console command descrintion.>Zoom*Avan strinaC) Command.oho© CreateDatabaseUsers.ohpprotected $description = 'Sync remote CRM objects.';C)DatabaseTiableCount.oho© DeleteOIdAiCrmNotesCommand.ohv* Eyecuto the concole commandlCDeleteS?Leftoverscommand.ohn@ DevPostmanCommand.phppublic function handle: voidf...;C) DiarizeViaAiParticinantldentificationCom@ EncrvntTokensCommand.nhn(C EnaaaementStatsReaenerateCommand.n( FeatureFlaasHelner nhr69 6t)protected function getStaggerDelavSeconds@: floatf...© FixCrossTenantlssues.php© FlushRolesPermissionsCache.php© GeneratelnternalWebhookToken.php9 GrounSetDefaultLanguageCommand.php74 6tprotected function getMaxDelavSeconds@: int{...}81 6t>protected function getlogPrefix@: stringf...;© HelperTruncateCoachingTables.phpHelner Code will hoin INF to underctand vour Laravel ann code II Generate II Don't Show Anvmore (8 minutes aaol=custom.log=laravel.log4 SF [jiminny@localhost]« HS_local [jiminny@localhost]A console [PROD]© TestPipedriveOfficialSdkCommand.php x © Kernel.phpA console [EU]class TestPipedrive0fficialSdkCommand extends Command117private tunction testbasiceruduperations (soclaLaccount esoclaLaccount): voia132||133134138-rawubloken = ssoclaLaccount->cecrrovlderuserlokenorSthis->info( string: "DB token (Length:Sthis->info( string: "DB token preview: ". substr(SrawDbToken, offset: 0,lengthcheck token expirySexpires = SsocialAccount->expires:sthas->inrol string:"oken exoires at."Sexoires? carbon::createrromimesSthis->infod"Token is expired:(Sexnires ss Sexpires ‹ timeo) ? 'v// Test with the DB token directlySthis->infod strinaTesting wilth D8 token ---").Sconfio = new Confiquration0:Scontia->se+AcceccToken(SrawlhToken):/ Test 2a: Get DealsReject// Test 2a: Get Deals Summarysunls->restberuealsscontlo// Test 2b: Get PersonsSthis->testGetPersons(Sconfiq)/I Test 2c: Get Activitiessthis->testbetacrivitlescont10)} catch (Exception Se) {sthis->recordResult testName:'Basic CRUD Operations".success: false. Se->aet100% Sz• Mon 18 May 9:43:46AskJiminnyReportActivityServiceTest v+0 ..continue and test alllin the commandlDebugging Pipedriv0 / 4 tasks do• Update POC to test official SDK methods properlyTest vz APl support if avallable6 Test error handlina properis• TestPipedriveOfficialSdkCommand.phpError while editino lestripedriveurticlalsokcommano.onp• TestPnand.oho #L3/9-408• TestPipedriveOfficialSdkCommand.php• Testcommand docker• docker exec dockerlamn 1 nhn artisanminnv.test-ninedrive-official-cdk 10+6 -6+20 -14+16 -6152181198216Run *d Skioprivate function testCurrentSystemApproach(SocialAccount SsocialAccount): voidf...?private function testGetDeals(Configuration Sconfig): void{...}private function testGetPersons(Configuration $config): void{...}private function testGetActivities(Configuration Sconfig): void{...}1 usageprivate function testFieldMetadata(SocialAccount SsocialAccount): void{...}privata funation tgst' Accept File &- Cogfiauaatiion, Sconfig) or voigi → . }Ask anvthina (&4b÷ « CodeSWF.16* Reiect allAccent alliWN Windsurf Teams127-1UTE.8io 4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
49423
|
1764
|
6
|
2026-05-18T06:43:48.099119+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086628099_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
8043719072324535154
|
-8628527368849355612
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
iTerm2ShelllEditViewSessi Project: faVsco.js, menu
iTerm2ShelllEditViewSessionScriptsProfilesWindowHelp•• 0APP (-zsh)DOCKERLast login: Mon May 18 09:17:28 on ttys007O 81DEV (-zsh)O 82Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentsPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (pipedrive-sdk-poc) $ U‹ $0APP (-zsh)|83100% C47 8• Mon 18 May 9:43:47T₴1screenpipe*O &4APP...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
49424
|
1765
|
7
|
2026-05-18T06:43:49.587048+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086629587_m2.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhostormVIewINavicarecodeLaravelKeractorFV faVsco. PhostormVIewINavicarecodeLaravelKeractorFV faVsco.js?° pipedrive-sdk-pocProiectC ActivityController.ong© SyncProfileMetadata.phpCConvertLeadActivities.ong© SyncPlanhat.php©syncleammetadata.ong© TestPipedriveOfficialSdkCommand.phUpoateopponunityspecrications.onocreatePlaybookcreatedevent.ong>@ Dealinsights>@Dev> 0 Dialersc) IntearationApp/Service.php© LeadConverted.php© CreateSelfCoachedEvent.phpD DTOSC ElasticsearchC) CreateCommentedEvent.phoC) CreateSmsSentEvent.ohpC) PlanhatActivityListener.pho(C)AskAnvthinaPromotService.ohd› EnqagementStatsш GeckoExport(C)AutomatedReportsCommand.ohvphp api y2.ohoC) RequestGenerateReport.Job.oho/>D Livestream(C) AutomatedReportkesulconp© AutomatedReport.phpA console [STAGING]Mallboxes• MidratePlavbackithemes• M Plavbooksdectarelstrict_types-ty,Playlists> M Postmarknanespace Jininny console comnanos crm> M PronhetAuse…..v M Renorto© AutomatedReportsCommand.phpC) AutomatedRenortsRetentionPolicvCor14 Dclass syncubnects extends command© AutomatedReportsSendCommand.pht© CreateMockAskJiminnyReportResultC© DeleteReportCommand.php© GenerateMarketingReport.php© Team.php© Usage.php>CJ Slack* The name and sianature of the console commandi* ovar strind›_ Teams>C TracksC Transcription>C TwilioC Users> M Vocabularvorotected ssionature = crm:sunc-ohnects «team?, <--svncs'** The console command descrintion.>Zoom*Avan strinaC) Command.oho© CreateDatabaseUsers.ohpprotected $description = 'Sync remote CRM objects.';C)DatabaseTiableCount.oho© DeleteOIdAiCrmNotesCommand.ohv* Eyecuto the concole commandlCDeleteS?Leftoverscommand.ohn@ DevPostmanCommand.phppublic function handle: voidf...;C) DiarizeViaAiParticinantldentificationCom@ EncrvntTokensCommand.nhn(C EnaaaementStatsReaenerateCommand.n( FeatureFlaasHelner nhr69 6t)protected function getStaggerDelavSeconds@: floatf...?© FixCrossTenantlssues.php© FlushRolesPermissionsCache.php© GeneratelnternalWebhookToken.php9 GrounSetDefaultLanguageCommand.php74 6tprotected function getMaxDelavSeconds@: int{...}81 6t>protected function getlogPrefix@: stringf...;© HelperTruncateCoachingTables.phpHelner Code will hoin INF to underctand vour Laravel ann code II Generate II Don't Show Anvmore (8 minutes aaolV 132.134138=custom.log=laravel.log4 SF [jiminny@localhost]« HS_local [jiminny@localhost]A console [PROD]© TestPipedriveOfficialSdkCommand.php x © Kernel.phpA console [EU]class TestPipedrive0fficialSdkCommand extends Command117privace tunccion cestbastcuruauperaclons soclaLaccount ssoclaLAccounc. vola-rawubloken = ssoclaLaccount->cecrrovlderuserlokenoSthis->info( string: "DB token (Length:Sthis->info( string: "DB token preview: ". substr(SrawDbToken, offset: 0,lengthcheck token expirySexpires = SsocialAccount->expires:sthas->inrol string:"oken exoires at.Sexoires? carbon::createrromiimesSthis->infod"Token is expired:(Sexnires ss Sexpires ‹ timeo) ? 'v// Test with the DB token directlySthis->infod strinaTesting with DB token ---").Sconfio = new Confiquration0:Scontia->se+AcceccToken(SrawlhToken):/ Test 2a: Get DealsReject// Test 2a: Get Deals Summarysunls->restberuealsscontlo// Test 2b: Get PersonsSthis->testGetPersons(Sconfiq)/I Test 2c: Get Activitiessthis->testbetacrivitlesscontz0)} catch (Exception Se) {sthis->recordResult testName:'Basic CRUD Operations".success: false. Se->aet100% Sz• Mon 18 May 9:43:49AskJiminnyReportActivityServiceTest v+0 ..continue and test alllin the commandlwDebugging PipedrivThought for 1s0 / 4 tasks done• Update POC to test official SDK methods properlyTest vz APl support if avallable3. Test error handling properiy• TestPipedriveOfficialSdkCommand.phpcrror while ediuing lestripeoriveurticlalsakcommano.onpThoughtsRead TestPipedriveOfficialSdkCommand.oho #L175-224• TestPipedriveOfficialSdkCommand.php• TestPipedRead lestripecialsdkCommand.oho #L3/9-408•TestPipedriveOfficialSdkCommand.php• TestPipedriveOfficialSdkCommand.phpcommand docker• docker exec dockerlamn 1 nhn artisanminnv.test-ninedrive-official-cdk 10+6 -6+20 -14+5 -51+16 -6152181198216private function testCurrentSystemApproach(SocialAccount $socialAccount): voidt...}private function testGetDeals(Configuration Sconfig): void{...}private function testGetPersons(Configuration Sconfig): void{...}private function testGetActivities(Configuration Sconfig): void{...}1 usageprivate function testFieldMetadata(SocialAccount SsocialAccount): void{...}privata funation tgst' Accept File &- Cogfiauaatiion, Sconfig) or voigi → . }Ask anvthina (&4b÷ « CodeSWF.16* Reiect allAccent alliWN Windsurf Toams 127-1UTF.8io 4 spaces...
|
NULL
|
-8851624223078114382
|
NULL
|
visual_change
|
ocr
|
NULL
|
PhostormVIewINavicarecodeLaravelKeractorFV faVsco. PhostormVIewINavicarecodeLaravelKeractorFV faVsco.js?° pipedrive-sdk-pocProiectC ActivityController.ong© SyncProfileMetadata.phpCConvertLeadActivities.ong© SyncPlanhat.php©syncleammetadata.ong© TestPipedriveOfficialSdkCommand.phUpoateopponunityspecrications.onocreatePlaybookcreatedevent.ong>@ Dealinsights>@Dev> 0 Dialersc) IntearationApp/Service.php© LeadConverted.php© CreateSelfCoachedEvent.phpD DTOSC ElasticsearchC) CreateCommentedEvent.phoC) CreateSmsSentEvent.ohpC) PlanhatActivityListener.pho(C)AskAnvthinaPromotService.ohd› EnqagementStatsш GeckoExport(C)AutomatedReportsCommand.ohvphp api y2.ohoC) RequestGenerateReport.Job.oho/>D Livestream(C) AutomatedReportkesulconp© AutomatedReport.phpA console [STAGING]Mallboxes• MidratePlavbackithemes• M Plavbooksdectarelstrict_types-ty,Playlists> M Postmarknanespace Jininny console comnanos crm> M PronhetAuse…..v M Renorto© AutomatedReportsCommand.phpC) AutomatedRenortsRetentionPolicvCor14 Dclass syncubnects extends command© AutomatedReportsSendCommand.pht© CreateMockAskJiminnyReportResultC© DeleteReportCommand.php© GenerateMarketingReport.php© Team.php© Usage.php>CJ Slack* The name and sianature of the console commandi* ovar strind›_ Teams>C TracksC Transcription>C TwilioC Users> M Vocabularvorotected ssionature = crm:sunc-ohnects «team?, <--svncs'** The console command descrintion.>Zoom*Avan strinaC) Command.oho© CreateDatabaseUsers.ohpprotected $description = 'Sync remote CRM objects.';C)DatabaseTiableCount.oho© DeleteOIdAiCrmNotesCommand.ohv* Eyecuto the concole commandlCDeleteS?Leftoverscommand.ohn@ DevPostmanCommand.phppublic function handle: voidf...;C) DiarizeViaAiParticinantldentificationCom@ EncrvntTokensCommand.nhn(C EnaaaementStatsReaenerateCommand.n( FeatureFlaasHelner nhr69 6t)protected function getStaggerDelavSeconds@: floatf...?© FixCrossTenantlssues.php© FlushRolesPermissionsCache.php© GeneratelnternalWebhookToken.php9 GrounSetDefaultLanguageCommand.php74 6tprotected function getMaxDelavSeconds@: int{...}81 6t>protected function getlogPrefix@: stringf...;© HelperTruncateCoachingTables.phpHelner Code will hoin INF to underctand vour Laravel ann code II Generate II Don't Show Anvmore (8 minutes aaolV 132.134138=custom.log=laravel.log4 SF [jiminny@localhost]« HS_local [jiminny@localhost]A console [PROD]© TestPipedriveOfficialSdkCommand.php x © Kernel.phpA console [EU]class TestPipedrive0fficialSdkCommand extends Command117privace tunccion cestbastcuruauperaclons soclaLaccount ssoclaLAccounc. vola-rawubloken = ssoclaLaccount->cecrrovlderuserlokenoSthis->info( string: "DB token (Length:Sthis->info( string: "DB token preview: ". substr(SrawDbToken, offset: 0,lengthcheck token expirySexpires = SsocialAccount->expires:sthas->inrol string:"oken exoires at.Sexoires? carbon::createrromiimesSthis->infod"Token is expired:(Sexnires ss Sexpires ‹ timeo) ? 'v// Test with the DB token directlySthis->infod strinaTesting with DB token ---").Sconfio = new Confiquration0:Scontia->se+AcceccToken(SrawlhToken):/ Test 2a: Get DealsReject// Test 2a: Get Deals Summarysunls->restberuealsscontlo// Test 2b: Get PersonsSthis->testGetPersons(Sconfiq)/I Test 2c: Get Activitiessthis->testbetacrivitlesscontz0)} catch (Exception Se) {sthis->recordResult testName:'Basic CRUD Operations".success: false. Se->aet100% Sz• Mon 18 May 9:43:49AskJiminnyReportActivityServiceTest v+0 ..continue and test alllin the commandlwDebugging PipedrivThought for 1s0 / 4 tasks done• Update POC to test official SDK methods properlyTest vz APl support if avallable3. Test error handling properiy• TestPipedriveOfficialSdkCommand.phpcrror while ediuing lestripeoriveurticlalsakcommano.onpThoughtsRead TestPipedriveOfficialSdkCommand.oho #L175-224• TestPipedriveOfficialSdkCommand.php• TestPipedRead lestripecialsdkCommand.oho #L3/9-408•TestPipedriveOfficialSdkCommand.php• TestPipedriveOfficialSdkCommand.phpcommand docker• docker exec dockerlamn 1 nhn artisanminnv.test-ninedrive-official-cdk 10+6 -6+20 -14+5 -51+16 -6152181198216private function testCurrentSystemApproach(SocialAccount $socialAccount): voidt...}private function testGetDeals(Configuration Sconfig): void{...}private function testGetPersons(Configuration Sconfig): void{...}private function testGetActivities(Configuration Sconfig): void{...}1 usageprivate function testFieldMetadata(SocialAccount SsocialAccount): void{...}privata funation tgst' Accept File &- Cogfiauaatiion, Sconfig) or voigi → . }Ask anvthina (&4b÷ « CodeSWF.16* Reiect allAccent alliWN Windsurf Toams 127-1UTF.8io 4 spaces...
|
49422
|
NULL
|
NULL
|
NULL
|
|
49425
|
1765
|
8
|
2026-05-18T06:43:52.586747+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086632586_m2.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"pipedrive-sdk-poc, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.05618351,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: pipedrive-sdk-poc","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.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-8603436982613086966
|
-8708828999581586494
|
visual_change
|
hybrid
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
PhostormVIewINavicarecodeLaravelKeractorFV faVsco.js?° pipedrive-sdk-pocProiectC ActivityController.ong© SyncProfileMetadata.phpCConvertLeadActivities.ong© SyncPlanhat.php©syncleammetadata.ong© TestPipedriveOfficialSdkCommand.phUpoateopponunityspecrications.onocreatePlaybookcreatedevent.ong>@ Dealinsights>@Dev> 0 Dialersc) IntearationApp/Service.php© LeadConverted.php© CreateSelfCoachedEvent.phpD DTOSC ElasticsearchC) CreateCommentedEvent.phoC) CreateSmsSentEvent.ohpC) PlanhatActivityListener.pho(C)AskAnvthinaPromotService.ohd› EnqagementStatsш GeckoExport(C)AutomatedReportsCommand.ohvphp api y2.ohoC) RequestGenerateReport.Job.oho/>D Livestream(C) AutomatedReportkesulconp© AutomatedReport.phpA console [STAGING]Mallboxes• MidratePlavbackithemes• M Plavbooksdectarelstrict_types-tyD Playlists> M Postmarknanespace Jininny console comnanos crm> M PronhetAuse…..v M Renorto© AutomatedReportsCommand.phpC) AutomatedRenortsRetentionPolicvCor14 Dclass syncubnects extends command© AutomatedReportsSendCommand.pht© CreateMockAskJiminnyReportResultC© DeleteReportCommand.php© GenerateMarketingReport.php© Team.php© Usage.php>C Slack* The name and sianature of the console commandi* ovar strind›_ Teams>C TracksC Transcription>C TwilioC Users> M Vocabularv7301nrotected ssionature = crm:sunc-ohnects -team?, ~--svnctr** The console command descrintion.>Zoom*Avan strinaC) Command.oho© CreateDatabaseUsers.phpprotected $description = 'Sync remote CRM objects.';C)DatabaseTiableCount.oho© DeleteOIdAiCrmNotesCommand.ohv* Eyecuto the concole commandlCDeleteS?Leftoverscommand.ohn@ DevPostmanCommand.phppublic function handle: voidf...;C) DiarizeViaAiParticinantldentificationCom@ EncrvntTokensCommand.nhn(C EnaadementStatsRedenerateCommand.r( FeatureFlaasHelner nhr69 6t)protected function getStaggerDelavSeconds@: floatf...?© FixCrossTenantlssues.php© FlushRolesPermissionsCache.php© GeneratelnternalWebhookToken.php9 GrounSetDefaultLanguageCommand.php74 6tprotected function getMaxDelavSeconds@: int{...}81 6t>protected function getLoqPrefix@: stringf...;l© HelperTruncateCoachingTables.phpHelner Code will hoin INF to underctand vour Laravel ann code II Generate II Don't Show Anvmore (8 minutes aaol=custom.log=laravel.log4 SF [jiminny@localhost]« HS_local [jiminny@localhost]A console [PROD]© TestPipedriveOfficialSdkCommand.php x © Kernel.phpA console [EU]class TestPipedrive0fficialSdkCommand extends Command117private tunction testbasiceruduperations (soclaLaccount esoclaLaccount): voia129V 132.134138140141142152181198-rawubloken = ssoclaLaccount->cecrrovlderuserlokenorSthis->info( string: "DB token (Length:Sthis->info( string: "DB token preview: ". substr(SrawDbToken, offset: 0,lengthcheck token expirySexpires = SsocialAccount->expires:sthas->inrol string:"oken exoires at:Sexoires? carbon::createrromiimesSthis->infod"Token is expired:• (Sexpines &s Sexpires ‹ timed) ? '// Test with the DB token directlySthis->infod strinaTesting wilth D8 token ---").Sconfio = new Confiquration0:Scontia->se+AcceccToken(SrawlhToken):/ Test 2a: Get DealsReject// Test 2a: Get Deals Summarysunls->restberuealsscontlo// Test 2b: Get PersonsSthis->testGetPersons(Sconfiq)II Test 2c:sthis->testbetacrivitlesscontz0)} catch (Exception Se) {sthis->recordResult testName:'Basic CRUD Operations".success: false. Se->aet100% Sz• Mon 18 May 9:43:52AskJiminnyReportActivityServiceTest v+0 ..continue and test all in the command.w• Debugging PipedriveThought for 1s0 4 tasks done1 Update Poc to test ofticial SDK methods properlyTest vz APl support it avallable3 Test error handlina properiv• TestPipedriveOfficialSdkCommand.phpThouahtserror while ediung lestripedriveurticlalsakcommano.onp ›Thoughts ›Read TestPipedriveOfficialSdkCommand.pho #L175-224lhoughts• TestPipedriveOfficialSdkCommand.phpThoughts >•TestPipedriveOfficialSdkCommand.phpRead TestPipedriveOfficialSdkCommand.php #L330-379Thouahts>Read [EMAIL] #L3/9-408lallteinie•TestPipedriveOfficialSdkCommand.phpThoughtsTestPipedriveOfficialSdkCommand.phpThoughtscommand docker• docker exec dockerlamn 1nhn artisanminnv.test-ninedrive-official-cdk 10+6 -6+20 -14+5 -5+16 -6private function testCurrentSystemApproach(SocialAccount SsocialAccount): void{...}private function testGetDeals(Configuration Sconfig): void{...}private function testGetPersons(Configuration $config): void{...}private function testGetActivities(Configuration Sconfig): void{...}1 usageprivate function testFieldMetadata(SocialAccount SsocialAccount): void{...}privata funation tgst' Accept File &- Cogfiauaatiion, Sconfig) or voigi → . }Ask anvthina (&4b÷ « CodeSWF.16* Reiect allAccent alliWN Windsurf Toams 127-1UTF.8Po 4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
49426
|
1764
|
7
|
2026-05-18T06:44:10.133614+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086650133_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"pipedrive-sdk-poc, menu","depth":5,"on_screen":true,"help_text":"Git Branch: pipedrive-sdk-poc","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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"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},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
7558552253815835299
|
-8132289082607555642
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
iTerm2ShelllEditViewSessionScriptsProfilesWindowHelp•• 0APP (-zsh)DOCKERLast login: Mon May 18 09:17:28 on ttys007O 81DEV (-zsh)O 82Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentsPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (pipedrive-sdk-poc) $ U‹ $0APP (-zsh)|83100% C47 8• Mon 18 May 9:44:09T₴1screenpipe*O &4APP...
|
49423
|
NULL
|
NULL
|
NULL
|
|
49427
|
1765
|
9
|
2026-05-18T06:44:10.144365+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086650144_m2.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"pipedrive-sdk-poc, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.05618351,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: pipedrive-sdk-poc","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.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
7558552253815835299
|
-8132289082607555642
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
PhostormVIewINavicarecodeLaravelKeractorFV faVsco.js?° pipedrive-sdk-pocProiectC ActivityController.ong© SyncProfileMetadata.phpCConvertLeadActivities.ong© SyncPlanhat.php©syncleammetadata.ong© TestPipedriveOfficialSdkCommand.phUpoateopponunityspecrications.onocreatePlaybookcreatedevent.ong>@ Dealinsights>@Dev> 0 Dialersc) IntearationApp/Service.php© LeadConverted.php© CreateSelfCoachedEvent.phpD DTOSC ElasticsearchC) CreateCommentedEvent.phoC) CreateSmsSentEvent.ohpC) PlanhatActivityListener.pho(C)AskAnvthinaPromotService.ohd› EnqagementStatsш GeckoExport(C)AutomatedReportsCommand.ohvphp api y2.ohoC) RequestGenerateReport.Job.oho/>D Livestream(C) AutomatedReportkesulconp© AutomatedReport.phpA console [STAGING]Mallboxes• MidratePlavbackithemes• M Plavbooksdectarelstrict_types-ty,Playlists> M Postmarknanespace Jininny console comnanos crm> M PronhetAuse…..v M Renorto© AutomatedReportsCommand.phpC) AutomatedRenortsRetentionPolicvCor14 Dclass syncubnects extends command© AutomatedReportsSendCommand.pht© CreateMockAskJiminnyReportResultC© DeleteReportCommand.php© GenerateMarketingReport.php© Team.php© Usage.php>C Slack* The name and sianature of the console commandi* ovar strind›_ Teams>C TracksC Transcription>C TwilioC Users> M Vocabularv7301nrotected ssionature = crm:sunc-ohnects -team?, ~--svnctr** The console command descrintion.>Zoom*Avan strinaC) Command.oho© CreateDatabaseUsers.phpprotected $description = 'Sync remote CRM objects.';C)DatabaseTiableCount.oho© DeleteOIdAiCrmNotesCommand.ohv* Eyecuto the concole commandlCDeleteS?Leftoverscommand.ohn@ DevPostmanCommand.phppublic function handle: voidf...;C) DiarizeViaAiParticinantldentificationCom@ EncrvntTokensCommand.nhn(C EnaaaementStatsReaenerateCommand.n( FeatureFlaasHelner nhr69 6t)protected function getStaggerDelavSeconds@: floatf...© FixCrossTenantlssues.php© FlushRolesPermissionsCache.php© GeneratelnternalWebhookToken.php9 GrounSetDefaultLanguageCommand.php74 6tprotected function getMaxDelavSeconds@: int{...}81 6t>protected function getlogPrefix@: stringf...;© HelperTruncateCoachingTables.phpHelner Code will hoin INF to underctand vour Laravel ann code II Generate II Don't Show Anvmore (Q minutes aaol=custom.loglaravel.log4 SF [jiminny@localhost]« HS_local [jiminny@localhost]A console [PROD]© TestPipedriveOfficialSdkCommand.php x © Kernel.phpA console [EU]class TestPipedrive0fficialSdkCommand extends Commandprivate tunction testbasiceruduperations (soclaLAccount ssoclaLAccount vola134138-rawubloken = ssoclaLaccount->cecrrovlderuserlokenorSthis->info( string: "DB token (Length:Sthis->info( string: "DB token preview: ". substr(SrawDbToken, offset: 0,lengthcheck token expirySexpires = SsocialAccount->expires:sthas->inrol string:"oken exoires at.Sexoires? carbon::createrromimesSthis->infod"Token is expired:(Sexnires ss Sexpires < timed) > 'v// Test with the DB token directlySthis->infod strinaTesting with DB token ---").Sconfio = new Confiquration0:Scontia->se+AcceccToken(SrawlhToken):/ Test 2a: Get DealsReject// Test 2a: Get Deals Summarysunls->restberuealsscontlo// Test 2b: Get PersonsSthis->testGetPersons(Sconfiq)II Test 2c:sthis->testbetacrivitlescont10)} catch (Exception Se) {sthis->recordResult testName:'Basic CRUD Operations".success: false. Se->aet100% Sz• Mon 18 May 9:44:09AskJiminnyReportActivityServiceTest v+0 ..continue and test all in the command.w152181198216Debugging PipedrivThought for 1s0 4 tasks done1 Update Poc to test ofticial SDK methods properlyTest vz APl support it avallable3 Test error handling properlv• TestPipedriveOfficialSdkCommand.phpThouahtscrror while ediuno lescripedriveurticlalsokcommano.onpThoughts ›Read TestPipedriveOfficialSdkCommand.pho #L175-224lhoughts• TestPipedriveOfficialSdkCommand.phpThoughts >•TestPipedriveOfficialSdkCommand.ohpDoad ToctDinodrivoOfficia|CdkCommand nhn #ll 220.270Thouahts>Read [EMAIL] #L3/9-408lallteinie•TestPipedriveOfficialSdkCommand.phpThoughtsTestPipedriveOfficialSdkCommand.phpThoughtscommand docker• docker exec dockerlamn 1 nhn artisanminnv.test-ninedrive-official-cdk 10Starting Pipedrive Official SDK POC for Team ID: 19Found Pipedrive account for team: Pipedrive. Inc.Test 1: OAuth Initialization on Client initialized successfullv in 148,02msTest 2: Basic CRUD Operations+6 -6+20 -14+5-5+16 -6private function testCurrentSystemApproach(SocialAccount SsocialAccount): voidf..?Tusageprivate function testGetDeals(Configuration Sconfig): void{...}private function testGetPersons(Configuration $config): void{...}private function testGetActivities(Configuration Sconfig): void{...}1 usageprivate function testFieldMetadata(SocialAccount SsocialAccount): void{...}privata funation tgst' Accept File &- Cogfiauaatiion, Sconfig) or voigi → . }Ask anvthina (&4b÷ « CodeSWF.16* Reiect allAccent alli•.0WN Windsurf Teamo127-1UTE.8io 4 spaces...
|
49425
|
NULL
|
NULL
|
NULL
|
|
49428
|
1765
|
10
|
2026-05-18T06:44:13.080496+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086653080_m2.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"pipedrive-sdk-poc, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.05618351,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: pipedrive-sdk-poc","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3036442092684777152
|
-5740307667640416884
|
visual_change
|
hybrid
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
P Project: faVsco.js, menu
pipedrive-sdk-poc, menu
PhostormVIewINavicarecodeLaravelKeractorFV faVsco.js?° pipedrive-sdk-pocProiect vC ActivityController.ong© SyncProfileMetadata.phpCConvertLeadActivities.ong© SyncPlanhat.php©syncleammeradata.ong© TestPipedriveOfficialSdkCommand.phUpoateopponunityspecrications.onocreatePlaybookcreatedevent.ong>@ Dealinsights>@Dev> 0 Dialersc) IntearationApp/Service.php© LeadConverted.php© CreateSelfCoachedEvent.phpD DTOSC ElasticsearchC) CreateCommentedEvent.phoC) CreateSmsSentEvent.ohp© PlanhatActivityListener.php(C)AskAnvthinaPromotService.ohd› EnqagementStatsш GeckoExport(C)AutomatedReportsCommand.ohvphp api y2.ohoC) RequestGenerateReport.Job.oho/>D Livestream(C) AutomatedReportkesulconp© AutomatedReport.phpA console [STAGING]Mallboxes• MidratePlavbackithemes• M Plavbooksdectarelstrict_types-tyPlaylists> M Postmarknanespace Jininny console comnanos crm> M PronhetAuse…..v M Renorto© AutomatedReportsCommand.phpC) AutomatedRenortsRetentionPolicvCor14 Dclass syncubnects extends command© AutomatedReportsSendCommand.pht© CreateMockAskJiminnyReportResultC© DeleteReportCommand.php© GenerateMarketingReport.php© Team.php© Usage.php>C Slack* The name and sianature of the console commandi* ovar strind›_ Teams>C TracksC Transcription>C TwilioC Users> M Vocabularv7301nrotected ssionature = crm:sunc-ohnects -team?, ~--svnctr** The console command descrintion.>Zoom*Avan strinaC) Command.oho© CreateDatabaseUsers.ohpprotected $description = 'Sync remote CRM objects.';C)DatabaseTiableCount.oho© DeleteOIdAiCrmNotesCommand.ohv* Eyecuto the concole commandlCDeleteS?Leftoverscommand.ohn@ DevPostmanCommand.phppublic function handle: voidf...;C) DiarizeViaAiParticinantldentificationCom@ EncrvntTokensCommand.nhn(C EnaaaementStatsReaenerateCommand.n( FeatureFlaasHelner nhr69 6t)protected function getStaggerDelavSeconds@: floatf...?© FixCrossTenantlssues.php© FlushRolesPermissionsCache.php© GeneratelnternalWebhookToken.php9 GrounSetDefaultLanguageCommand.php© HelperTruncateCoachingTables.php74 6tprotected function getMaxDelavSeconds@: int{...}81 6t>protected function getlogPrefix@: stringf...;Helner Code will hoin INF to underctand vour Laravel ann code II Generate II Don't Show Anvmore (Q minutes aaolAskJiminnyReportActivityServiceTest v=custom.log=laravel.log4 SF [jiminny@localhost]« HS_local [jiminny@localhost]A console [PROD]© TestPipedriveOfficialSdkCommand.php x © Kernel.phpA console [EU]class TestPipedrive0fficialSdkCommand extends Commandprivate tunction testbasiceruduperations (soclaLAccount ssoclaLAccount vola132-rawubloken = ssoclaLaccount->cecrrovlderuserlokenorSthis->info( string: "DB token (Length:")");Sthis->info( string: "DB token preview: " . substr(SrawDbToken, offset: 0. lengthcheck token expirySexpires = SsocialAccount->expires:sthas->inrol string:"oken exoires at."Sexoires? carbon::createrromiimesSthis->infod"Token is expired:Sexoires . Sexpires < timeo ?// Test with the DB token directlySthis->infod strinaTesting wilth D8 token ---").Sconfio = new ConfiaurationOrScontia->se+AcceccToken(SrawlhToken):/ Test 2a: Get DealsReject// Test 2a: Get Deals SummarySthis->testGetDeals(Sconfig):// Test 2b: Get PersonsSthis->testGetPersons(Sconfiq)II Test 2c:sthis->testbetacrivitlescont10)} catch (Exception Se) {sthis->recordResult testName:'Basic CRUD Operations".success: false. Se->aetCascadeDebugging PipedriveRead TestPipedriveOfficialSdkCommand.php #L330-379Read TestPipedriveOtricialsakcommand.pho #L3/9-408• TestPipedriveOfficialSdkCommand.php• TestPipedriveOfficialSdkCommand.phpdocker exec docker lamn 1nhn artisan iiminnv.tect-ninedrive-official-cok 10Starting Pipedrive Official SDK POC for Team ID: 19Found Pioedrive account for team: Pivedrive. Inc.Test 1: OAuth InitializatiorVPASS = oxuth intlau zation: cuent inttlauzed successtully in 148.02m.Test 2: Basic CRUD OperationsToken expires a: 2026-05-45 15:2828 ...Token is expired: YES{"success": false, "error" :"Invalid token:Test 2b: Get PersonsErrorCall to undefined method Pipedrivelversions\v1\Api\PersonsApi:: getPersons()(e in.a so ua e reaaedoaa ut, fconta) » 191):, carca fese on sut ey cet ertone) Mer forr ed pr ohd n (selopsolas', solupse):100% Lz. Mon 18 May 9:44:12+0 ..+5-5+16 -6wresulted in a '401 Unauthorized response152181198257private function testCurrentSystemApproach(SocialAccount SsocialAccount): voidf.Tusageprivate function testGetDeals(Configuration Sconfig): void{...}private function testGetPersons(Configuration Sconfig): void{...}private function testGetActivities(Configuration Sconfig): void{...}1 usageprivate function testFieldMetadata(SocialAccount SsocialAccount): void{...}privata funation tgst' Accept File &- Cogfiauaatiion, Sconfig) or voigi → . }Ask anvthina (&4-L)÷ « CodeSWF.16* Reiect allWN Windsurf Teamo127-1 1Accent alliio 4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
49429
|
1764
|
8
|
2026-05-18T06:44:20.503375+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086660503_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
8043719072324535154
|
-8628527368849355612
|
visual_change
|
hybrid
|
NULL
|
Project: faVsco.js, menu
iTerm2ShellEditViewSessio Project: faVsco.js, menu
iTerm2ShellEditViewSessionScriptsProfilesWindowHelp100% C47 8• Mon 18 May 9:44:20•• 0APP (-zsh)T81DOCKERLast login: Mon May 18 09:17:28 on ttys007₴81DEV (-zsh)O ₴2Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentsPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentsLukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (pipedrive-sdk-poc) $ 0APP (-zsh)83pmsetO ₴4APPNPS-Firefox...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
49430
|
1764
|
9
|
2026-05-18T06:44:23.530700+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086663530_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"pipedrive-sdk-poc, menu","depth":5,"on_screen":true,"help_text":"Git Branch: pipedrive-sdk-poc","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}]...
|
4013489942575271730
|
-7483500116281160310
|
visual_change
|
hybrid
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
iTerm2ShelllEditViewSessionScriptsProfilesWindowHelp•• 0APP (-zsh)DOCKERLast login: Mon May 18 09:17:28 on ttys007O 81DEV (-zsh)O ₴2Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentsPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (pipedrive-sdk-poc) $ U‹ $0APP (-zsh)|83100% C47 8• Mon 18 May 9:44:23T₴1screenpipe*O &4APP...
|
49429
|
NULL
|
NULL
|
NULL
|
|
49431
|
1764
|
10
|
2026-05-18T06:44:24.704247+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086664704_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
iTerm2ShelllEditViewSessionScriptsProfilesWindowHe iTerm2ShelllEditViewSessionScriptsProfilesWindowHelp•• 0APP (-zsh)DOCKERLast login: Mon May 18 09:17:28 on ttys007O 81DEV (-zsh)O ₴2Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentsPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (pipedrive-sdk-poc) $ U‹ $0APP (-zsh)|83100% C47 8• Mon 18 May 9:44:24T₴1screenpipe*O &4APP...
|
NULL
|
-2775080756249258382
|
NULL
|
click
|
ocr
|
NULL
|
iTerm2ShelllEditViewSessionScriptsProfilesWindowHe iTerm2ShelllEditViewSessionScriptsProfilesWindowHelp•• 0APP (-zsh)DOCKERLast login: Mon May 18 09:17:28 on ttys007O 81DEV (-zsh)O ₴2Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentsPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (pipedrive-sdk-poc) $ U‹ $0APP (-zsh)|83100% C47 8• Mon 18 May 9:44:24T₴1screenpipe*O &4APP...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
49433
|
1765
|
11
|
2026-05-18T06:44:24.705613+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086664705_m2.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhostormVIewINavicarecodeLaravelKeractorFV faVsco. PhostormVIewINavicarecodeLaravelKeractorFV faVsco.js?° pipedrive-sdk-pocProiectC ActivityController.ong© SyncProfileMetadata.phpCConvertLeadActivities.ong© SyncPlanhat.php©syncleammetadata.ong© TestPipedriveOfficialSdkCommand.phUpoateopponunityspecrications.onocreatePlaybookcreatedevent.ong>@ Dealinsights>@Dev> 0 Dialersc) IntearationApp/Service.php© LeadConverted.php© CreateSelfCoachedEvent.phpD DTOSC ElasticsearchC) CreateCommentedEvent.phoC) CreateSmsSentEvent.ohpC) PlanhatActivityListener.pho(C)AskAnvthinaPromotService.ohd› EnqagementStatsш GeckoExport(C)AutomatedReportsCommand.ohvphp api y2.ohoC) RequestGenerateReport.Job.oho/>D Livestream(C) AutomatedReportkesulconp© AutomatedReport.phpA console [STAGING]Mallboxes• MidratePlavbackithemes• M Plavbooksdectarelstrict_types-ty,Playlists> M Postmarknanespace Jininny console comnanos crm> M PronhetAuse…..v M Renorto© AutomatedReportsCommand.phpC) AutomatedRenortsRetentionPolicvCor14 Dclass syncubnects extends command© AutomatedReportsSendCommand.pht© CreateMockAskJiminnyReportResultC© DeleteReportCommand.php© GenerateMarketingReport.php© Team.php© Usage.php>C Slack* The name and sianature of the console commandi* ovar strind›_ Teams>C TracksC Transcription>C TwilioC Users> M Vocabularv7301nrotected ssionature = crm:sunc-ohnects -team?, ~--svnctr** The console command descrintion.>Zoom*Avan strinaC) Command.oho© CreateDatabaseUsers.ohpprotected $description = 'Sync remote CRM objects.';C)DatabaseTiableCount.oho© DeleteOIdAiCrmNotesCommand.ohv* Eyecuto the concole commandlCDeleteS?Leftoverscommand.ohn@ DevPostmanCommand.phppublic function handle: voidf...;C) DiarizeViaAiParticinantldentificationCom@ EncrvntTokensCommand.nhn(C EnaadementStatsRedenerateCommand.r( FeatureFlaasHelner nhr69 6t)protected function getStaggerDelavSeconds@: floatf...?© FixCrossTenantlssues.php© FlushRolesPermissionsCache.php© GeneratelnternalWebhookToken.php9 GrounSetDefaultLanguageCommand.php74 6tprotected function getMaxDelavSeconds@: int{...}81 6t>protected function getlogPrefix@: stringf...;© HelperTruncateCoachingTables.phpHelner Code will hoin INF to underctand vour Laravel ann code II Generate II Don't Show Anvmore (Q minutes aaol=custom.loglaravel.logA SF [jiminny@localhost]« HS_local [jiminny@localhost]A console [PROD]© TestPipedriveOfficialSdkCommand.php x © Kernel.phpA console [EU]class TestPipedrive0fficialSdkCommand extends Commandprivate tunction testbasiceruduperations (soclaLAccount ssoclaLAccount vola132134138-rawubloken = ssoclaLaccount->cecrrovlderuserlokenorSthis->info( string: "DB token (Length:"")"Sthis->info( string: "DB token preview: ". substr(SrawDbToken, offset: 0,check token expirySexpires = SsocialAccount->expires:sthas->inrol string:"oken exoires at."Sexoires? carbon::createrromiimesSthis->infod"Token is expired:(Sexnires ss Sexpires ‹ timeo) ? 'v// Test with the DB token directlySthis->infod strinaTesting with DB token ---").Sconfio = new Confiquration0:Scontia->se+AcceccToken(SrawlhToken):/ Test 2a: Get DealsReject// Test 2a: Get Deals Summarysunls->restberuealsscontlo// Test 2b: Get PersonsSthis->testGetPersons(Sconfiq)II Test 2c:sthis->testbetacrivitlesscontz0)catch (Excention Se) <sthis->recordResult testName:'Basic CRUD Operations".success: false. Se->aet152181198private function testCurrentSystemApproach(SocialAccount SsocialAccount): voidf...?Tusageprivate function testGetDeals(Configuration Sconfig): void{...}private function testGetPersons(Configuration Sconfig): void{...}private function testGetActivities(Configuration Sconfiq): void{...}1 usageprivate function testFieldMetadata(SocialAccount SsocialAccount): void{...}privata funation tgst' Accept File &- Cogfiauaatiion, Sconfig) or voigi → . }AskJiminnyReportActivityServiceTest vCascade• Debugging Pipedriveneolnis•TestPipedriveOfficialSdkCommand.php• TestPipedriveOfficialSdkCommand.phpdocker exec docker lamo 1 oho artisan liminnv.test-oinedrive-official-sdk 19Starting Pipedrive Official SDK POC for Team ID: 19Found Pipedrive account for team: Pipedrive, Inc.Test PAS - dauth intzatization: Cient initialized successfully in 148.02msrestoken asec th: operationsToken ex expired: 2E56-05-15 15:2N:2yu...Tstine witbealssmaresulted in a '401 Unauthorized"WC FAIL - Get Deals Summary: 1401l Client error: GET https://ap2.pipedrive.com/vz/deals/summapy resulted in a 401 Unauthorized reTest 2b: Get PersonsErrorCall to undefined method Pipedrive\versions\v1\Api\PersonsApi::getPersons()selapsed =' ret Persons),true, "Retrieved persons in &selapsedins", selanced).Alh tocke dano• Update POC to test official SDK methods properly6 Tect actual CPUD onerationc with official Snk.TectDinedriveOfficialSdkCommand.nhr100% Lz• • Mon 18 May 9:44:24+0 ..+16 -6Ask anvthina (&4-L)÷ « CodeSWF.16J 637 tokens* Reiect allAccent allWN Windsurf Teams127-1UTE.8Po 4 spaces...
|
NULL
|
-2260476629171350108
|
NULL
|
click
|
ocr
|
NULL
|
PhostormVIewINavicarecodeLaravelKeractorFV faVsco. PhostormVIewINavicarecodeLaravelKeractorFV faVsco.js?° pipedrive-sdk-pocProiectC ActivityController.ong© SyncProfileMetadata.phpCConvertLeadActivities.ong© SyncPlanhat.php©syncleammetadata.ong© TestPipedriveOfficialSdkCommand.phUpoateopponunityspecrications.onocreatePlaybookcreatedevent.ong>@ Dealinsights>@Dev> 0 Dialersc) IntearationApp/Service.php© LeadConverted.php© CreateSelfCoachedEvent.phpD DTOSC ElasticsearchC) CreateCommentedEvent.phoC) CreateSmsSentEvent.ohpC) PlanhatActivityListener.pho(C)AskAnvthinaPromotService.ohd› EnqagementStatsш GeckoExport(C)AutomatedReportsCommand.ohvphp api y2.ohoC) RequestGenerateReport.Job.oho/>D Livestream(C) AutomatedReportkesulconp© AutomatedReport.phpA console [STAGING]Mallboxes• MidratePlavbackithemes• M Plavbooksdectarelstrict_types-ty,Playlists> M Postmarknanespace Jininny console comnanos crm> M PronhetAuse…..v M Renorto© AutomatedReportsCommand.phpC) AutomatedRenortsRetentionPolicvCor14 Dclass syncubnects extends command© AutomatedReportsSendCommand.pht© CreateMockAskJiminnyReportResultC© DeleteReportCommand.php© GenerateMarketingReport.php© Team.php© Usage.php>C Slack* The name and sianature of the console commandi* ovar strind›_ Teams>C TracksC Transcription>C TwilioC Users> M Vocabularv7301nrotected ssionature = crm:sunc-ohnects -team?, ~--svnctr** The console command descrintion.>Zoom*Avan strinaC) Command.oho© CreateDatabaseUsers.ohpprotected $description = 'Sync remote CRM objects.';C)DatabaseTiableCount.oho© DeleteOIdAiCrmNotesCommand.ohv* Eyecuto the concole commandlCDeleteS?Leftoverscommand.ohn@ DevPostmanCommand.phppublic function handle: voidf...;C) DiarizeViaAiParticinantldentificationCom@ EncrvntTokensCommand.nhn(C EnaadementStatsRedenerateCommand.r( FeatureFlaasHelner nhr69 6t)protected function getStaggerDelavSeconds@: floatf...?© FixCrossTenantlssues.php© FlushRolesPermissionsCache.php© GeneratelnternalWebhookToken.php9 GrounSetDefaultLanguageCommand.php74 6tprotected function getMaxDelavSeconds@: int{...}81 6t>protected function getlogPrefix@: stringf...;© HelperTruncateCoachingTables.phpHelner Code will hoin INF to underctand vour Laravel ann code II Generate II Don't Show Anvmore (Q minutes aaol=custom.loglaravel.logA SF [jiminny@localhost]« HS_local [jiminny@localhost]A console [PROD]© TestPipedriveOfficialSdkCommand.php x © Kernel.phpA console [EU]class TestPipedrive0fficialSdkCommand extends Commandprivate tunction testbasiceruduperations (soclaLAccount ssoclaLAccount vola132134138-rawubloken = ssoclaLaccount->cecrrovlderuserlokenorSthis->info( string: "DB token (Length:"")"Sthis->info( string: "DB token preview: ". substr(SrawDbToken, offset: 0,check token expirySexpires = SsocialAccount->expires:sthas->inrol string:"oken exoires at."Sexoires? carbon::createrromiimesSthis->infod"Token is expired:(Sexnires ss Sexpires ‹ timeo) ? 'v// Test with the DB token directlySthis->infod strinaTesting with DB token ---").Sconfio = new Confiquration0:Scontia->se+AcceccToken(SrawlhToken):/ Test 2a: Get DealsReject// Test 2a: Get Deals Summarysunls->restberuealsscontlo// Test 2b: Get PersonsSthis->testGetPersons(Sconfiq)II Test 2c:sthis->testbetacrivitlesscontz0)catch (Excention Se) <sthis->recordResult testName:'Basic CRUD Operations".success: false. Se->aet152181198private function testCurrentSystemApproach(SocialAccount SsocialAccount): voidf...?Tusageprivate function testGetDeals(Configuration Sconfig): void{...}private function testGetPersons(Configuration Sconfig): void{...}private function testGetActivities(Configuration Sconfiq): void{...}1 usageprivate function testFieldMetadata(SocialAccount SsocialAccount): void{...}privata funation tgst' Accept File &- Cogfiauaatiion, Sconfig) or voigi → . }AskJiminnyReportActivityServiceTest vCascade• Debugging Pipedriveneolnis•TestPipedriveOfficialSdkCommand.php• TestPipedriveOfficialSdkCommand.phpdocker exec docker lamo 1 oho artisan liminnv.test-oinedrive-official-sdk 19Starting Pipedrive Official SDK POC for Team ID: 19Found Pipedrive account for team: Pipedrive, Inc.Test PAS - dauth intzatization: Cient initialized successfully in 148.02msrestoken asec th: operationsToken ex expired: 2E56-05-15 15:2N:2yu...Tstine witbealssmaresulted in a '401 Unauthorized"WC FAIL - Get Deals Summary: 1401l Client error: GET https://ap2.pipedrive.com/vz/deals/summapy resulted in a 401 Unauthorized reTest 2b: Get PersonsErrorCall to undefined method Pipedrive\versions\v1\Api\PersonsApi::getPersons()selapsed =' ret Persons),true, "Retrieved persons in &selapsedins", selanced).Alh tocke dano• Update POC to test official SDK methods properly6 Tect actual CPUD onerationc with official Snk.TectDinedriveOfficialSdkCommand.nhr100% Lz• • Mon 18 May 9:44:24+0 ..+16 -6Ask anvthina (&4-L)÷ « CodeSWF.16J 637 tokens* Reiect allAccent allWN Windsurf Teams127-1UTE.8Po 4 spaces...
|
49428
|
NULL
|
NULL
|
NULL
|
|
49439
|
1764
|
15
|
2026-05-18T06:44:37.659648+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086677659_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
iTerm2ShellEditViewSessionScriptsProfilesWindowHel iTerm2ShellEditViewSessionScriptsProfilesWindowHelp100% C8 • Mon 18 May 9:44:37STAGE (ssh)T81DOCKERX 11 DOCKER (docker-compose)RUNNINGdocker_lamp_11s DONEdocker_1amp_1RUNNINGdocker_lamp_11s DONEdocker_1amp_1docker_lamp_11/fd/1'2>&1docker_lamp_181DEV (-zsh)O $22026-05-18 06:42:22 Jiminny\Jobs\Crm\Hubspot\ProcessWebhookEventsJob2026-05-1806:42:22 Jiminny\Jobs\Crm\Hubspot\ProcessWebhookEventsJob2026-05-18 06:42:23 Jiminny\Jobs\Crm\Hubspot\ProcessWebhookEventsJob11sDONE1 '/usr/local/bin/php' 'artisan' dialers:monitor-activities › '/proc/2026-05-18 06:42:27 Running ['artisan' jiminny:monitor-social-accountdocker_1amp_11 '/usr/local/bin/php' 'artisan' jiminny:monitor-social-accounts > */proc/1/fd/1'2>&1docker_lamp_12026-05-18 06:42:41 Running ['artisan' mailbox:skip-lists:refresh]13s DONEdocker_lamp_11 '/usr/local/bin/php' 'artisan' mailbox:skip-lists:refresh › */proc/1/fd/1' 2>&1docker_lamp_12026-05-18 06:42:54 Running ['artisan'mailbox:batch:process --max-batches=15]14S DONEdocker_lamp_11 '/usr/local/bin/php' 'artisan"mailbox:batch:process --max-batches=15 > '/proc/1/fd/1' 2>&1docker_1amp_12026-05-18 06:43:08 Running ['artisan'conference:monitor: count].. 13S DONEdocker_lamp_11 '/usr/local/bin/php' 'artisan'conference:monitor: count > '/proc/1/fd/1' 2>&1docker_lamp_12026-05-18 06:43:22 Running ['artisan' mailbox:batch: create]...... 14s DONEdocker_lamp_11 '/usr/local/bin/php' 'artisan'mailbox:batch: create › '/proc/1/fd/12>&1docker_lamp_1docker_lamp_1docker_1amp_1run_artisan_schedule: Done waiting for schedule:run2026-05-18 06:43:37 Jiminny\Jobs\Mailbox\CreateBatchesRUNNINGdocker_lamp_12026-05-18 06:43:38 Jiminny Jobs \Mailbox\CreateBatches1s DONEdocker_1amp_1docker_1amp_12026-05-18 06:44:17 Running ['artisan' meeting-bot:schedule-bot] .. 17s DONEdocker_lamp_11 '/usr/local/bin/php' 'artisan' meeting-bot:schedule-bot › */proc/1/fd/1' 2>&1APP (-zsh)*3screenpipe"Y2PROD (-zsh)Last login: Sat May 16 18:04:33 on ttys001Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could notfind a pyproject.toml file in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ ||X t3EU (-zsh)Last login: Sat May 16 18:04:33 on ttys001Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parents@Lukas-Kovaliks-MacBook-Pro-JiminnyX T4 STAGE (ssh)Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.toml file in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ stg(lukas@jiminny-stage-bastion) Verification code: ?QA (-zsh)Last login: Mon May 18 09:17:28 on ttys003Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentsX 16FE (-zsh)Last login: Mon May 18 09:17:28 on ttys004O $4STAGEPoetry could not find a pyproject.toml file in /Users/lukas or its parentsFRONTENDPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ IEXT (-zsh)Poetry could not find a pyproject.toml file in /Users/lukas or its parentsEXTENSIONPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ [|View in Docker DesktopView ConfigEnable Watch...
|
NULL
|
-2280462260247206547
|
NULL
|
click
|
ocr
|
NULL
|
iTerm2ShellEditViewSessionScriptsProfilesWindowHel iTerm2ShellEditViewSessionScriptsProfilesWindowHelp100% C8 • Mon 18 May 9:44:37STAGE (ssh)T81DOCKERX 11 DOCKER (docker-compose)RUNNINGdocker_lamp_11s DONEdocker_1amp_1RUNNINGdocker_lamp_11s DONEdocker_1amp_1docker_lamp_11/fd/1'2>&1docker_lamp_181DEV (-zsh)O $22026-05-18 06:42:22 Jiminny\Jobs\Crm\Hubspot\ProcessWebhookEventsJob2026-05-1806:42:22 Jiminny\Jobs\Crm\Hubspot\ProcessWebhookEventsJob2026-05-18 06:42:23 Jiminny\Jobs\Crm\Hubspot\ProcessWebhookEventsJob11sDONE1 '/usr/local/bin/php' 'artisan' dialers:monitor-activities › '/proc/2026-05-18 06:42:27 Running ['artisan' jiminny:monitor-social-accountdocker_1amp_11 '/usr/local/bin/php' 'artisan' jiminny:monitor-social-accounts > */proc/1/fd/1'2>&1docker_lamp_12026-05-18 06:42:41 Running ['artisan' mailbox:skip-lists:refresh]13s DONEdocker_lamp_11 '/usr/local/bin/php' 'artisan' mailbox:skip-lists:refresh › */proc/1/fd/1' 2>&1docker_lamp_12026-05-18 06:42:54 Running ['artisan'mailbox:batch:process --max-batches=15]14S DONEdocker_lamp_11 '/usr/local/bin/php' 'artisan"mailbox:batch:process --max-batches=15 > '/proc/1/fd/1' 2>&1docker_1amp_12026-05-18 06:43:08 Running ['artisan'conference:monitor: count].. 13S DONEdocker_lamp_11 '/usr/local/bin/php' 'artisan'conference:monitor: count > '/proc/1/fd/1' 2>&1docker_lamp_12026-05-18 06:43:22 Running ['artisan' mailbox:batch: create]...... 14s DONEdocker_lamp_11 '/usr/local/bin/php' 'artisan'mailbox:batch: create › '/proc/1/fd/12>&1docker_lamp_1docker_lamp_1docker_1amp_1run_artisan_schedule: Done waiting for schedule:run2026-05-18 06:43:37 Jiminny\Jobs\Mailbox\CreateBatchesRUNNINGdocker_lamp_12026-05-18 06:43:38 Jiminny Jobs \Mailbox\CreateBatches1s DONEdocker_1amp_1docker_1amp_12026-05-18 06:44:17 Running ['artisan' meeting-bot:schedule-bot] .. 17s DONEdocker_lamp_11 '/usr/local/bin/php' 'artisan' meeting-bot:schedule-bot › */proc/1/fd/1' 2>&1APP (-zsh)*3screenpipe"Y2PROD (-zsh)Last login: Sat May 16 18:04:33 on ttys001Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could notfind a pyproject.toml file in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ ||X t3EU (-zsh)Last login: Sat May 16 18:04:33 on ttys001Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parents@Lukas-Kovaliks-MacBook-Pro-JiminnyX T4 STAGE (ssh)Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.toml file in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ stg(lukas@jiminny-stage-bastion) Verification code: ?QA (-zsh)Last login: Mon May 18 09:17:28 on ttys003Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentsX 16FE (-zsh)Last login: Mon May 18 09:17:28 on ttys004O $4STAGEPoetry could not find a pyproject.toml file in /Users/lukas or its parentsFRONTENDPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ IEXT (-zsh)Poetry could not find a pyproject.toml file in /Users/lukas or its parentsEXTENSIONPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ [|View in Docker DesktopView ConfigEnable Watch...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
49440
|
1765
|
14
|
2026-05-18T06:44:37.754158+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086677754_m2.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PnostormcodeFV faVsco.js?9 pipedrive-sdk-pocrolede PnostormcodeFV faVsco.js?9 pipedrive-sdk-pocroledey© SyncProfileMetadata.php©syncleammetadata.ong© TestPipedriveOfficialSdkCommand.phUpoateopponunityspecrications.ono>@ Dealinsights>@Dev> 0 DialersD DTOSC Elasticsearch› EnqagementStatsш GeckoExport>D LivestreamMallboxes• MidratePlavbackithemesM PlavbooksD Playlists> M Postmark> M PronhetAv M Renorto© AutomatedReportsCommand.phpC) AutomatedRenortsRetentionPolicvCor14 D© AutomatedReportsSendCommand.pht© CreateMockAskJiminnyReportResultC© DeleteReportCommand.php© GenerateMarketingReport.php© Team.php© Usage.php>C Slack›_ Teams>C TracksC Transcription>C TwilioC Users> M Vocabularv7301>ZoomC) Command.oho© CreateDatabaseUsers.ohpC)DatabaseTiableCount.oho© DeleteOIdAiCrmNotesCommand.ohvCDeleteS?Leftoverscommand.ohn@ DevPostmanCommand.phpC) DiarizeViaAiParticinantldentificationCom@ EncrvntTokensCommand.nhn(C EnaaaementStatsReaenerateCommand.n( FeatureFlaasHelner nhr69 6t)© FixCrossTenantlssues.php© FlushRolesPermissionsCache.php© GeneratelnternalWebhookToken.php© GroupSetDefaultLanguageCommand.phc© HelperTruncateCoachingTables.php74 6t81 6t>100% (7 • Mon 18 May 9:44:37AskJiminnyReportActivityServiceTest ve Q.C ActivityController.ongC) ConvertLeadActivities.ong© SyncPlanhat.phpcreatePlaybookcreatedevent.ongc) IntearationApp/Service.php© LeadConverted.php© CreateSelfCoachedEvent.phpC) CreateCommentedEvent.phoC) CreateSmsSentEvent.ohpC) PlanhatActivityListener.pho(C)AskAnvthinaPromotService.ohd(C)AutomatedReportsCommand.ohvphp api y2.ohoC) RequestGenerateReport.Job.oho/(C) AutomatedReportkesulconp© AutomatedReport.phpA console [STAGING]dectarelstrict_types-ty,nanespace Jininny console connanos crmuse…..class syncubnects extends command* The name and sianature of the console commandi* ovar strindnrotected ssionature = crm:sunc-ohnects -team?, *--svnct'** The console command descrintion.* Avan strinaprotected $description = 'Sync remote CRM objects.';* Eyecuto the concole commandlpublic function handle: voidf...;protected function getStaggerDelavSeconds@: floatf...protected function getMaxDelavSeconds@: int{...}protected function getlogPrefix@: stringf...;=custom.loglaravel.log4 SF [jiminny@localhost]« HS_local [jiminny@localhost]A console [PROD]© TestPipedriveOfficialSdkCommand.php x © Kernel.phpA console [EU]class TestPipedrive0fficialSdkCommand extends Commandprivate tunction testbasiceruduperations (soclaLAccount ssoclaLAccount vola134138-rawubloken = ssoclaLaccount->cecrrovlderuserlokenorSthis->info( string: "DB token (Length:"")"Sthis->info( string: "DB token preview: " . substr(SrawDbToken, offset: 0. lengthcheck token expirySexpires = SsocialAccount->expires:sthas->inrol string:"oken exoires at."Sexoires? carbon:: createrromiimesSthis->infod"Token is expired:(Sexnires ss Sexpires < timed) > 'v// Test with the DB token directlySthis->infod strinaTesting with DB token ---").Sconfio = new ConfiaurationOrScontia->se+AcceccToken(SrawlhToken):/ Test 2a: Get DealsReject// Test 2a: Get Deals Summarysunls->restberuealsscontlo// Test 2b: Get PersonsSthis->testGetPersons(Sconfiq)II Test 2c:sthis->testbetacrivitlesscontz0)catch (Excention Se) <sthis->recordResult testName:'Basic CRUD Operations".success: false. Se->aet152198Debugging Pipedrive s• TestPipedriveOfficialSdkCommand.phpcommand dockedocker exec docker_lamp_1 php artisan jiminny:test-pipedrive-official-sdk 19Starting Pipedrive Official SDK POC for Team ID: 19Found Pioedrive account for team: Pinedrive.Inc.Test PASS - DAuth 1aialization: Client initialized successfullv in 148.02mBasie cho operationstoken preview:т tn en rerted: 226405-15 15328194 ...Test 20: Get PersonsErrorcal to undetined method Pioedrive versions viAo1 PersonsAo1::detPersonsotry ppotricant sokofficial SoK uses getPersons methodtmit, a 101).?, trut, -Retrleved person's in (selapsed)ms", selapsed) ;I catch (Exception se) €ThouahtsAlA tocke dond• Update POC to test official SDK methods properly* Test actual CRUD operations with official SDK• TestPipedriveOfficialSdkCoThoushteThe token is expired again (expires at 2026-05-15 15:28:18). Please refresh the token so i can continue testing the official SDK methods.private function testCurrentSystemApproach(SocialAccount SsocialAccount): voidf...?1 usageprivate function testGetDeals(Configuration Sconfig): void{...}private function testGetPersons(Configuration Sconfig): void{...}private function testGetActivities(Configuration Sconfig): voidf...}1 usageprivate function testFieldMetadata(SocialAccount SsocialAccount): void{...}private function testGetDealFields(Confiquration Sconfig): void{...}l* Reiect allAsk anvthina (&4-L)÷ « CodeSWF.16+A-ATenli axeAccent alli•0 1.Po. 4 spac...
|
NULL
|
769658773358293449
|
NULL
|
click
|
ocr
|
NULL
|
PnostormcodeFV faVsco.js?9 pipedrive-sdk-pocrolede PnostormcodeFV faVsco.js?9 pipedrive-sdk-pocroledey© SyncProfileMetadata.php©syncleammetadata.ong© TestPipedriveOfficialSdkCommand.phUpoateopponunityspecrications.ono>@ Dealinsights>@Dev> 0 DialersD DTOSC Elasticsearch› EnqagementStatsш GeckoExport>D LivestreamMallboxes• MidratePlavbackithemesM PlavbooksD Playlists> M Postmark> M PronhetAv M Renorto© AutomatedReportsCommand.phpC) AutomatedRenortsRetentionPolicvCor14 D© AutomatedReportsSendCommand.pht© CreateMockAskJiminnyReportResultC© DeleteReportCommand.php© GenerateMarketingReport.php© Team.php© Usage.php>C Slack›_ Teams>C TracksC Transcription>C TwilioC Users> M Vocabularv7301>ZoomC) Command.oho© CreateDatabaseUsers.ohpC)DatabaseTiableCount.oho© DeleteOIdAiCrmNotesCommand.ohvCDeleteS?Leftoverscommand.ohn@ DevPostmanCommand.phpC) DiarizeViaAiParticinantldentificationCom@ EncrvntTokensCommand.nhn(C EnaaaementStatsReaenerateCommand.n( FeatureFlaasHelner nhr69 6t)© FixCrossTenantlssues.php© FlushRolesPermissionsCache.php© GeneratelnternalWebhookToken.php© GroupSetDefaultLanguageCommand.phc© HelperTruncateCoachingTables.php74 6t81 6t>100% (7 • Mon 18 May 9:44:37AskJiminnyReportActivityServiceTest ve Q.C ActivityController.ongC) ConvertLeadActivities.ong© SyncPlanhat.phpcreatePlaybookcreatedevent.ongc) IntearationApp/Service.php© LeadConverted.php© CreateSelfCoachedEvent.phpC) CreateCommentedEvent.phoC) CreateSmsSentEvent.ohpC) PlanhatActivityListener.pho(C)AskAnvthinaPromotService.ohd(C)AutomatedReportsCommand.ohvphp api y2.ohoC) RequestGenerateReport.Job.oho/(C) AutomatedReportkesulconp© AutomatedReport.phpA console [STAGING]dectarelstrict_types-ty,nanespace Jininny console connanos crmuse…..class syncubnects extends command* The name and sianature of the console commandi* ovar strindnrotected ssionature = crm:sunc-ohnects -team?, *--svnct'** The console command descrintion.* Avan strinaprotected $description = 'Sync remote CRM objects.';* Eyecuto the concole commandlpublic function handle: voidf...;protected function getStaggerDelavSeconds@: floatf...protected function getMaxDelavSeconds@: int{...}protected function getlogPrefix@: stringf...;=custom.loglaravel.log4 SF [jiminny@localhost]« HS_local [jiminny@localhost]A console [PROD]© TestPipedriveOfficialSdkCommand.php x © Kernel.phpA console [EU]class TestPipedrive0fficialSdkCommand extends Commandprivate tunction testbasiceruduperations (soclaLAccount ssoclaLAccount vola134138-rawubloken = ssoclaLaccount->cecrrovlderuserlokenorSthis->info( string: "DB token (Length:"")"Sthis->info( string: "DB token preview: " . substr(SrawDbToken, offset: 0. lengthcheck token expirySexpires = SsocialAccount->expires:sthas->inrol string:"oken exoires at."Sexoires? carbon:: createrromiimesSthis->infod"Token is expired:(Sexnires ss Sexpires < timed) > 'v// Test with the DB token directlySthis->infod strinaTesting with DB token ---").Sconfio = new ConfiaurationOrScontia->se+AcceccToken(SrawlhToken):/ Test 2a: Get DealsReject// Test 2a: Get Deals Summarysunls->restberuealsscontlo// Test 2b: Get PersonsSthis->testGetPersons(Sconfiq)II Test 2c:sthis->testbetacrivitlesscontz0)catch (Excention Se) <sthis->recordResult testName:'Basic CRUD Operations".success: false. Se->aet152198Debugging Pipedrive s• TestPipedriveOfficialSdkCommand.phpcommand dockedocker exec docker_lamp_1 php artisan jiminny:test-pipedrive-official-sdk 19Starting Pipedrive Official SDK POC for Team ID: 19Found Pioedrive account for team: Pinedrive.Inc.Test PASS - DAuth 1aialization: Client initialized successfullv in 148.02mBasie cho operationstoken preview:т tn en rerted: 226405-15 15328194 ...Test 20: Get PersonsErrorcal to undetined method Pioedrive versions viAo1 PersonsAo1::detPersonsotry ppotricant sokofficial SoK uses getPersons methodtmit, a 101).?, trut, -Retrleved person's in (selapsed)ms", selapsed) ;I catch (Exception se) €ThouahtsAlA tocke dond• Update POC to test official SDK methods properly* Test actual CRUD operations with official SDK• TestPipedriveOfficialSdkCoThoushteThe token is expired again (expires at 2026-05-15 15:28:18). Please refresh the token so i can continue testing the official SDK methods.private function testCurrentSystemApproach(SocialAccount SsocialAccount): voidf...?1 usageprivate function testGetDeals(Configuration Sconfig): void{...}private function testGetPersons(Configuration Sconfig): void{...}private function testGetActivities(Configuration Sconfig): voidf...}1 usageprivate function testFieldMetadata(SocialAccount SsocialAccount): void{...}private function testGetDealFields(Confiquration Sconfig): void{...}l* Reiect allAsk anvthina (&4-L)÷ « CodeSWF.16+A-ATenli axeAccent alli•0 1.Po. 4 spac...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
49441
|
1764
|
16
|
2026-05-18T06:44:39.941547+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086679941_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"pipedrive-sdk-poc, menu","depth":5,"on_screen":true,"help_text":"Git Branch: pipedrive-sdk-poc","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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
7266624862028544203
|
577591577001045378
|
visual_change
|
hybrid
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
iTerm2ShellEditViewSessionScriptsProfilesWindowHelp100% <78• Mon 18 May 9:44:39STAGE (ssh)181DOCKER81DEV (-zsh)O ₴2docker_lamp_12026-05-1806:42:22 Jiminny\Jobs\Crm\Hubspot\ProcessWebhookEventsJobdocker_1amp_12026-05-1806:42:22 Jiminny\Jobs\Crm\Hubspot\ProcessWebhookEventsJobdocker_lamp_12026-05-18 06:42:23 Jiminny\Jobs\Crm\Hubspot\ProcessWebhookEventsJobdocker_1amp_1docker_lamp_11 '/usr/local/bin/php' 'artisan' dialers:monitor-activities › '/proc/docker_lamp_12026-05-18 06:42:27 Running ['artisan' jiminny:monitor-social-accountdocker_1amp_1proc/1/fd/1'docker_lamp_11 '/usr/local/bin/php' 'artisan' jiminny:monitor-social-accounts > */2026-05-18 06:42:41 Running ['artisan' mailbox:skip-lists:refresh]docker_lamp_11/fd/1' 2>&11 '/usr/local/bin/php' 'artisan' mailbox:skip-lists:refresh › */proc/2026-05-18 06:42:54 Running ['artisan'mailbox:batch:process --max-badocker_lamp_11 '/usr/local/bin/php' 'artisan'mailbox:batch:process --max-batches=15 > '/proc/1/fd/1' 2>&1docker_1amp_12026-05-18 06:43:08 Running ['artisan'conference:monitor:count]docker_lamp_1fd/1' 2>&1docker_lamp_11 '/usr/local/bin/php' 'artisan'conference:monitor: count > '/proc/1/2026-05-18 06:43:22 Running ['artisan' mailbox:batch: create]docker_lamp_11 '/usr/local/bin/php' 'artisan'mailbox:batch: create › '/proc/1/fd/1docker_lamp_1docker_lamp_1docker_1amp_1run_artisan_schedule: Done waiting for schedule:run2026-05-18 06:43:37 Jiminny\Jobs\Mailbox\CreateBatchesdocker_lamp_11s DONEdocker_1amp_1docker_1amp_17s DONEdocker_lamp_1fd/1' 2>&12026-05-18 06:43:38 Jiminny Jobs Mailbox \CreateBatches2026-05-18 06:44:17 Running ['artisan' meeting-bot:schedule-bot] .. 11 '/usr/local/bin/php' 'artisan' meeting-bot:schedule-bot › */proc/1/APP (-zsh)*3screenpipe"X 12PROD (-zsh)Last login: Sat May 16 18:04:33 on ttys001Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.toml file in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ |X 13EU (-zsh)Last login: Sat May 16 18:04:33 on ttys001Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parents@Lukas-Kovaliks-MacBook-Pro-Jiminny~$ IX T4 STAGE (ssh)Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.toml file in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ stg(lukas@jiminny-stage-bastion) Verification code:QA (-zsh)Last login: Mon May 18 09:17:28 on ttys003Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentsX 16FE (-zsh)Last login: Mon May 18 09:17:28 on ttys004O ₴4PRODSTAGEPoetry could not find a pyproject.toml file in /Users/lukas or its parentsFRONTENDPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ IEXT (-zsh)Poetry could not find a pyproject.toml file in /Users/lukas or its parentsEXTENSIONPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~View in Docker DesktopView ConfigEnable Watch...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
49442
|
1764
|
17
|
2026-05-18T06:44:43.704168+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086683704_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
1
51
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Jiminny\Component\Encryption\EncryptedTokenManager;
use Jiminny\Models\SocialAccount;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Pipedrive\versions\v1\Api\ActivitiesApi;
use Pipedrive\versions\v1\Api\ActivityFieldsApi;
use Pipedrive\versions\v1\Api\DealFieldsApi;
use Pipedrive\versions\v1\Api\DealsApi;
use Pipedrive\versions\v1\Api\PersonFieldsApi;
use Pipedrive\versions\v1\Api\PersonsApi;
use Pipedrive\versions\v1\Configuration;
class TestPipedriveOfficialSdkCommand extends Command
{
protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';
protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';
private array $results = [];
private EncryptedTokenManager $tokenManager;
public function __construct(EncryptedTokenManager $tokenManager)
{
parent::__construct();
$this->tokenManager = $tokenManager;
}
public function handle(): int
{
$teamId = (int) $this->argument('teamId');
$this->info("Starting Pipedrive Official SDK POC for Team ID: {$teamId}");
$this->newLine();
try {
$this->runTests($teamId);
} catch (Exception $e) {
$this->error("POC failed with error: {$e->getMessage()}");
$this->error($e->getTraceAsString());
return 1;
}
$this->displayResults();
return 0;
}
private function runTests(int $teamId): void
{
$team = Team::find($teamId);
if (! $team) {
throw new Exception("Team with ID {$teamId} not found");
}
$socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)
->whereHas('sociable', function ($query) use ($team) {
$query->where('team_id', $team->id);
})
->orderByDesc('expires') // Get the most recently expiring token
->first();
if (! $socialAccount) {
throw new Exception("No Pipedrive social account found for team {$teamId}");
}
$this->info("Found Pipedrive account for team: {$team->name}");
$this->newLine();
// Test 1: OAuth Initialization
$this->testOAuthInitialization($socialAccount);
// Test 2: Basic CRUD Operations
$this->testBasicCrudOperations($socialAccount);
// Test 3: Field Metadata
$this->testFieldMetadata($socialAccount);
// Test 4: v2 API Support
$this->testV2ApiSupport($socialAccount);
// Test 5: Error Handling
$this->testErrorHandling($socialAccount);
}
private function testOAuthInitialization(SocialAccount $socialAccount): void
{
$this->info('Test 1: OAuth Initialization');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', true, "Client initialized successfully in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);
}
$this->newLine();
}
private function testBasicCrudOperations(SocialAccount $socialAccount): void
{
$this->info('Test 2: Basic CRUD Operations');
try {
// Get the raw token from database
$rawDbToken = $socialAccount->getProviderUserToken();
$this->info("DB token (length: " . strlen($rawDbToken) . ")");
$this->info("DB token preview: " . substr($rawDbToken, 0, 20) . "...");
// Check token expiry
$expires = $socialAccount->expires;
$this->info("Token expires at: " . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));
$this->info("Token is expired: " . ($expires && $expires < time() ? 'YES' : 'NO'));
// Test with the DB token directly
$this->info("\n--- Testing with DB token ---");
$config = new Configuration();
$config->setAccessToken($rawDbToken);
// Test 2a: Get Deals Summary
$this->testGetDeals($config);
// Test 2b: Get Persons
$this->testGetPersons($config);
// Test 2c: Get Activities
$this->testGetActivities($config);
} catch (Exception $e) {
$this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testCurrentSystemApproach(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
// Simulate how current system makes requests
$token = $socialAccount->getProviderUserToken();
$url = 'https://api.pipedrive.com/api/v2/deals';
$headers = [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
];
$client = new \GuzzleHttp\Client();
$response = $client->get($url, [
'headers' => $headers,
'timeout' => 30,
]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$statusCode = $response->getStatusCode();
$this->recordResult('Current System v2 API', $statusCode === 200, "Status: {$statusCode} in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);
}
}
private function testGetDeals(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealsApi(null, $config);
// Official SDK uses getDealsSummary instead of getDeals
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', true, "Retrieved deals summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersons(Configuration $config): void
{
$this->info('Test 2b: Get Persons');
$startTime = microtime(true);
try {
$apiInstance = new PersonsApi(null, $config);
// Official SDK uses getAllPersons method
$result = $apiInstance->getAllPersons(['limit' => 10]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', true, "Retrieved persons in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivities(Configuration $config): void
{
$this->info('Test 2c: Get Activities');
$startTime = microtime(true);
try {
$apiInstance = new ActivitiesApi(null, $config);
// Official SDK uses getAllActivities method
$result = $apiInstance->getAllActivities(['limit' => 10]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', true, "Retrieved activities in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);
}
}
private function testFieldMetadata(SocialAccount $socialAccount): void
{
$this->info('Test 3: Field Metadata');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
// Test 3a: Get Deal Fields
$this->testGetDealFields($config);
// Test 3b: Get Person Fields
$this->testGetPersonFields($config);
// Test 3c: Get Activity Fields
$this->testGetActivityFields($config);
} catch (Exception $e) {
$this->recordResult('Field Metadata', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testGetDealFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealFieldsApi(null, $config);
$result = $apiInstance->getDealFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Deal Fields', true, "Retrieved {$count} deal fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersonFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new PersonFieldsApi(null, $config);
$result = $apiInstance->getPersonFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Person Fields', true, "Retrieved {$count} person fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivityFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new ActivityFieldsApi(null, $config);
$result = $apiInstance->getActivityFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Activity Fields', true, "Retrieved {$count} activity fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);
}
}
private function testV2ApiSupport(SocialAccount $socialAccount): void
{
$this->info('Test 4: v2 API Support (Cursor-based Pagination)');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$startTime = microtime(true);
// Test v2 deals endpoint with cursor pagination
// Note: The official SDK may or may not have v2 API support
// This test will determine if v2 is available
try {
$apiInstance = new DealsApi(null, $config);
$updatedSince = Carbon::now()->subDays(30)->toRfc3339String();
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
// Check if response has cursor information (v2 indicator)
// The response object doesn't have getAdditionalData method
// Just test if the call works - v2 API support is limited in official SDK v1
$this->recordResult('v2 API Support', false, "v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);
}
} catch (Exception $e) {
$this->recordResult('v2 API Support', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testErrorHandling(SocialAccount $socialAccount): void
{
$this->info('Test 5: Error Handling');
try {
// Test 5a: Invalid Token
$this->testInvalidToken();
// Test 5b: Non-existent Resource
$this->testNonExistentResource($socialAccount);
} catch (Exception $e) {
$this->recordResult('Error Handling', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testInvalidToken(): void
{
$this->info('Test 5a: Invalid Token');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken('invalid_token');
$client = new DealsApi(null, $config);
$client->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function testNonExistentResource(SocialAccount $socialAccount): void
{
$this->info('Test 5b: Non-existent Resource');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
// Try to get a deal with a very high ID that likely doesn't exist
// Official SDK has getDealsSummary which works without specific ID
// Instead, let's test with an invalid field ID
try {
$fieldClient = new DealFieldsApi(null, $config);
$fieldClient->getDealField(999999999); // Very high ID that likely doesn't exist
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, $e->getMessage(), $elapsed);
}
}
private function recordResult(string $testName, bool $success, string $message, float $elapsed): void
{
$this->results[] = [
'test' => $testName,
'success' => $success,
'message' => $message,
'elapsed' => $elapsed,
];
$status = $success ? '✅ PASS' : '❌ FAIL';
$this->line(" {$status} - {$testName}: {$message}");
}
private function displayResults(): void
{
$this->newLine();
$this->info('=== POC Test Results Summary ===');
$this->newLine();
$total = count($this->results);
$passed = count(array_filter($this->results, fn ($r) => $r['success']));
$failed = $total - $passed;
$this->line("Total Tests: {$total}");
$this->line("Passed: {$passed}");
$this->line("Failed: {$failed}");
$this->newLine();
$totalTime = array_sum(array_column($this->results, 'elapsed'));
$this->line("Total Execution Time: {$totalTime}ms");
$this->newLine();
if ($failed > 0) {
$this->warn('Failed Tests:');
foreach ($this->results as $result) {
if (! $result['success']) {
$this->line(" - {$result['test']}: {$result['message']}");
}
}
$this->newLine();
}
$this->info('=== Detailed Results ===');
foreach ($this->results as $result) {
$status = $result['success'] ? '✅' : '❌';
$this->line("{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}");
}
$this->newLine();
$this->info('=== Recommendations ===');
if ($passed === $total) {
$this->info('✅ All tests passed! The official SDK is viable for migration.');
$this->info('Next steps: Proceed with full migration planning.');
} else {
$this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');
$this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"pipedrive-sdk-poc, menu","depth":5,"on_screen":true,"help_text":"Git Branch: pipedrive-sdk-poc","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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","role_description":"text entry area","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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":false,"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},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"51","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.02013889,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"on_screen":false,"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},"on_screen":false,"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\\Console\\Commands\\Crm;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Console\\Command;\nuse Jiminny\\Component\\Encryption\\EncryptedTokenManager;\nuse Jiminny\\Models\\SocialAccount;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Pipedrive\\versions\\v1\\Api\\ActivitiesApi;\nuse Pipedrive\\versions\\v1\\Api\\ActivityFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonsApi;\nuse Pipedrive\\versions\\v1\\Configuration;\n\nclass TestPipedriveOfficialSdkCommand extends Command\n{\n protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';\n\n protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';\n\n private array $results = [];\n\n private EncryptedTokenManager $tokenManager;\n\n public function __construct(EncryptedTokenManager $tokenManager)\n {\n parent::__construct();\n $this->tokenManager = $tokenManager;\n }\n\n public function handle(): int\n {\n $teamId = (int) $this->argument('teamId');\n\n $this->info(\"Starting Pipedrive Official SDK POC for Team ID: {$teamId}\");\n $this->newLine();\n\n try {\n $this->runTests($teamId);\n } catch (Exception $e) {\n $this->error(\"POC failed with error: {$e->getMessage()}\");\n $this->error($e->getTraceAsString());\n\n return 1;\n }\n\n $this->displayResults();\n\n return 0;\n }\n\n private function runTests(int $teamId): void\n {\n $team = Team::find($teamId);\n if (! $team) {\n throw new Exception(\"Team with ID {$teamId} not found\");\n }\n\n $socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)\n ->whereHas('sociable', function ($query) use ($team) {\n $query->where('team_id', $team->id);\n })\n ->orderByDesc('expires') // Get the most recently expiring token\n ->first();\n\n if (! $socialAccount) {\n throw new Exception(\"No Pipedrive social account found for team {$teamId}\");\n }\n\n $this->info(\"Found Pipedrive account for team: {$team->name}\");\n $this->newLine();\n\n // Test 1: OAuth Initialization\n $this->testOAuthInitialization($socialAccount);\n\n // Test 2: Basic CRUD Operations\n $this->testBasicCrudOperations($socialAccount);\n\n // Test 3: Field Metadata\n $this->testFieldMetadata($socialAccount);\n\n // Test 4: v2 API Support\n $this->testV2ApiSupport($socialAccount);\n\n // Test 5: Error Handling\n $this->testErrorHandling($socialAccount);\n }\n\n private function testOAuthInitialization(SocialAccount $socialAccount): void\n {\n $this->info('Test 1: OAuth Initialization');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $client = new DealsApi(null, $config);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', true, \"Client initialized successfully in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);\n }\n\n $this->newLine();\n }\n\n private function testBasicCrudOperations(SocialAccount $socialAccount): void\n {\n $this->info('Test 2: Basic CRUD Operations');\n\n try {\n // Get the raw token from database\n $rawDbToken = $socialAccount->getProviderUserToken();\n $this->info(\"DB token (length: \" . strlen($rawDbToken) . \")\");\n $this->info(\"DB token preview: \" . substr($rawDbToken, 0, 20) . \"...\");\n\n // Check token expiry\n $expires = $socialAccount->expires;\n $this->info(\"Token expires at: \" . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));\n $this->info(\"Token is expired: \" . ($expires && $expires < time() ? 'YES' : 'NO'));\n\n // Test with the DB token directly\n $this->info(\"\\n--- Testing with DB token ---\");\n $config = new Configuration();\n $config->setAccessToken($rawDbToken);\n\n // Test 2a: Get Deals Summary\n $this->testGetDeals($config);\n\n // Test 2b: Get Persons\n $this->testGetPersons($config);\n\n // Test 2c: Get Activities\n $this->testGetActivities($config);\n } catch (Exception $e) {\n $this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testCurrentSystemApproach(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n // Simulate how current system makes requests\n $token = $socialAccount->getProviderUserToken();\n $url = 'https://api.pipedrive.com/api/v2/deals';\n\n $headers = [\n 'Authorization' => 'Bearer ' . $token,\n 'Accept' => 'application/json',\n ];\n\n $client = new \\GuzzleHttp\\Client();\n $response = $client->get($url, [\n 'headers' => $headers,\n 'timeout' => 30,\n ]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $statusCode = $response->getStatusCode();\n $this->recordResult('Current System v2 API', $statusCode === 200, \"Status: {$statusCode} in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetDeals(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealsApi(null, $config);\n // Official SDK uses getDealsSummary instead of getDeals\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', true, \"Retrieved deals summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersons(Configuration $config): void\n {\n $this->info('Test 2b: Get Persons');\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonsApi(null, $config);\n // Official SDK uses getAllPersons method\n $result = $apiInstance->getAllPersons(['limit' => 10]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', true, \"Retrieved persons in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivities(Configuration $config): void\n {\n $this->info('Test 2c: Get Activities');\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivitiesApi(null, $config);\n // Official SDK uses getAllActivities method\n $result = $apiInstance->getAllActivities(['limit' => 10]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', true, \"Retrieved activities in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testFieldMetadata(SocialAccount $socialAccount): void\n {\n $this->info('Test 3: Field Metadata');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n // Test 3a: Get Deal Fields\n $this->testGetDealFields($config);\n\n // Test 3b: Get Person Fields\n $this->testGetPersonFields($config);\n\n // Test 3c: Get Activity Fields\n $this->testGetActivityFields($config);\n } catch (Exception $e) {\n $this->recordResult('Field Metadata', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testGetDealFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealFieldsApi(null, $config);\n $result = $apiInstance->getDealFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Deal Fields', true, \"Retrieved {$count} deal fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersonFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonFieldsApi(null, $config);\n $result = $apiInstance->getPersonFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Person Fields', true, \"Retrieved {$count} person fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivityFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivityFieldsApi(null, $config);\n $result = $apiInstance->getActivityFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Activity Fields', true, \"Retrieved {$count} activity fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testV2ApiSupport(SocialAccount $socialAccount): void\n {\n $this->info('Test 4: v2 API Support (Cursor-based Pagination)');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $startTime = microtime(true);\n\n // Test v2 deals endpoint with cursor pagination\n // Note: The official SDK may or may not have v2 API support\n // This test will determine if v2 is available\n try {\n $apiInstance = new DealsApi(null, $config);\n $updatedSince = Carbon::now()->subDays(30)->toRfc3339String();\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n\n // Check if response has cursor information (v2 indicator)\n // The response object doesn't have getAdditionalData method\n // Just test if the call works - v2 API support is limited in official SDK v1\n $this->recordResult('v2 API Support', false, \"v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);\n }\n } catch (Exception $e) {\n $this->recordResult('v2 API Support', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testErrorHandling(SocialAccount $socialAccount): void\n {\n $this->info('Test 5: Error Handling');\n\n try {\n // Test 5a: Invalid Token\n $this->testInvalidToken();\n\n // Test 5b: Non-existent Resource\n $this->testNonExistentResource($socialAccount);\n } catch (Exception $e) {\n $this->recordResult('Error Handling', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testInvalidToken(): void\n {\n $this->info('Test 5a: Invalid Token');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken('invalid_token');\n $client = new DealsApi(null, $config);\n\n $client->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function testNonExistentResource(SocialAccount $socialAccount): void\n {\n $this->info('Test 5b: Non-existent Resource');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n $client = new DealsApi(null, $config);\n\n // Try to get a deal with a very high ID that likely doesn't exist\n // Official SDK has getDealsSummary which works without specific ID\n // Instead, let's test with an invalid field ID\n try {\n $fieldClient = new DealFieldsApi(null, $config);\n $fieldClient->getDealField(999999999); // Very high ID that likely doesn't exist\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function recordResult(string $testName, bool $success, string $message, float $elapsed): void\n {\n $this->results[] = [\n 'test' => $testName,\n 'success' => $success,\n 'message' => $message,\n 'elapsed' => $elapsed,\n ];\n\n $status = $success ? '✅ PASS' : '❌ FAIL';\n $this->line(\" {$status} - {$testName}: {$message}\");\n }\n\n private function displayResults(): void\n {\n $this->newLine();\n $this->info('=== POC Test Results Summary ===');\n $this->newLine();\n\n $total = count($this->results);\n $passed = count(array_filter($this->results, fn ($r) => $r['success']));\n $failed = $total - $passed;\n\n $this->line(\"Total Tests: {$total}\");\n $this->line(\"Passed: {$passed}\");\n $this->line(\"Failed: {$failed}\");\n $this->newLine();\n\n $totalTime = array_sum(array_column($this->results, 'elapsed'));\n $this->line(\"Total Execution Time: {$totalTime}ms\");\n $this->newLine();\n\n if ($failed > 0) {\n $this->warn('Failed Tests:');\n foreach ($this->results as $result) {\n if (! $result['success']) {\n $this->line(\" - {$result['test']}: {$result['message']}\");\n }\n }\n $this->newLine();\n }\n\n $this->info('=== Detailed Results ===');\n foreach ($this->results as $result) {\n $status = $result['success'] ? '✅' : '❌';\n $this->line(\"{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}\");\n }\n\n $this->newLine();\n $this->info('=== Recommendations ===');\n\n if ($passed === $total) {\n $this->info('✅ All tests passed! The official SDK is viable for migration.');\n $this->info('Next steps: Proceed with full migration planning.');\n } else {\n $this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');\n $this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Console\\Commands\\Crm;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Console\\Command;\nuse Jiminny\\Component\\Encryption\\EncryptedTokenManager;\nuse Jiminny\\Models\\SocialAccount;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Pipedrive\\versions\\v1\\Api\\ActivitiesApi;\nuse Pipedrive\\versions\\v1\\Api\\ActivityFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\DealsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonFieldsApi;\nuse Pipedrive\\versions\\v1\\Api\\PersonsApi;\nuse Pipedrive\\versions\\v1\\Configuration;\n\nclass TestPipedriveOfficialSdkCommand extends Command\n{\n protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';\n\n protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';\n\n private array $results = [];\n\n private EncryptedTokenManager $tokenManager;\n\n public function __construct(EncryptedTokenManager $tokenManager)\n {\n parent::__construct();\n $this->tokenManager = $tokenManager;\n }\n\n public function handle(): int\n {\n $teamId = (int) $this->argument('teamId');\n\n $this->info(\"Starting Pipedrive Official SDK POC for Team ID: {$teamId}\");\n $this->newLine();\n\n try {\n $this->runTests($teamId);\n } catch (Exception $e) {\n $this->error(\"POC failed with error: {$e->getMessage()}\");\n $this->error($e->getTraceAsString());\n\n return 1;\n }\n\n $this->displayResults();\n\n return 0;\n }\n\n private function runTests(int $teamId): void\n {\n $team = Team::find($teamId);\n if (! $team) {\n throw new Exception(\"Team with ID {$teamId} not found\");\n }\n\n $socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)\n ->whereHas('sociable', function ($query) use ($team) {\n $query->where('team_id', $team->id);\n })\n ->orderByDesc('expires') // Get the most recently expiring token\n ->first();\n\n if (! $socialAccount) {\n throw new Exception(\"No Pipedrive social account found for team {$teamId}\");\n }\n\n $this->info(\"Found Pipedrive account for team: {$team->name}\");\n $this->newLine();\n\n // Test 1: OAuth Initialization\n $this->testOAuthInitialization($socialAccount);\n\n // Test 2: Basic CRUD Operations\n $this->testBasicCrudOperations($socialAccount);\n\n // Test 3: Field Metadata\n $this->testFieldMetadata($socialAccount);\n\n // Test 4: v2 API Support\n $this->testV2ApiSupport($socialAccount);\n\n // Test 5: Error Handling\n $this->testErrorHandling($socialAccount);\n }\n\n private function testOAuthInitialization(SocialAccount $socialAccount): void\n {\n $this->info('Test 1: OAuth Initialization');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $client = new DealsApi(null, $config);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', true, \"Client initialized successfully in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);\n }\n\n $this->newLine();\n }\n\n private function testBasicCrudOperations(SocialAccount $socialAccount): void\n {\n $this->info('Test 2: Basic CRUD Operations');\n\n try {\n // Get the raw token from database\n $rawDbToken = $socialAccount->getProviderUserToken();\n $this->info(\"DB token (length: \" . strlen($rawDbToken) . \")\");\n $this->info(\"DB token preview: \" . substr($rawDbToken, 0, 20) . \"...\");\n\n // Check token expiry\n $expires = $socialAccount->expires;\n $this->info(\"Token expires at: \" . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));\n $this->info(\"Token is expired: \" . ($expires && $expires < time() ? 'YES' : 'NO'));\n\n // Test with the DB token directly\n $this->info(\"\\n--- Testing with DB token ---\");\n $config = new Configuration();\n $config->setAccessToken($rawDbToken);\n\n // Test 2a: Get Deals Summary\n $this->testGetDeals($config);\n\n // Test 2b: Get Persons\n $this->testGetPersons($config);\n\n // Test 2c: Get Activities\n $this->testGetActivities($config);\n } catch (Exception $e) {\n $this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testCurrentSystemApproach(SocialAccount $socialAccount): void\n {\n $startTime = microtime(true);\n\n try {\n // Simulate how current system makes requests\n $token = $socialAccount->getProviderUserToken();\n $url = 'https://api.pipedrive.com/api/v2/deals';\n\n $headers = [\n 'Authorization' => 'Bearer ' . $token,\n 'Accept' => 'application/json',\n ];\n\n $client = new \\GuzzleHttp\\Client();\n $response = $client->get($url, [\n 'headers' => $headers,\n 'timeout' => 30,\n ]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $statusCode = $response->getStatusCode();\n $this->recordResult('Current System v2 API', $statusCode === 200, \"Status: {$statusCode} in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetDeals(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealsApi(null, $config);\n // Official SDK uses getDealsSummary instead of getDeals\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', true, \"Retrieved deals summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersons(Configuration $config): void\n {\n $this->info('Test 2b: Get Persons');\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonsApi(null, $config);\n // Official SDK uses getAllPersons method\n $result = $apiInstance->getAllPersons(['limit' => 10]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', true, \"Retrieved persons in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivities(Configuration $config): void\n {\n $this->info('Test 2c: Get Activities');\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivitiesApi(null, $config);\n // Official SDK uses getAllActivities method\n $result = $apiInstance->getAllActivities(['limit' => 10]);\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', true, \"Retrieved activities in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testFieldMetadata(SocialAccount $socialAccount): void\n {\n $this->info('Test 3: Field Metadata');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n // Test 3a: Get Deal Fields\n $this->testGetDealFields($config);\n\n // Test 3b: Get Person Fields\n $this->testGetPersonFields($config);\n\n // Test 3c: Get Activity Fields\n $this->testGetActivityFields($config);\n } catch (Exception $e) {\n $this->recordResult('Field Metadata', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testGetDealFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new DealFieldsApi(null, $config);\n $result = $apiInstance->getDealFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Deal Fields', true, \"Retrieved {$count} deal fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetPersonFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new PersonFieldsApi(null, $config);\n $result = $apiInstance->getPersonFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Person Fields', true, \"Retrieved {$count} person fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testGetActivityFields(Configuration $config): void\n {\n $startTime = microtime(true);\n\n try {\n $apiInstance = new ActivityFieldsApi(null, $config);\n $result = $apiInstance->getActivityFields();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $count = $result->getData() ? count($result->getData()) : 0;\n $this->recordResult('Get Activity Fields', true, \"Retrieved {$count} activity fields in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function testV2ApiSupport(SocialAccount $socialAccount): void\n {\n $this->info('Test 4: v2 API Support (Cursor-based Pagination)');\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n\n $startTime = microtime(true);\n\n // Test v2 deals endpoint with cursor pagination\n // Note: The official SDK may or may not have v2 API support\n // This test will determine if v2 is available\n try {\n $apiInstance = new DealsApi(null, $config);\n $updatedSince = Carbon::now()->subDays(30)->toRfc3339String();\n $result = $apiInstance->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n\n // Check if response has cursor information (v2 indicator)\n // The response object doesn't have getAdditionalData method\n // Just test if the call works - v2 API support is limited in official SDK v1\n $this->recordResult('v2 API Support', false, \"v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);\n }\n } catch (Exception $e) {\n $this->recordResult('v2 API Support', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testErrorHandling(SocialAccount $socialAccount): void\n {\n $this->info('Test 5: Error Handling');\n\n try {\n // Test 5a: Invalid Token\n $this->testInvalidToken();\n\n // Test 5b: Non-existent Resource\n $this->testNonExistentResource($socialAccount);\n } catch (Exception $e) {\n $this->recordResult('Error Handling', false, $e->getMessage(), 0);\n }\n\n $this->newLine();\n }\n\n private function testInvalidToken(): void\n {\n $this->info('Test 5a: Invalid Token');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken('invalid_token');\n $client = new DealsApi(null, $config);\n\n $client->getDealsSummary();\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Invalid Token Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n }\n\n private function testNonExistentResource(SocialAccount $socialAccount): void\n {\n $this->info('Test 5b: Non-existent Resource');\n $startTime = microtime(true);\n\n try {\n $config = new Configuration();\n $config->setAccessToken($socialAccount->provider_user_token);\n $client = new DealsApi(null, $config);\n\n // Try to get a deal with a very high ID that likely doesn't exist\n // Official SDK has getDealsSummary which works without specific ID\n // Instead, let's test with an invalid field ID\n try {\n $fieldClient = new DealFieldsApi(null, $config);\n $fieldClient->getDealField(999999999); // Very high ID that likely doesn't exist\n\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, \"Expected error but request succeeded in {$elapsed}ms\", $elapsed);\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', true, \"Correctly threw exception: {$e->getMessage()} in {$elapsed}ms\", $elapsed);\n }\n } catch (Exception $e) {\n $elapsed = round((microtime(true) - $startTime) * 1000, 2);\n $this->recordResult('Non-existent Resource Error', false, $e->getMessage(), $elapsed);\n }\n }\n\n private function recordResult(string $testName, bool $success, string $message, float $elapsed): void\n {\n $this->results[] = [\n 'test' => $testName,\n 'success' => $success,\n 'message' => $message,\n 'elapsed' => $elapsed,\n ];\n\n $status = $success ? '✅ PASS' : '❌ FAIL';\n $this->line(\" {$status} - {$testName}: {$message}\");\n }\n\n private function displayResults(): void\n {\n $this->newLine();\n $this->info('=== POC Test Results Summary ===');\n $this->newLine();\n\n $total = count($this->results);\n $passed = count(array_filter($this->results, fn ($r) => $r['success']));\n $failed = $total - $passed;\n\n $this->line(\"Total Tests: {$total}\");\n $this->line(\"Passed: {$passed}\");\n $this->line(\"Failed: {$failed}\");\n $this->newLine();\n\n $totalTime = array_sum(array_column($this->results, 'elapsed'));\n $this->line(\"Total Execution Time: {$totalTime}ms\");\n $this->newLine();\n\n if ($failed > 0) {\n $this->warn('Failed Tests:');\n foreach ($this->results as $result) {\n if (! $result['success']) {\n $this->line(\" - {$result['test']}: {$result['message']}\");\n }\n }\n $this->newLine();\n }\n\n $this->info('=== Detailed Results ===');\n foreach ($this->results as $result) {\n $status = $result['success'] ? '✅' : '❌';\n $this->line(\"{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}\");\n }\n\n $this->newLine();\n $this->info('=== Recommendations ===');\n\n if ($passed === $total) {\n $this->info('✅ All tests passed! The official SDK is viable for migration.');\n $this->info('Next steps: Proceed with full migration planning.');\n } else {\n $this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');\n $this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-7411996384006884351
|
34631079629834884
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
1
51
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Jiminny\Component\Encryption\EncryptedTokenManager;
use Jiminny\Models\SocialAccount;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Pipedrive\versions\v1\Api\ActivitiesApi;
use Pipedrive\versions\v1\Api\ActivityFieldsApi;
use Pipedrive\versions\v1\Api\DealFieldsApi;
use Pipedrive\versions\v1\Api\DealsApi;
use Pipedrive\versions\v1\Api\PersonFieldsApi;
use Pipedrive\versions\v1\Api\PersonsApi;
use Pipedrive\versions\v1\Configuration;
class TestPipedriveOfficialSdkCommand extends Command
{
protected $signature = 'jiminny:test-pipedrive-official-sdk {teamId}';
protected $description = 'Test official Pipedrive SDK (pipedrive/pipedrive) with existing OAuth tokens';
private array $results = [];
private EncryptedTokenManager $tokenManager;
public function __construct(EncryptedTokenManager $tokenManager)
{
parent::__construct();
$this->tokenManager = $tokenManager;
}
public function handle(): int
{
$teamId = (int) $this->argument('teamId');
$this->info("Starting Pipedrive Official SDK POC for Team ID: {$teamId}");
$this->newLine();
try {
$this->runTests($teamId);
} catch (Exception $e) {
$this->error("POC failed with error: {$e->getMessage()}");
$this->error($e->getTraceAsString());
return 1;
}
$this->displayResults();
return 0;
}
private function runTests(int $teamId): void
{
$team = Team::find($teamId);
if (! $team) {
throw new Exception("Team with ID {$teamId} not found");
}
$socialAccount = SocialAccount::where('provider', SocialAccount::PROVIDER_PIPEDRIVE)
->whereHas('sociable', function ($query) use ($team) {
$query->where('team_id', $team->id);
})
->orderByDesc('expires') // Get the most recently expiring token
->first();
if (! $socialAccount) {
throw new Exception("No Pipedrive social account found for team {$teamId}");
}
$this->info("Found Pipedrive account for team: {$team->name}");
$this->newLine();
// Test 1: OAuth Initialization
$this->testOAuthInitialization($socialAccount);
// Test 2: Basic CRUD Operations
$this->testBasicCrudOperations($socialAccount);
// Test 3: Field Metadata
$this->testFieldMetadata($socialAccount);
// Test 4: v2 API Support
$this->testV2ApiSupport($socialAccount);
// Test 5: Error Handling
$this->testErrorHandling($socialAccount);
}
private function testOAuthInitialization(SocialAccount $socialAccount): void
{
$this->info('Test 1: OAuth Initialization');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', true, "Client initialized successfully in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('OAuth Initialization', false, $e->getMessage(), $elapsed);
}
$this->newLine();
}
private function testBasicCrudOperations(SocialAccount $socialAccount): void
{
$this->info('Test 2: Basic CRUD Operations');
try {
// Get the raw token from database
$rawDbToken = $socialAccount->getProviderUserToken();
$this->info("DB token (length: " . strlen($rawDbToken) . ")");
$this->info("DB token preview: " . substr($rawDbToken, 0, 20) . "...");
// Check token expiry
$expires = $socialAccount->expires;
$this->info("Token expires at: " . ($expires ? Carbon::createFromTimestamp($expires)->toDateTimeString() : 'null'));
$this->info("Token is expired: " . ($expires && $expires < time() ? 'YES' : 'NO'));
// Test with the DB token directly
$this->info("\n--- Testing with DB token ---");
$config = new Configuration();
$config->setAccessToken($rawDbToken);
// Test 2a: Get Deals Summary
$this->testGetDeals($config);
// Test 2b: Get Persons
$this->testGetPersons($config);
// Test 2c: Get Activities
$this->testGetActivities($config);
} catch (Exception $e) {
$this->recordResult('Basic CRUD Operations', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testCurrentSystemApproach(SocialAccount $socialAccount): void
{
$startTime = microtime(true);
try {
// Simulate how current system makes requests
$token = $socialAccount->getProviderUserToken();
$url = 'https://api.pipedrive.com/api/v2/deals';
$headers = [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
];
$client = new \GuzzleHttp\Client();
$response = $client->get($url, [
'headers' => $headers,
'timeout' => 30,
]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$statusCode = $response->getStatusCode();
$this->recordResult('Current System v2 API', $statusCode === 200, "Status: {$statusCode} in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Current System v2 API', false, $e->getMessage(), $elapsed);
}
}
private function testGetDeals(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealsApi(null, $config);
// Official SDK uses getDealsSummary instead of getDeals
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', true, "Retrieved deals summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deals Summary', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersons(Configuration $config): void
{
$this->info('Test 2b: Get Persons');
$startTime = microtime(true);
try {
$apiInstance = new PersonsApi(null, $config);
// Official SDK uses getAllPersons method
$result = $apiInstance->getAllPersons(['limit' => 10]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', true, "Retrieved persons in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Persons', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivities(Configuration $config): void
{
$this->info('Test 2c: Get Activities');
$startTime = microtime(true);
try {
$apiInstance = new ActivitiesApi(null, $config);
// Official SDK uses getAllActivities method
$result = $apiInstance->getAllActivities(['limit' => 10]);
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', true, "Retrieved activities in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activities', false, $e->getMessage(), $elapsed);
}
}
private function testFieldMetadata(SocialAccount $socialAccount): void
{
$this->info('Test 3: Field Metadata');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
// Test 3a: Get Deal Fields
$this->testGetDealFields($config);
// Test 3b: Get Person Fields
$this->testGetPersonFields($config);
// Test 3c: Get Activity Fields
$this->testGetActivityFields($config);
} catch (Exception $e) {
$this->recordResult('Field Metadata', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testGetDealFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new DealFieldsApi(null, $config);
$result = $apiInstance->getDealFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Deal Fields', true, "Retrieved {$count} deal fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Deal Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetPersonFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new PersonFieldsApi(null, $config);
$result = $apiInstance->getPersonFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Person Fields', true, "Retrieved {$count} person fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Person Fields', false, $e->getMessage(), $elapsed);
}
}
private function testGetActivityFields(Configuration $config): void
{
$startTime = microtime(true);
try {
$apiInstance = new ActivityFieldsApi(null, $config);
$result = $apiInstance->getActivityFields();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$count = $result->getData() ? count($result->getData()) : 0;
$this->recordResult('Get Activity Fields', true, "Retrieved {$count} activity fields in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Get Activity Fields', false, $e->getMessage(), $elapsed);
}
}
private function testV2ApiSupport(SocialAccount $socialAccount): void
{
$this->info('Test 4: v2 API Support (Cursor-based Pagination)');
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$startTime = microtime(true);
// Test v2 deals endpoint with cursor pagination
// Note: The official SDK may or may not have v2 API support
// This test will determine if v2 is available
try {
$apiInstance = new DealsApi(null, $config);
$updatedSince = Carbon::now()->subDays(30)->toRfc3339String();
$result = $apiInstance->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
// Check if response has cursor information (v2 indicator)
// The response object doesn't have getAdditionalData method
// Just test if the call works - v2 API support is limited in official SDK v1
$this->recordResult('v2 API Support', false, "v1 API works but v2 cursor pagination not available in official SDK v1. Retrieved summary in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('v2 API Support', false, $e->getMessage(), $elapsed);
}
} catch (Exception $e) {
$this->recordResult('v2 API Support', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testErrorHandling(SocialAccount $socialAccount): void
{
$this->info('Test 5: Error Handling');
try {
// Test 5a: Invalid Token
$this->testInvalidToken();
// Test 5b: Non-existent Resource
$this->testNonExistentResource($socialAccount);
} catch (Exception $e) {
$this->recordResult('Error Handling', false, $e->getMessage(), 0);
}
$this->newLine();
}
private function testInvalidToken(): void
{
$this->info('Test 5a: Invalid Token');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken('invalid_token');
$client = new DealsApi(null, $config);
$client->getDealsSummary();
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Invalid Token Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
}
private function testNonExistentResource(SocialAccount $socialAccount): void
{
$this->info('Test 5b: Non-existent Resource');
$startTime = microtime(true);
try {
$config = new Configuration();
$config->setAccessToken($socialAccount->provider_user_token);
$client = new DealsApi(null, $config);
// Try to get a deal with a very high ID that likely doesn't exist
// Official SDK has getDealsSummary which works without specific ID
// Instead, let's test with an invalid field ID
try {
$fieldClient = new DealFieldsApi(null, $config);
$fieldClient->getDealField(999999999); // Very high ID that likely doesn't exist
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, "Expected error but request succeeded in {$elapsed}ms", $elapsed);
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', true, "Correctly threw exception: {$e->getMessage()} in {$elapsed}ms", $elapsed);
}
} catch (Exception $e) {
$elapsed = round((microtime(true) - $startTime) * 1000, 2);
$this->recordResult('Non-existent Resource Error', false, $e->getMessage(), $elapsed);
}
}
private function recordResult(string $testName, bool $success, string $message, float $elapsed): void
{
$this->results[] = [
'test' => $testName,
'success' => $success,
'message' => $message,
'elapsed' => $elapsed,
];
$status = $success ? '✅ PASS' : '❌ FAIL';
$this->line(" {$status} - {$testName}: {$message}");
}
private function displayResults(): void
{
$this->newLine();
$this->info('=== POC Test Results Summary ===');
$this->newLine();
$total = count($this->results);
$passed = count(array_filter($this->results, fn ($r) => $r['success']));
$failed = $total - $passed;
$this->line("Total Tests: {$total}");
$this->line("Passed: {$passed}");
$this->line("Failed: {$failed}");
$this->newLine();
$totalTime = array_sum(array_column($this->results, 'elapsed'));
$this->line("Total Execution Time: {$totalTime}ms");
$this->newLine();
if ($failed > 0) {
$this->warn('Failed Tests:');
foreach ($this->results as $result) {
if (! $result['success']) {
$this->line(" - {$result['test']}: {$result['message']}");
}
}
$this->newLine();
}
$this->info('=== Detailed Results ===');
foreach ($this->results as $result) {
$status = $result['success'] ? '✅' : '❌';
$this->line("{$status} {$result['test']} ({$result['elapsed']}ms): {$result['message']}");
}
$this->newLine();
$this->info('=== Recommendations ===');
if ($passed === $total) {
$this->info('✅ All tests passed! The official SDK is viable for migration.');
$this->info('Next steps: Proceed with full migration planning.');
} else {
$this->warn('⚠️ Some tests failed. Review the failures above before proceeding.');
$this->warn('Consider hybrid approach or investigate alternatives if critical operations fail.');
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
49441
|
NULL
|
NULL
|
NULL
|
|
49443
|
1765
|
15
|
2026-05-18T06:44:43.704177+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086683704_m2.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"pipedrive-sdk-poc, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.05618351,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: pipedrive-sdk-poc","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.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","depth":4,"bounds":{"left":0.15724733,"top":0.2952913,"width":0.21476063,"height":0.7047087},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-7615602772621210147
|
-8678315669563599930
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
49444
|
1765
|
16
|
2026-05-18T06:44:44.591604+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779086684591_m2.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}
Code changed:
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"pipedrive-sdk-poc, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.05618351,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: pipedrive-sdk-poc","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.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"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.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"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\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","depth":4,"bounds":{"left":0.15724733,"top":0.1963288,"width":0.390625,"height":0.8036712},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Console\\Commands\\Crm;\n\nuse Jiminny\\Console\\Commands\\Command;\nuse Jiminny\\Console\\Commands\\Crm\\Traits\\SyncObjectsCommandTrait;\nuse Jiminny\\Jobs\\Crm\\SyncObjects as SyncObjectsJob;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Team;\n\nclass SyncObjects extends Command\n{\n use SyncObjectsCommandTrait;\n\n /**\n * The name and signature of the console command.\n *\n * @var string\n */\n protected $signature = 'crm:sync-objects {team?} {--sync}';\n\n /**\n * The console command description.\n *\n * @var string\n */\n protected $description = 'Sync remote CRM objects.';\n\n /**\n * Execute the console command.\n */\n public function handle(): void\n {\n $teamId = $this->argument('team');\n $teams = [];\n\n if ($teamId) {\n $team = Team::idOrUuId($teamId);\n if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {\n $this->error(sprintf(\n 'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',\n $team->getName(),\n $team->getUuid()\n ));\n\n return;\n }\n if ($team) {\n $teams[] = $team;\n }\n } else {\n // Exclude HubSpot teams - handled by crm:sync-hubspot-objects\n $teams = Team::where('status', Team::STATUS_ACTIVE)\n ->whereHas(\n 'crm',\n fn ($q) => $q\n ->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)\n ->where('sync_objects', true)\n )\n ->get();\n }\n\n $this->dispatchSyncJobsForTeams($teams);\n }\n\n protected function getStaggerDelaySeconds(): float\n {\n return 2;\n }\n\n protected function getMaxDelaySeconds(): int\n {\n // Cap delay at 15 minutes (SQS max delay limit)\n // capacity of 450 teams before reach\n return 900;\n }\n\n protected function getLogPrefix(): string\n {\n return '';\n }\n\n protected function createSyncJob(Team $team): Job\n {\n return new SyncObjectsJob($team);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
7656656192700481823
|
-8678315669563599932
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Crm;
use Jiminny\Console\Commands\Command;
use Jiminny\Console\Commands\Crm\Traits\SyncObjectsCommandTrait;
use Jiminny\Jobs\Crm\SyncObjects as SyncObjectsJob;
use Jiminny\Jobs\Job;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Team;
class SyncObjects extends Command
{
use SyncObjectsCommandTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'crm:sync-objects {team?} {--sync}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync remote CRM objects.';
/**
* Execute the console command.
*/
public function handle(): void
{
$teamId = $this->argument('team');
$teams = [];
if ($teamId) {
$team = Team::idOrUuId($teamId);
if ($team && $team->crm?->provider === Configuration::PROVIDER_HUBSPOT) {
$this->error(sprintf(
'Team %s (%s) uses HubSpot. Use crm:sync-hubspot-objects instead.',
$team->getName(),
$team->getUuid()
));
return;
}
if ($team) {
$teams[] = $team;
}
} else {
// Exclude HubSpot teams - handled by crm:sync-hubspot-objects
$teams = Team::where('status', Team::STATUS_ACTIVE)
->whereHas(
'crm',
fn ($q) => $q
->where('provider', '!=', Configuration::PROVIDER_HUBSPOT)
->where('sync_objects', true)
)
->get();
}
$this->dispatchSyncJobsForTeams($teams);
}
protected function getStaggerDelaySeconds(): float
{
return 2;
}
protected function getMaxDelaySeconds(): int
{
// Cap delay at 15 minutes (SQS max delay limit)
// capacity of 450 teams before reach
return 900;
}
protected function getLogPrefix(): string
{
return '';
}
protected function createSyncJob(Team $team): Job
{
return new SyncObjectsJob($team);
}
}
Code changed:
Hide...
|
49443
|
NULL
|
NULL
|
NULL
|
|
49653
|
1766
|
51
|
2026-05-18T06:50:49.433257+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779087049433_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"pipedrive-sdk-poc, menu","depth":5,"on_screen":true,"help_text":"Git Branch: pipedrive-sdk-poc","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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
7266624862028544203
|
577591577001045378
|
visual_change
|
hybrid
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
Notion CalendarEditViewWindowHelpDaily - Platform • now100% 12Mon 18 May 9:50:49meet.google.com/agt-teir-cwt?authuser=lukas.kovalik%40jiminny.comNikolay Yankov (Presenting)+S18E3 JiminE3 Prom0 Atten:83 MCP(a Sche x• Curso x | * Cavo xCostX Jmn xQ мсP3 Pipel© S[URL_WITH_CREDENTIALS] 18 May 9:50L Al bookmarxsSteliyan GeorgievNikolay YankovNikolay VanovAneliya AngelovaLukas Kovalik4:23...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
49654
|
1768
|
32
|
2026-05-18T06:50:51.803921+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779087051803_m2.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
VIewINavicareCodeLaravelPhostormFV faVsco.js?° pip VIewINavicareCodeLaravelPhostormFV faVsco.js?° pipedrive-sdk-poc vroledeyso sonar-oroiect oroperties= test.ov<> Untitled Diagram.xmlus vetur.confio.ism. WEBHOOK FILTERING_MPLEMENTATION.mo>ib External Librariesv =° Scratches and Consolesv M Database ConsolesV AEUA console (EU]A DEAL RISKS [EU]A DI (EU]A EU [EU)v &llminny@localnostA console [jiminny@localhost]# Di [liminny@localhostlA HS local [iiminny@localhostl# SF ['iiminny@localhost]& zoho dev liminny@localhostV & PROD& console PRODI¿ console_1 [PROD14DPROD> ДOA> APAILPAIPRODSTAGING& console [STAGINGconsole " STAGNGduranus STAGINGIDally - Platrorm • now100% S2. Mon 18 May 9:50:51C ActivityController.ong=custom.loglaravel.log4 SF [jiminny@localhost]& console (STAGINGICascadeCConvertLeadActivities.ong© SyncPlanhat.phpA HS_local [jiminny@localhost]A console [PROD]© TestPipedriveOfficialSdkCommand.php X © Kernel.php• Debugging Pipedrive+0 ..# console EU© CreatePlaybookCreatedEvent.phpclass lestripeuraveurticralsakcommano excenas commanaprivate function testBasicCrudOperations(SocialAccount SsocialAccount): voidsthls->intol su. (Sexpires && $expires ‹ timeo ? ywServices+,o,cv M DatabasevAEU#consolev&jiminny@localhost# HS localA SFA PROD« consoleA STAGING# console 2 s 241 msy, Noshorc) IntearationApp/Service.php(C) LeadConverted.php© CreateSelfCoachedEvent.phpC) CreateCommentedEvent.phpC) CreateSmsSentEvent.ohpC) PlanhatActivityListener.pho(C)AskAnvthinaPromotService.ohoC) AutomatedReportsCommand.ohophp api y2.ohoC) RequestGenerateReport.Job.oho/// Test with the DB token directlySthis->info( string: "\n--- Testing with DB token ---")Scont1a = new confiqurationorsconf10->setaccesstoken(Srawdbloken):(C) AutomatedReportkesulconp(C) AutomatedRenort nhn X// Test 2a: Get DealsAccept Rejectclass AutomatedRenont extends ModellBAY11141143// Test 2a: Get Deals SummarySthis->tes+Getleals(Sconfioalreturn Sthis->getType === AutomatedReportsService::TYPE_ASK_JIMINNY:public function isExpired: boolf...}- 139140141// Test 2b: Get PersonsSthis-stes+Ge+Pencons(Sconfia)public function canExecute@: boolf...;public function getActivitvSearchido: ?int-...=// Test 2c: Get ActivitiesSthis->testGetActivities($config)} catch (Exception $e) {$this->recordResult( testName: 'Basic CRUD Operations'.success: false, $e->getpublic function getAskAnvthingPromptIdO: 2intf...}Sthis->newLineO172nublic function aetExoiresAtO: 2Carhons...no usaget 1 of 10 edits +Accept File & X Reject File 0%€+ 2 of 2 files →private tunction testlurrentsystemApproachSoc1alAccount ssoc1aLAccount: vO1d ...OutputGid jiminny.social_accounts xdid w 1 rowv1116241.19555731|docker exec docker lamp 1 php artisan jiminny:test-pipedrive-official-sdk 19ot a onso e oy anes/mr/este pert veotiCla Sor omane, рp 206tr ippoirieant soкnew PersonsApa null, scontag);A official Sok uses getAllPersons methodandt, . 101)., caran tesde on sut e cet ersone) rorr e per old n telopscohes, solbpecel):ghmirnys Conso1el Commandis/CrmtPestP2pedrtveott1c2aCSckcomnandt: 1testGetPersons (Object(Pipedrivel versions| vz1 Configuration))grer contednd te oteo es ea esre re te 41 u Seakeoe- oha e tes Crudopertions oi e (Jeimy Voe 3 5ocso 1ccoune))Thouahts• TestPipedriveOfficialSdkCommand.php+8-10l2) Reject allAccept allAsk anvthina (*4L)+ & codeSWE.1Ge idsociable 1dW provider_user_id! provider_user_token(• provider refresh tokenI expiresM refresh token expiresU oroviden!O state1 auth_scopeI retry afterI created atundated atV1U:AQLBAHS -L2 TNK2yuuuaLq142hWb9crUNKTpk4=-109r5nXap_6AE0hDhDQVa1nvWCHEVnpVSEAAAAFB8BqKahk:G9WOBBwagbzBtAgEAMGgGCSqGS1D3D0EHATAeB9LghkgBZ0MEAS4wEQQMnG8KNCZLI5EnLRPXA9EQgDsGP1CKfI5MU/0eL36BtM5FCQa56mYUy24_AAoqdh2y;V=ka6egLqSOinp-5G4JE/frJURMV8VIW-FY149HGHSO8DKUSBq30JM398MH84Cb10mzCrXaVo2GbZEd6nn5034113:[TELEGRAM_TOKEN]b2bfc1779088398<null)oinedriveconnectedibase.deals:full.activities:full.contacts:full.search:read2023-09-08 09:44:292026-05-15 15:44•311 row retrieved ctartina from 1 in 740 mc (eyecution: 186 mc fetchina- 554 mclW Windsurf Toamc137:1 (35 chars, 1 line break) UTF-8 # 4 spaces...
|
NULL
|
6348686074786956141
|
NULL
|
visual_change
|
ocr
|
NULL
|
VIewINavicareCodeLaravelPhostormFV faVsco.js?° pip VIewINavicareCodeLaravelPhostormFV faVsco.js?° pipedrive-sdk-poc vroledeyso sonar-oroiect oroperties= test.ov<> Untitled Diagram.xmlus vetur.confio.ism. WEBHOOK FILTERING_MPLEMENTATION.mo>ib External Librariesv =° Scratches and Consolesv M Database ConsolesV AEUA console (EU]A DEAL RISKS [EU]A DI (EU]A EU [EU)v &llminny@localnostA console [jiminny@localhost]# Di [liminny@localhostlA HS local [iiminny@localhostl# SF ['iiminny@localhost]& zoho dev liminny@localhostV & PROD& console PRODI¿ console_1 [PROD14DPROD> ДOA> APAILPAIPRODSTAGING& console [STAGINGconsole " STAGNGduranus STAGINGIDally - Platrorm • now100% S2. Mon 18 May 9:50:51C ActivityController.ong=custom.loglaravel.log4 SF [jiminny@localhost]& console (STAGINGICascadeCConvertLeadActivities.ong© SyncPlanhat.phpA HS_local [jiminny@localhost]A console [PROD]© TestPipedriveOfficialSdkCommand.php X © Kernel.php• Debugging Pipedrive+0 ..# console EU© CreatePlaybookCreatedEvent.phpclass lestripeuraveurticralsakcommano excenas commanaprivate function testBasicCrudOperations(SocialAccount SsocialAccount): voidsthls->intol su. (Sexpires && $expires ‹ timeo ? ywServices+,o,cv M DatabasevAEU#consolev&jiminny@localhost# HS localA SFA PROD« consoleA STAGING# console 2 s 241 msy, Noshorc) IntearationApp/Service.php(C) LeadConverted.php© CreateSelfCoachedEvent.phpC) CreateCommentedEvent.phpC) CreateSmsSentEvent.ohpC) PlanhatActivityListener.pho(C)AskAnvthinaPromotService.ohoC) AutomatedReportsCommand.ohophp api y2.ohoC) RequestGenerateReport.Job.oho/// Test with the DB token directlySthis->info( string: "\n--- Testing with DB token ---")Scont1a = new confiqurationorsconf10->setaccesstoken(Srawdbloken):(C) AutomatedReportkesulconp(C) AutomatedRenort nhn X// Test 2a: Get DealsAccept Rejectclass AutomatedRenont extends ModellBAY11141143// Test 2a: Get Deals SummarySthis->tes+Getleals(Sconfioalreturn Sthis->getType === AutomatedReportsService::TYPE_ASK_JIMINNY:public function isExpired: boolf...}- 139140141// Test 2b: Get PersonsSthis-stes+Ge+Pencons(Sconfia)public function canExecute@: boolf...;public function getActivitvSearchido: ?int-...=// Test 2c: Get ActivitiesSthis->testGetActivities($config)} catch (Exception $e) {$this->recordResult( testName: 'Basic CRUD Operations'.success: false, $e->getpublic function getAskAnvthingPromptIdO: 2intf...}Sthis->newLineO172nublic function aetExoiresAtO: 2Carhons...no usaget 1 of 10 edits +Accept File & X Reject File 0%€+ 2 of 2 files →private tunction testlurrentsystemApproachSoc1alAccount ssoc1aLAccount: vO1d ...OutputGid jiminny.social_accounts xdid w 1 rowv1116241.19555731|docker exec docker lamp 1 php artisan jiminny:test-pipedrive-official-sdk 19ot a onso e oy anes/mr/este pert veotiCla Sor omane, рp 206tr ippoirieant soкnew PersonsApa null, scontag);A official Sok uses getAllPersons methodandt, . 101)., caran tesde on sut e cet ersone) rorr e per old n telopscohes, solbpecel):ghmirnys Conso1el Commandis/CrmtPestP2pedrtveott1c2aCSckcomnandt: 1testGetPersons (Object(Pipedrivel versions| vz1 Configuration))grer contednd te oteo es ea esre re te 41 u Seakeoe- oha e tes Crudopertions oi e (Jeimy Voe 3 5ocso 1ccoune))Thouahts• TestPipedriveOfficialSdkCommand.php+8-10l2) Reject allAccept allAsk anvthina (*4L)+ & codeSWE.1Ge idsociable 1dW provider_user_id! provider_user_token(• provider refresh tokenI expiresM refresh token expiresU oroviden!O state1 auth_scopeI retry afterI created atundated atV1U:AQLBAHS -L2 TNK2yuuuaLq142hWb9crUNKTpk4=-109r5nXap_6AE0hDhDQVa1nvWCHEVnpVSEAAAAFB8BqKahk:G9WOBBwagbzBtAgEAMGgGCSqGS1D3D0EHATAeB9LghkgBZ0MEAS4wEQQMnG8KNCZLI5EnLRPXA9EQgDsGP1CKfI5MU/0eL36BtM5FCQa56mYUy24_AAoqdh2y;V=ka6egLqSOinp-5G4JE/frJURMV8VIW-FY149HGHSO8DKUSBq30JM398MH84Cb10mzCrXaVo2GbZEd6nn5034113:[TELEGRAM_TOKEN]b2bfc1779088398<null)oinedriveconnectedibase.deals:full.activities:full.contacts:full.search:read2023-09-08 09:44:292026-05-15 15:44•311 row retrieved ctartina from 1 in 740 mc (eyecution: 186 mc fetchina- 554 mclW Windsurf Toamc137:1 (35 chars, 1 line break) UTF-8 # 4 spaces...
|
49652
|
NULL
|
NULL
|
NULL
|
|
49655
|
1766
|
52
|
2026-05-18T06:51:02.703949+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779087062703_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
1
6...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"pipedrive-sdk-poc, menu","depth":5,"on_screen":true,"help_text":"Git Branch: pipedrive-sdk-poc","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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"role_description":"text"}]...
|
-233160542334707940
|
-8132368178556458034
|
visual_change
|
hybrid
|
NULL
|
Project: faVsco.js, menu
pipedrive-sdk-poc, menu
S Project: faVsco.js, menu
pipedrive-sdk-poc, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
6
1
6
Notion CalendarEditViewWindowHelpDaily - Platform • now100% L28•Mon 18 May 9:51:02meet.google.com/agt-teir-cwt?authuser=lukas.kovalik%40jiminny.comNikolay Yankov (Presenting)+18E3 Jimin:E3 Prom:Attent X83 мCP - x(a Scher x• Curso x |* Cauc xCosti XoUmn X@ MoP[URL_WITH_CREDENTIALS] Georgiev®Nikolay YankovNikolay vanovSUL SЛO5Aneliya AngelovaLukas Kovalik:4:36...
|
49653
|
NULL
|
NULL
|
NULL
|
|
49656
|
1766
|
53
|
2026-05-18T06:51:05.718460+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779087065718_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Notion CalendarEditViewWindowHelpDaily - Platform Notion CalendarEditViewWindowHelpDaily - Platform • now100% 12Mon 18 May 9:51:05meet.google.com/agt-teir-cwt?authuser=lukas.kovalik%40jiminny.comNikolay Yankov (Presenting)+i8M Inbo|E3 JmicE3 Prom0 Ater xE3 MCP- x( Sche x© Curs x* Owo x• cost x Jmin x Q MCP xo Pipe x aть xI• Calehttps:/fiminny.atlassian.net/jra/software/c/projects/JJY/boards/37?selectedissue=JY-208350 идестиDacaowyb1 cadeL Insights & Coschin…C Dw C UKPlatform Team %Q Sesron coardS009 J1-207591 1 J7-4985STAOTOKONotify the user d adeined but is used in AJ...XI maarEsBaciiogO.Jy2067e3 9as =Notity the user it a $S isdeieted but is used in AJReportBacidogД Jy 20E15Upgrade DE ibranas - MayLAA DATEN MIKEDBасто9E Jy-198581. =QsuggestonsAUTO DITECTED ASTIVITY TYNGBacidagД 3y-20010Note: here you can find example prompts for testing - ® Prompts and SultsSubtasksWork% JY-20859 tool for get_deal|%, JY-20860 tool for search.deais% JY-20861 tool for ist_deal,options4a JY-20862 tooi for get deal activities% JY-20863 rate limits%, JY-20884 add toois to tools list"tJY*20805 maniusitesung% JY-20874 describe toois data structuresLinked work itemsAoelinsoowrendcConfluence contentPrompts and SkilsPrlocityStor..Assignes= Medium8 Unassign...= Medium% Unassign..= Medium% Unassign...= MediumUnassign...= MediumUnassign..= Medium|8Unassign...= Medium= MediumUnassign..Nikclay Y....O Crore85031 0X DL-•+SunaCATORSYREADY FORDEVSREADY FORDEVSREADY FORDEVSREADY FOR DEVSREADY FOR DEVSIn DevI Improve StoryDetailsAssigneeNkolsy IvanouAssign to me& Gatya DintrovaDevelopmentQ Open with VS Code1 branch3 commits1 pull request3 bullds3 aays ag0WENOLDLomoononPlatformAdd optionsLatetsstory poit esonatAJ Panorama for CallScoring m o0Qл-2л0 08(Deadina 25 Mayl Migratedepnicared Gemin 3.1 FlashQ.J1-220800 3 [1 *** =Setup test coverage forL1 Ai bockmarxsSteliyan GeorgievNikolay YankovNikolay vanovSSUE SNOiSAneliya Angelova9:51 AM | Daily - Platform50 1-20172Sidekick SMS issueDepioyedТЕ Рнеи11 »*=→•..Lukas Kovalik4:39...
|
NULL
|
-2551918996894445971
|
NULL
|
visual_change
|
ocr
|
NULL
|
Notion CalendarEditViewWindowHelpDaily - Platform Notion CalendarEditViewWindowHelpDaily - Platform • now100% 12Mon 18 May 9:51:05meet.google.com/agt-teir-cwt?authuser=lukas.kovalik%40jiminny.comNikolay Yankov (Presenting)+i8M Inbo|E3 JmicE3 Prom0 Ater xE3 MCP- x( Sche x© Curs x* Owo x• cost x Jmin x Q MCP xo Pipe x aть xI• Calehttps:/fiminny.atlassian.net/jra/software/c/projects/JJY/boards/37?selectedissue=JY-208350 идестиDacaowyb1 cadeL Insights & Coschin…C Dw C UKPlatform Team %Q Sesron coardS009 J1-207591 1 J7-4985STAOTOKONotify the user d adeined but is used in AJ...XI maarEsBaciiogO.Jy2067e3 9as =Notity the user it a $S isdeieted but is used in AJReportBacidogД Jy 20E15Upgrade DE ibranas - MayLAA DATEN MIKEDBасто9E Jy-198581. =QsuggestonsAUTO DITECTED ASTIVITY TYNGBacidagД 3y-20010Note: here you can find example prompts for testing - ® Prompts and SultsSubtasksWork% JY-20859 tool for get_deal|%, JY-20860 tool for search.deais% JY-20861 tool for ist_deal,options4a JY-20862 tooi for get deal activities% JY-20863 rate limits%, JY-20884 add toois to tools list"tJY*20805 maniusitesung% JY-20874 describe toois data structuresLinked work itemsAoelinsoowrendcConfluence contentPrompts and SkilsPrlocityStor..Assignes= Medium8 Unassign...= Medium% Unassign..= Medium% Unassign...= MediumUnassign...= MediumUnassign..= Medium|8Unassign...= Medium= MediumUnassign..Nikclay Y....O Crore85031 0X DL-•+SunaCATORSYREADY FORDEVSREADY FORDEVSREADY FORDEVSREADY FOR DEVSREADY FOR DEVSIn DevI Improve StoryDetailsAssigneeNkolsy IvanouAssign to me& Gatya DintrovaDevelopmentQ Open with VS Code1 branch3 commits1 pull request3 bullds3 aays ag0WENOLDLomoononPlatformAdd optionsLatetsstory poit esonatAJ Panorama for CallScoring m o0Qл-2л0 08(Deadina 25 Mayl Migratedepnicared Gemin 3.1 FlashQ.J1-220800 3 [1 *** =Setup test coverage forL1 Ai bockmarxsSteliyan GeorgievNikolay YankovNikolay vanovSSUE SNOiSAneliya Angelova9:51 AM | Daily - Platform50 1-20172Sidekick SMS issueDepioyedТЕ Рнеи11 »*=→•..Lukas Kovalik4:39...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
49657
|
1766
|
54
|
2026-05-18T06:51:08.751898+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779087068751_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Notion CalendarEditViewWindowHelpDaily - Platform Notion CalendarEditViewWindowHelpDaily - Platform • now100% L2meet.google.com/agt-teir-cwt?authuser=lukas.kovalik%40jiminny.comNikolay Yankov (Presenting)+M Inbo|E3 JmicE3 Prom0 Ater xx MoP: X( Sche x© Curs x* Owo x• Cost x Jmi x Q MP xo Pipe x® Snh xO Calehttps:/fiminny.atlassian.net/jra/software/c/projects/JJY/boards/37?selectedissue=JY-208350 идестиE caao 0 e1 cadeL Insights & Coschin…C Dw C UKPlatform Team %Q Sesron coardS009 J1-207591 1 J7-4985STAOTOKONotify the user d adeined but is used in AJ...XI maarEsBaciiogO.Jy2067e3 9as =Notity the user it a $S isdeieted but is used in AJReportBacidogД Jy 20E15Upgrade DE ibranas - MayLAA DATEN MIKEDBасто9E Jy-198581. =QsuggestonsAUTO DITECTED ASTTVTTY TYЛOBacidagД 3y-20010Note: here you can find example prompts for testing - ® Prompts and SultsSubtasksWork% JY-20859 tool for get_deal|%, JY-20860 tool for search.deais% JY-20861 tool for ist_deal,options4a JY-20862 tooi for get deal activities% JY-20863 rate limits%, JY-20884 add toois to tools list"tJY*20805 maniusitesung% JY-20874 describe toois data structuresLinked work itemsAoelinsoowrendcConfluence contentPrompts and SkilsPrlocityStor..Assignes= Medium8 Unassign...= Medium% Unassign..= Medium% Unassign...= MediumUnassign...= MediumUnassign..= Medium|8Unassign...= Medium= MediumUnassign..Nikclay Y....O Crore85037 07 DL-•+SunaCATORSYREADY FORDEVSREADY FORDEVSREADY FORDEVSREADY FOR DEVSREADY FOR DEVSIn DevI Improve StoryDetailsAssigneeNkolsy IvanouAssign to me& Gatya DintrovaDevelopmentQ Open with VS Code1 branch3 commits1 pull request3 bullds3 aays ag0MEROEDLomoononPlatformAdd optionsLatetsstory poitesoratAJ Panorama for CallScoring m o0Qл-2л0 08(Deadina 25 Mayl Migratedepnicared Gemin 3.1 FlashQ.J1-220800 3 [1 *** =Setup test coverage fortọn 18 May 9:51L1 Ai bockmarxsSteliyan GeorgievNikolay Vanov"SSUL SOgIS9:51 AM | Daily - Platform50 1-20172Sidekick SMS issueDepioyedТЕ Рнеи11 »*=→•..Lukas Kovalik•4:42Mon 18 May 9:51:08Nikolay YankovAneliya Angelova...
|
NULL
|
2428079084665594274
|
NULL
|
visual_change
|
ocr
|
NULL
|
Notion CalendarEditViewWindowHelpDaily - Platform Notion CalendarEditViewWindowHelpDaily - Platform • now100% L2meet.google.com/agt-teir-cwt?authuser=lukas.kovalik%40jiminny.comNikolay Yankov (Presenting)+M Inbo|E3 JmicE3 Prom0 Ater xx MoP: X( Sche x© Curs x* Owo x• Cost x Jmi x Q MP xo Pipe x® Snh xO Calehttps:/fiminny.atlassian.net/jra/software/c/projects/JJY/boards/37?selectedissue=JY-208350 идестиE caao 0 e1 cadeL Insights & Coschin…C Dw C UKPlatform Team %Q Sesron coardS009 J1-207591 1 J7-4985STAOTOKONotify the user d adeined but is used in AJ...XI maarEsBaciiogO.Jy2067e3 9as =Notity the user it a $S isdeieted but is used in AJReportBacidogД Jy 20E15Upgrade DE ibranas - MayLAA DATEN MIKEDBасто9E Jy-198581. =QsuggestonsAUTO DITECTED ASTTVTTY TYЛOBacidagД 3y-20010Note: here you can find example prompts for testing - ® Prompts and SultsSubtasksWork% JY-20859 tool for get_deal|%, JY-20860 tool for search.deais% JY-20861 tool for ist_deal,options4a JY-20862 tooi for get deal activities% JY-20863 rate limits%, JY-20884 add toois to tools list"tJY*20805 maniusitesung% JY-20874 describe toois data structuresLinked work itemsAoelinsoowrendcConfluence contentPrompts and SkilsPrlocityStor..Assignes= Medium8 Unassign...= Medium% Unassign..= Medium% Unassign...= MediumUnassign...= MediumUnassign..= Medium|8Unassign...= Medium= MediumUnassign..Nikclay Y....O Crore85037 07 DL-•+SunaCATORSYREADY FORDEVSREADY FORDEVSREADY FORDEVSREADY FOR DEVSREADY FOR DEVSIn DevI Improve StoryDetailsAssigneeNkolsy IvanouAssign to me& Gatya DintrovaDevelopmentQ Open with VS Code1 branch3 commits1 pull request3 bullds3 aays ag0MEROEDLomoononPlatformAdd optionsLatetsstory poitesoratAJ Panorama for CallScoring m o0Qл-2л0 08(Deadina 25 Mayl Migratedepnicared Gemin 3.1 FlashQ.J1-220800 3 [1 *** =Setup test coverage fortọn 18 May 9:51L1 Ai bockmarxsSteliyan GeorgievNikolay Vanov"SSUL SOgIS9:51 AM | Daily - Platform50 1-20172Sidekick SMS issueDepioyedТЕ Рнеи11 »*=→•..Lukas Kovalik•4:42Mon 18 May 9:51:08Nikolay YankovAneliya Angelova...
|
49656
|
NULL
|
NULL
|
NULL
|
|
49658
|
1766
|
55
|
2026-05-18T06:51:11.780468+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779087071780_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Notion CalendarEditViewWindowHelpDaily - Platform Notion CalendarEditViewWindowHelpDaily - Platform • now100% 12Mon 18 May 9:51:11meet.google.com/agt-teir-cwt?authuser=lukas.kovalik%40jiminny.comNikolay Yankov (Presenting)+M Inbo|E3 3minE3 Prom0 Ate xE3 MCP- x( Sche x© Curx x* Owo x• cost x Jmin x Q MCP xo Pipe xhttps:/fiminny.atlassian.net/jra/software/c/projects/JJY/boards/37?selectedissue=JY-208350 идестиDacaooyb1 cabeO Crore85037 07 DLLa Insights & Coschin…C Dw CukPlatform Team %Q Sesron coardS009 J1-207591 1 J7-4983STAOTOKONotify the user d adeined but is used in AJ...XI maarEsBaciiogO.Jy2067e3 sas =Notity che user it a 5S ideieted but is used in AJReportBacidogД Jy 20E15Upgrade DE ibranas - MayLAA DATER MIKEDBасто9E Jy-19a581. =QsuggestonsAUTO DITECTED AOTTVTTY TУЛОBacidagД 3y-20010Note: here you can find example prompts for testing - ® Prompts and SkultsSubtasksWork% JY-20859 tool for get_deal|%, JY-20860 tool for search.deais% JY-20861 tool for ist_deal,options4a JY-20862 tooi for get deal activities% JY-20863 rate limits%, JY-20884 add toois to tools list"t JY*20805 maniusitesung% JY-20874 describe toois data structuresLinked work itemsAoelinsoowrendcConfluence contentPrompts and Skils-•+PrlocityStor..AssignesSuna= Medium8 Unassign...CATORSY= Medium% Unassign..= Medium% Unassign...READY FOR DEVS= MediumUnassign...READY FORDEVS= MediumUnassign..READY FORDEVS= Medium|8Unassign...READY FOR DEVS= Medium= MediumUnassign..Nikclay Y....READY FOR DEVS@ 2In DevI Improve StoryDetailsAssigneeNkolsy IvanouAssign to meReporter& Gatya DintrovaDevelopmentQ Open with VS Code1 branch3 commits1 pull request3 bullds3 aays ag0ENGLDLompononPlatformAdd optionsLatetsstory poit esonat• Caletọn 18 May 9:51L1 Ai bockmarxsSteliyan Georgiev.Nikolay YankovAJ Panorama for CallScoring m o0Qл-2л0 08(Deadina 25 Mayl Migratedepnicared Gemin 3.1 FlashQ.J1-22800 3 [1 *** =Sesup test coverage forNikolay vanovAneliya Angelova9:51 AM | Daily - Platform50 11-20172Sidekick SMS issueDepioyedТЕ Рнеи11 »*=→•..Lukas Kovalik4:45...
|
NULL
|
-7195903002697802741
|
NULL
|
visual_change
|
ocr
|
NULL
|
Notion CalendarEditViewWindowHelpDaily - Platform Notion CalendarEditViewWindowHelpDaily - Platform • now100% 12Mon 18 May 9:51:11meet.google.com/agt-teir-cwt?authuser=lukas.kovalik%40jiminny.comNikolay Yankov (Presenting)+M Inbo|E3 3minE3 Prom0 Ate xE3 MCP- x( Sche x© Curx x* Owo x• cost x Jmin x Q MCP xo Pipe xhttps:/fiminny.atlassian.net/jra/software/c/projects/JJY/boards/37?selectedissue=JY-208350 идестиDacaooyb1 cabeO Crore85037 07 DLLa Insights & Coschin…C Dw CukPlatform Team %Q Sesron coardS009 J1-207591 1 J7-4983STAOTOKONotify the user d adeined but is used in AJ...XI maarEsBaciiogO.Jy2067e3 sas =Notity che user it a 5S ideieted but is used in AJReportBacidogД Jy 20E15Upgrade DE ibranas - MayLAA DATER MIKEDBасто9E Jy-19a581. =QsuggestonsAUTO DITECTED AOTTVTTY TУЛОBacidagД 3y-20010Note: here you can find example prompts for testing - ® Prompts and SkultsSubtasksWork% JY-20859 tool for get_deal|%, JY-20860 tool for search.deais% JY-20861 tool for ist_deal,options4a JY-20862 tooi for get deal activities% JY-20863 rate limits%, JY-20884 add toois to tools list"t JY*20805 maniusitesung% JY-20874 describe toois data structuresLinked work itemsAoelinsoowrendcConfluence contentPrompts and Skils-•+PrlocityStor..AssignesSuna= Medium8 Unassign...CATORSY= Medium% Unassign..= Medium% Unassign...READY FOR DEVS= MediumUnassign...READY FORDEVS= MediumUnassign..READY FORDEVS= Medium|8Unassign...READY FOR DEVS= Medium= MediumUnassign..Nikclay Y....READY FOR DEVS@ 2In DevI Improve StoryDetailsAssigneeNkolsy IvanouAssign to meReporter& Gatya DintrovaDevelopmentQ Open with VS Code1 branch3 commits1 pull request3 bullds3 aays ag0ENGLDLompononPlatformAdd optionsLatetsstory poit esonat• Caletọn 18 May 9:51L1 Ai bockmarxsSteliyan Georgiev.Nikolay YankovAJ Panorama for CallScoring m o0Qл-2л0 08(Deadina 25 Mayl Migratedepnicared Gemin 3.1 FlashQ.J1-22800 3 [1 *** =Sesup test coverage forNikolay vanovAneliya Angelova9:51 AM | Daily - Platform50 11-20172Sidekick SMS issueDepioyedТЕ Рнеи11 »*=→•..Lukas Kovalik4:45...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
49659
|
1768
|
33
|
2026-05-18T06:51:13.050260+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779087073050_m2.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhostormProiectVIewINavicareCodeLaravelKeractor• D PhostormProiectVIewINavicareCodeLaravelKeractor• Dally - Platrorm • now100% C• • Mon 18 May 9:51:12FV faVsco.js?° pipedrive-sdk-poc vAskJiminnyReportActivityServiceTest vC ActivityController.ong=custom.loglaravel.log4 SF [jiminny@localhost]& console (STAGINGI© SyncProfileMetadata.php©syncleammetadata.ong1estPlpeariveomricialsakcommand.orCConvertLeadActivities.ongOPurgeLookupcache.pnp© SyncPlanhat.phpA HS_local [jiminny@localhost]A console [PROD]© TestPipedriveOfficialSdkCommand.php X © Kernel.php• Debugging Pipedriv+0 ..# console EUSapiinstance = new PersonsApi(null, $config);Upoateopponunityspecrications.ono>@ Dealinsights• … Dev>C DialersD DTOSC ElasticsearchEnqagementstatsш GeckoExport• LivestreamMallboxesa MidratePlavbackithemesM Plavbooks141143D Playlists> M Postmark> M PronhetAv D Reports© AutomatedReportsCommand.php© AutomatedReportSRetentionPollcycor 161© AutomatedReportsSendCommand.pht(C) CroateMockAck.liminnvRenortResult@© DeleteReportCommand.php© GenerateMarketingReport.php© Team.php© Usage.php>07 Slack›_ TeamscreatePlaybookcreatedevent.ongclass lestripeuraveurricralsakcommano excenas commanaprivate function testBasicCrudOperations(SocialAccount SsocialAccount): voidsthls->intol su. (Sexpires && $expires ‹ timeo ? yc(r'limiti = 101)c) IntearationApp/Service.php(C) LeadConverted.php© CreateSelfCoachedEvent.php1 caten tescepe on sa) y et Personel), ro, netrtleved per soni 1n (elopseoes', selopsed) :C) CreateCommentedEvent.phpC) CreateSmsSentEvent.ohpC) PlanhatActivityListener.pho(C)AskAnvthinaPromotService.oho(C)AutomatedReportsRepositorv.ohd// Test with the DB token directlySthis->info( string: "\n--- Testing with DB token ---"):Scont1a = new confiqurationorSconfig->setAccessToken(SrawDbToken) :d: :testGetPersons(Obiect (Pioedrive versions v1 ConfiqurationC) AutomatedRenortsCommand.ohophp api y2.ohoC) RequestGenerateReport.Job.oho/eertvectaisda SdKCod.phd: :testBasicCrudOperations (Object(Jiminny (Mode15|SocialAccount))(C) AutomatedReportkesulconp(C) AutomatedRenort nhn X// Test 2a: Get DealsAccept Rejectclass AutomatedRenont extends Mode]li46 y1Y6AY137// Test 2a: Get Deals SummarySthis->tes+Getleals(Sconfjol*• Tereturn Sthis->getType === AutomatedReportsService::TYPE_ASK_JIMINNY:public function isExpired: boolf...}- 139140141— 142// Test 2b: Get PersonsSthis-stes+Ge+Pencons(Sconfia)O docker exec docker lamp 1 pho artisan timinnv:test-pinedrive-official-sdk 19public function canExecute@: boolf...;public function getActivitvSearchido: ?int-...=// Test 2c: Get ActivitiesSthis->testGetActivities($config)} catch (Exception $e) {$this->recordResult( testName: 'Basic CRUD Operations'.success: false, Se->getpublic function getAskAnvthingPromptIdO: 2intf...}Sthis->newLineO24les 190-12>2) Reject allAccept allAsk anvthina (*4L)nublic function aetExoiresAtO: 2Carhons...no usaget 1 of 10 edits +Accept File & X Reject File 0%€+ 2 of 2 files →+ & codeSWE.1Gprivate tunction testlurrentsystemApproachSoc1alAccount ssoc1aLAccount: vO1d ...ServicesToC exv M DatabasevAEU#consolev&jiminny@localhost# HS localA SFA PROD« consoleA STAGING# console 2 s 241 msy, NoshorOutputGid jiminny.social_accounts xdid w 1 rowvCSu| 1 + 0, sộ3e idsociable 1d1116241.19555731|W provider_user_id! provider_user_token(• provider refresh tokenI expiresM refresh token expiresU oroviden!O state1 auth_scopeI retry afterI created atV1U:AQLBAHS -L2 TNK2yuuuaLq142hWb9crUNKTpk4=-109r5nXap_6AE0hDhDQVa1nvWCHEVnpVSEAAAAFB8BqKahk:G9WOBBwagbzBtAgEAMGgGCSqGS1D3D0EHATAeB9LghkgBZ0MEAS4wEQQMnG8KNCZLI5EnLRPXA9EQgDsGP1CKfI5MU/0eL36BtM5FCQa56mYUy24_AAoqdh2y;V=ka6egLqSOinp-5G4JE/frJURMV8VIW-FY149HGHSO8DKUSBq30JM398MH84Cb10mzCrXaVo2GbZEd6nn5034113:[TELEGRAM_TOKEN]b2bfc1779088398<null)oinedriveconnectedlbase.deals:full.activities:full.contacts:full.search:readundated at2023-09-08 09:44:292026-05-15 15:44•311 row retrieved ctartina from 1 in 740 mc (eyecution: 186 mc fetchina- 554 mclW Windsurf Toams 127-1/25 charc 1 line hreak) UTF.8ih 4 spaces...
|
NULL
|
6272835950382003353
|
NULL
|
click
|
ocr
|
NULL
|
PhostormProiectVIewINavicareCodeLaravelKeractor• D PhostormProiectVIewINavicareCodeLaravelKeractor• Dally - Platrorm • now100% C• • Mon 18 May 9:51:12FV faVsco.js?° pipedrive-sdk-poc vAskJiminnyReportActivityServiceTest vC ActivityController.ong=custom.loglaravel.log4 SF [jiminny@localhost]& console (STAGINGI© SyncProfileMetadata.php©syncleammetadata.ong1estPlpeariveomricialsakcommand.orCConvertLeadActivities.ongOPurgeLookupcache.pnp© SyncPlanhat.phpA HS_local [jiminny@localhost]A console [PROD]© TestPipedriveOfficialSdkCommand.php X © Kernel.php• Debugging Pipedriv+0 ..# console EUSapiinstance = new PersonsApi(null, $config);Upoateopponunityspecrications.ono>@ Dealinsights• … Dev>C DialersD DTOSC ElasticsearchEnqagementstatsш GeckoExport• LivestreamMallboxesa MidratePlavbackithemesM Plavbooks141143D Playlists> M Postmark> M PronhetAv D Reports© AutomatedReportsCommand.php© AutomatedReportSRetentionPollcycor 161© AutomatedReportsSendCommand.pht(C) CroateMockAck.liminnvRenortResult@© DeleteReportCommand.php© GenerateMarketingReport.php© Team.php© Usage.php>07 Slack›_ TeamscreatePlaybookcreatedevent.ongclass lestripeuraveurricralsakcommano excenas commanaprivate function testBasicCrudOperations(SocialAccount SsocialAccount): voidsthls->intol su. (Sexpires && $expires ‹ timeo ? yc(r'limiti = 101)c) IntearationApp/Service.php(C) LeadConverted.php© CreateSelfCoachedEvent.php1 caten tescepe on sa) y et Personel), ro, netrtleved per soni 1n (elopseoes', selopsed) :C) CreateCommentedEvent.phpC) CreateSmsSentEvent.ohpC) PlanhatActivityListener.pho(C)AskAnvthinaPromotService.oho(C)AutomatedReportsRepositorv.ohd// Test with the DB token directlySthis->info( string: "\n--- Testing with DB token ---"):Scont1a = new confiqurationorSconfig->setAccessToken(SrawDbToken) :d: :testGetPersons(Obiect (Pioedrive versions v1 ConfiqurationC) AutomatedRenortsCommand.ohophp api y2.ohoC) RequestGenerateReport.Job.oho/eertvectaisda SdKCod.phd: :testBasicCrudOperations (Object(Jiminny (Mode15|SocialAccount))(C) AutomatedReportkesulconp(C) AutomatedRenort nhn X// Test 2a: Get DealsAccept Rejectclass AutomatedRenont extends Mode]li46 y1Y6AY137// Test 2a: Get Deals SummarySthis->tes+Getleals(Sconfjol*• Tereturn Sthis->getType === AutomatedReportsService::TYPE_ASK_JIMINNY:public function isExpired: boolf...}- 139140141— 142// Test 2b: Get PersonsSthis-stes+Ge+Pencons(Sconfia)O docker exec docker lamp 1 pho artisan timinnv:test-pinedrive-official-sdk 19public function canExecute@: boolf...;public function getActivitvSearchido: ?int-...=// Test 2c: Get ActivitiesSthis->testGetActivities($config)} catch (Exception $e) {$this->recordResult( testName: 'Basic CRUD Operations'.success: false, Se->getpublic function getAskAnvthingPromptIdO: 2intf...}Sthis->newLineO24les 190-12>2) Reject allAccept allAsk anvthina (*4L)nublic function aetExoiresAtO: 2Carhons...no usaget 1 of 10 edits +Accept File & X Reject File 0%€+ 2 of 2 files →+ & codeSWE.1Gprivate tunction testlurrentsystemApproachSoc1alAccount ssoc1aLAccount: vO1d ...ServicesToC exv M DatabasevAEU#consolev&jiminny@localhost# HS localA SFA PROD« consoleA STAGING# console 2 s 241 msy, NoshorOutputGid jiminny.social_accounts xdid w 1 rowvCSu| 1 + 0, sộ3e idsociable 1d1116241.19555731|W provider_user_id! provider_user_token(• provider refresh tokenI expiresM refresh token expiresU oroviden!O state1 auth_scopeI retry afterI created atV1U:AQLBAHS -L2 TNK2yuuuaLq142hWb9crUNKTpk4=-109r5nXap_6AE0hDhDQVa1nvWCHEVnpVSEAAAAFB8BqKahk:G9WOBBwagbzBtAgEAMGgGCSqGS1D3D0EHATAeB9LghkgBZ0MEAS4wEQQMnG8KNCZLI5EnLRPXA9EQgDsGP1CKfI5MU/0eL36BtM5FCQa56mYUy24_AAoqdh2y;V=ka6egLqSOinp-5G4JE/frJURMV8VIW-FY149HGHSO8DKUSBq30JM398MH84Cb10mzCrXaVo2GbZEd6nn5034113:[TELEGRAM_TOKEN]b2bfc1779088398<null)oinedriveconnectedlbase.deals:full.activities:full.contacts:full.search:readundated at2023-09-08 09:44:292026-05-15 15:44•311 row retrieved ctartina from 1 in 740 mc (eyecution: 186 mc fetchina- 554 mclW Windsurf Toams 127-1/25 charc 1 line hreak) UTF.8ih 4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
49660
|
1766
|
56
|
2026-05-18T06:51:14.870859+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779087074870_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Notion CalendarEditViewWindowHelpDaily - Platform Notion CalendarEditViewWindowHelpDaily - Platform • now100% 12Mon 18 May 9:51:14meet.google.com/agt-teir-cwt?authuser=lukas.kovalik%40jiminny.comNikolay Yankov (Presenting)+18M InboxE3 JiminE3 Prom0 Atten:E3 MCP - X[Q ScheX0) Curso X * Cauo X Cost X 0Jmr x1@ Moo Poe X→ Jmrhttps:/fiminny.atlassian.net/jra/software/c/projects/JJY/boards/37?selectedissue=JY-20835Platform Team 8.Q Search board-81MADTOKONotify the user d adeleted but is used in AJ…..AJ REPORTSBacklogД Jy-200763 .* =0Notify the user it a SS isdeleted but is used in AJReportAJREPORTSBackiogR JY-2061525 а000 =Upgrade BE ibraries - MayMAINTENANCSE JY-199581 •=©Amprove Acuvity lypesuggestionsAUTO-DETEGTED ASTIVITY TYPBacklogД Jy-204702 as=C Projectsu0 Daaosd00e caudeASoozy 2X D Lasnsigits & CoachnC Dw Cux9 J-2W758 1 J7-4085SNote: here you can find example prompts for testing - ® Prompts and Sklls~ SubtasksWork% JY-20859 tool for get.deal% JY-20860 tool for search,deais% JY-20861 tool for Iisf_deal_options* JY-20862 tool for get.deal activities9, JY-20884 add tools to tools, lisgl"o JY-20805 manualtesung% JY-20874 describe tools data structuresLinked work itemsAoainxodweneConfluence contentPrompts and SkillsPriorityStor...Assignee= Medium8 Unassign...KATURUSY= MediumUnassign...KAURWY= Medium% Unassign...READY FOR DEVS= Medium= Medium= Medium|Unassign...8 Unassion...8Unassign...READY FOR DEVSREADY FOR DEVSREADY FOR DEV= MediumUnassign.READY FOR DEV= MediumNxolay T.@ 2In DevI Improve Story~ DetailsAssignee• Nkclay IvanouAssign to me2 Galya DimitrovaDevelopmentQ Open with VS Code1 branch3 commits1 pull request3 bullds3 days agoWeXolDuomponentPlatformSub-ProductAdd optionsLabetsStory point estimate© Calertọn 18 May 9:51L1 Al BookmarxsSteliyan GeorgievNikolay YankovAJ Panorama for CallScoring in ODAUTOMATED AT SCORINGR Jy-20301 05(Deadiine 25 May) Migratedepricated Gemini 3.1 FlashDeployedД л7-20880 0 0 ***=Setup test coverage forProphet in SonarВ4Р-T9TЕ Jy-202720 •=0=Sidekick SMS issueDeployed(. P7-20091n • = @Nikolay lIvanovAneliya Angelova9:51 AM | Daily - Platform...Lukas Kovalik4:48...
|
NULL
|
413501360037312494
|
NULL
|
visual_change
|
ocr
|
NULL
|
Notion CalendarEditViewWindowHelpDaily - Platform Notion CalendarEditViewWindowHelpDaily - Platform • now100% 12Mon 18 May 9:51:14meet.google.com/agt-teir-cwt?authuser=lukas.kovalik%40jiminny.comNikolay Yankov (Presenting)+18M InboxE3 JiminE3 Prom0 Atten:E3 MCP - X[Q ScheX0) Curso X * Cauo X Cost X 0Jmr x1@ Moo Poe X→ Jmrhttps:/fiminny.atlassian.net/jra/software/c/projects/JJY/boards/37?selectedissue=JY-20835Platform Team 8.Q Search board-81MADTOKONotify the user d adeleted but is used in AJ…..AJ REPORTSBacklogД Jy-200763 .* =0Notify the user it a SS isdeleted but is used in AJReportAJREPORTSBackiogR JY-2061525 а000 =Upgrade BE ibraries - MayMAINTENANCSE JY-199581 •=©Amprove Acuvity lypesuggestionsAUTO-DETEGTED ASTIVITY TYPBacklogД Jy-204702 as=C Projectsu0 Daaosd00e caudeASoozy 2X D Lasnsigits & CoachnC Dw Cux9 J-2W758 1 J7-4085SNote: here you can find example prompts for testing - ® Prompts and Sklls~ SubtasksWork% JY-20859 tool for get.deal% JY-20860 tool for search,deais% JY-20861 tool for Iisf_deal_options* JY-20862 tool for get.deal activities9, JY-20884 add tools to tools, lisgl"o JY-20805 manualtesung% JY-20874 describe tools data structuresLinked work itemsAoainxodweneConfluence contentPrompts and SkillsPriorityStor...Assignee= Medium8 Unassign...KATURUSY= MediumUnassign...KAURWY= Medium% Unassign...READY FOR DEVS= Medium= Medium= Medium|Unassign...8 Unassion...8Unassign...READY FOR DEVSREADY FOR DEVSREADY FOR DEV= MediumUnassign.READY FOR DEV= MediumNxolay T.@ 2In DevI Improve Story~ DetailsAssignee• Nkclay IvanouAssign to me2 Galya DimitrovaDevelopmentQ Open with VS Code1 branch3 commits1 pull request3 bullds3 days agoWeXolDuomponentPlatformSub-ProductAdd optionsLabetsStory point estimate© Calertọn 18 May 9:51L1 Al BookmarxsSteliyan GeorgievNikolay YankovAJ Panorama for CallScoring in ODAUTOMATED AT SCORINGR Jy-20301 05(Deadiine 25 May) Migratedepricated Gemini 3.1 FlashDeployedД л7-20880 0 0 ***=Setup test coverage forProphet in SonarВ4Р-T9TЕ Jy-202720 •=0=Sidekick SMS issueDeployed(. P7-20091n • = @Nikolay lIvanovAneliya Angelova9:51 AM | Daily - Platform...Lukas Kovalik4:48...
|
49658
|
NULL
|
NULL
|
NULL
|
|
49661
|
1766
|
57
|
2026-05-18T06:51:19.136592+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779087079136_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Notion CalendarEditViewWindowHelpmeet.google.com/a Notion CalendarEditViewWindowHelpmeet.google.com/agt-teir-cwt?authuser=lukas.kovalik%40jiminny.com+18Nikolay Yankov (Presenting)Flex JimE3 Prom0 AtteniE3 MCP(a Sche xo Curso X * Cauo XCostXhttps://fiminny.atlassian.net/jra/software/c/projectsJJY/boards/37?selectedissue=JY-2083523 SSHE Datadog* Claudel3 CircieClA SentryPlatform Team %.Q Search boardS00P J-20S8 W JT-298SSMAOTOKONote: here you can find example prompts for testing - ® Prompts and SkllsNotify the user d a~ Subtasksdeleted but is used in AJ…..AJ REPORTSBacklogД Jy-200763 .* =0WorkPriorityStor...Assignee% JY-20859 tool for get.deal= Medium8 Unassign...KATDURUEYNotify the user if a SS isdeieted but is used in AJReportAJREPORTSBacklogA JY-206152.5 a4+0 =% JY-20860 tool for search,deals= MediumUnassign...% JY-20861 tool for lisk_deal_options= Medium% Unassign...READY FOR DEVS* JY-20862 tool for get.deal activities= MediumUnassign..READY FOR DEVS= MediumUnassign...READY FOR DEVS9, JY-20884 add tools to tooks lisgl= MediumUpgrade BE lbraries - MayMAINTENANCE8Unassign...READY FOR DEVS"o JY-20805 manualtesung= MediumUnassign...READY FOR DEVS% JY-20874 describe tools data structures= MediumNxoiay T.E JY-19958Linked work itemsAnerowenevilsuggestionsAolinkeewewneAUTO-DETEGTED ACTIVITY TYРОcontuence contontBacklogI Jy-204702 a0=Prompts and Skills"0AGOQ мсР3 PipelTasks7insights & Coachin…C Dev@ 2In DevI Improve StoryDetailsAssigneeNkolay IvanowAssign to meHeoot& Gatya DimitrovaDevelopment@ Open with VS Code1 branch3 commits1 pull request3 bullds3 days agoWeXolDwomponentPlatformSub-ProductAdd optionsLabetsStory point estimate9:51 AM | Daily - PlatformDaily - Platform • now100% L28•Mon 18 May 9:51:18• CaleMon 18 May 9:51L Al BockmarxsSteliyan GeorgievNikolay YankovAJ Panorama for CallScoring m o0(AUTOMATED AT ECORINGR J-20301 05 0 •*=(Deadiine 25 May) Migratedepricated Gemini 3.1 FlashLhe Fietew moocDeployedД л-20880 (00 11 •**=Setup test coverage forProphet in SonarE JY-19951 1Е Jy-202720 •=*=Sidekick SMS issueDeployedCЕ P4-20091n • = @...Nikolay lvanovAneliya AngelovaLukas Kovalik4:52...
|
NULL
|
8397054432965923408
|
NULL
|
visual_change
|
ocr
|
NULL
|
Notion CalendarEditViewWindowHelpmeet.google.com/a Notion CalendarEditViewWindowHelpmeet.google.com/agt-teir-cwt?authuser=lukas.kovalik%40jiminny.com+18Nikolay Yankov (Presenting)Flex JimE3 Prom0 AtteniE3 MCP(a Sche xo Curso X * Cauo XCostXhttps://fiminny.atlassian.net/jra/software/c/projectsJJY/boards/37?selectedissue=JY-2083523 SSHE Datadog* Claudel3 CircieClA SentryPlatform Team %.Q Search boardS00P J-20S8 W JT-298SSMAOTOKONote: here you can find example prompts for testing - ® Prompts and SkllsNotify the user d a~ Subtasksdeleted but is used in AJ…..AJ REPORTSBacklogД Jy-200763 .* =0WorkPriorityStor...Assignee% JY-20859 tool for get.deal= Medium8 Unassign...KATDURUEYNotify the user if a SS isdeieted but is used in AJReportAJREPORTSBacklogA JY-206152.5 a4+0 =% JY-20860 tool for search,deals= MediumUnassign...% JY-20861 tool for lisk_deal_options= Medium% Unassign...READY FOR DEVS* JY-20862 tool for get.deal activities= MediumUnassign..READY FOR DEVS= MediumUnassign...READY FOR DEVS9, JY-20884 add tools to tooks lisgl= MediumUpgrade BE lbraries - MayMAINTENANCE8Unassign...READY FOR DEVS"o JY-20805 manualtesung= MediumUnassign...READY FOR DEVS% JY-20874 describe tools data structures= MediumNxoiay T.E JY-19958Linked work itemsAnerowenevilsuggestionsAolinkeewewneAUTO-DETEGTED ACTIVITY TYРОcontuence contontBacklogI Jy-204702 a0=Prompts and Skills"0AGOQ мсР3 PipelTasks7insights & Coachin…C Dev@ 2In DevI Improve StoryDetailsAssigneeNkolay IvanowAssign to meHeoot& Gatya DimitrovaDevelopment@ Open with VS Code1 branch3 commits1 pull request3 bullds3 days agoWeXolDwomponentPlatformSub-ProductAdd optionsLabetsStory point estimate9:51 AM | Daily - PlatformDaily - Platform • now100% L28•Mon 18 May 9:51:18• CaleMon 18 May 9:51L Al BockmarxsSteliyan GeorgievNikolay YankovAJ Panorama for CallScoring m o0(AUTOMATED AT ECORINGR J-20301 05 0 •*=(Deadiine 25 May) Migratedepricated Gemini 3.1 FlashLhe Fietew moocDeployedД л-20880 (00 11 •**=Setup test coverage forProphet in SonarE JY-19951 1Е Jy-202720 •=*=Sidekick SMS issueDeployedCЕ P4-20091n • = @...Nikolay lvanovAneliya AngelovaLukas Kovalik4:52...
|
NULL
|
NULL
|
NULL
|
NULL
|