|
18692
|
805
|
12
|
2026-05-11T11:40:20.442515+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778499620442_m2.jpg...
|
PhpStorm
|
faVsco.js – MatchActivityCrmData.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormViewINavicareCodeLaravelKeractorTOOISWindo PhpStormViewINavicareCodeLaravelKeractorTOOISWindowFV faVsco.js°9 JY-20725-handle-HS-search-rate-limitProiect v© UserAutomatedReportsController.php© OutOfBoundsException.plQuotaExceededExceptior(c) HubSoot/Service.onp© SyncCrmEntitiesTrait.php© CachedCrmServiceDecorator.phpA console [STAGING]-kateLimitexception.ong© RegistrationInvitationMisr© RequestQueuedForDeferrResponseException.phpc Job.phoRateLimitException.php© RingCentralException.php(C)PaqinationState.phpC) MatchCrmData.phpC) CrmObiectsResolver.phoRingCentralExtensionNotl•+ maxExcentionsX P CcW÷I Y.RuntimeException.php© CheскAnaketrykemotematch.pnpMatchactivitycrmData.phpx© ProviderRateLimiter.php(C) PaqinationContia.php1/141715namespace Jiminny Jods.Crm1/161717) SequenceNumberExcepti© ServicelntegrationExcept( ServiceUnavailable ExcepSidekickSettingsExceptio() Socia Account NotFounde© SocialAccountTokeninvaliSvncActivitvExcention.ohTenantisolationException© TextRelayException.php© TooManyFailedActivities.© TranscriptionNotindexed!© UnexpectedCallExceptior© UnexpectedEloquentMod© UnexpectedValueExcepti© ZipAttackException.php› D FFMpeg> D FormatsD Guards1V8 AVuse Exceptionuse Illuminate\Contracts\Queue\ShouldBeUnique;use Illuminate \Contracts \Queue \ShouldQueue;use Illuminate \Database Connection;use Illuminate \Queue\InteractsWithQueue;use Illuminate \Queue\SerializesModelsuse Illuminate\Support\Facades \Log;use Jiminny Component \Queue \Constantsuse Jiminny Exceptions \InvalidArgumentException;use Jiminny\Exceptions\RateLimitExceptionuse laiminny .iohs winh.use Jiminny\Jobs\MiddLeware\HandleHubspotRateLimit;use Jiminny Models \Activity:use Jiminny Models\Crm\Configuration:use Jiminny Repositories ActivityRepository:use Jiminny\Services\Crm\CrmActivityService:use Psr\Container\ContainerExceptionInterface:1/18171911728|- 17211-172217231727-=172— 17291-173017321733-17541735Console,Log xChanges 12 filesE.env.local app© Client.php app/Services/Crm/Hubspot© HandleHubspotRateLimit.php app/Jobs/Middleware© HandleHubspotRateLimitTest.php tests/Unit/Jobs/Middleware@ HubspotClientInterface.php app/Services/Crm/Hubspot© HubspotPaginationService.php app/Services/Crm/Hubspot/Pagination© JiminnyDebugCommand.php app/Console/CommandsTJ0 + → Side-by-side viewer •8 35f036ac app/Exceptions/RateLimitException.phpDo not ignoreHighlight wordsx 13 B?declare(strict types=1)namespace Jiminny Exceptionsclass RateLimitException extends LoqicExcentionphp loaaina.php confia© MatchActivityCrmData.php app/Jobs/Crm© PaginationState.php app/Services/Crm/Hubspot/Pagination© RateLimitException.pho app/ExceptionsC RateLimitExceptionTest.pho tests/Unit/ExceptionsUnversioned Files 9 files=env.nikilocal appenv.other aoo@ CanAccessAiReportsTest.phn tests/Unit/Policies(C) CreateMockAsk.liminnvReoortResultCommand.ohn aoo/Console/Commands/Rre tavicon ico nublic= ids tyt ann"2 raw sal auerv sal anniC)SimulateWehhookcCommand nhn ann/Console/Commands/Crm/HubsnotML WERHOOK FIL TEPING IMDI EMENTATION md andTacts naccod. 12 (todav 12:02A SF (jiminny@localhost]4 HS_local jiminny@localhost]# console [PKol)# console [eu)• Preparation tor kerl.. In 20 m100% L2• Mon 11 May 14:40:20HandleHubspotRateLimitTest v+0..[2026-05-11 11:20:43] local.ERROR: Failed to refresh HubSpot token {"account_id":1372, "updated_at": "2025 mm[2026-05-11 11:20:43] local.NOTICE: Repairing HubSpot tokens end {"total":3,"fixed":0,"failed":3} {"correlatian[2026-05-11 11:20:48] local.INF0: Jiminny\Console\Commands\Command::run Memory usage before starting command[2026-05-11 11:20:48] local.INF0: Jiminny\Console\Commands\Command::run Memory usage before starting command {"12020-05-11 11.20.48 LocaL.LNFU. LHUDspor Journal commana scaruing polling service1"correlac1on.10: 754015[2026-05-11 11:20:48] local.INFO: [HubSpot Journal Pollingl Service starting {"memory_limit":"256M", "max_execut2020-05-11 11:20:48 Local. LNFU:Hubspor Journal Polling Acqulrea polling lock ""explres ac": 2020-05-1111132026-05-11 11.20.48 LocaL.LNFU:2026-05-11 11.20.48 LocaL.LNFU:Jaminny console commanos.comnremory usage tor commano ""2826-05-11 11:20:49 Local.INF0:Jiminny\Console\Commands\Command::run Memory usage for co2826-05-11 11:20:49 Local.INF0:HubSoot Journal Auth Successfully obtained new access token «"expires_1n":2826-05-11 11:20:49 Local.INF0:2826-05-11 11:20:53 Local.INF0:Memory usage before starting command "[2026-05-11 11:20:53] Local.INF0: Jiminny\Console\Commands\Command: :run Memory usage for command {"command" : "o2026-05-11 11-20:5411LocaL.INFO"HuoSoot Journal Pollinal No data2026-05-11 11:20:5611local.INFO: Jiminny \Console\Commands \Command::run Memory usage before starting command f2026-05-11 11:20:571 Local.TNE0: Jiminnv Console Commands \Command::run Memory usage for command t"command":"cr[2026-05-11 11:20:591 local. INF0:[HubSpot Journal Pollingl No data"correlation 1d":"054d156d-8a4f-4592-a0ce2026-05-11 11:21:151 LocolINF0: HubSoot Journal Polzinal No data"correlation 1d"."054d156d-8a4f-4592-a046[2026-05-11 11:21:22] Local. INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"[2026-05-11 11:21:22] local.INF0: [ScheduleBotCommand] Number of activities to be captured: 0 1"correlation_ic[2026-05-11 11:21:22] local.INF0: Jiminny Console\Commands\Command::run Memory usage for command {"command" : "me(2026-05-11 11•21•311 1ocal TAE0• Liminnv Console Commands Command• •nun Memony ucade hefonp stantina command !2026-05-11 11•21•311 local TAS0• liminnv Console Commands Command• •nun Memony ucade fon command &"command"."dHubspot Rate Limit ReOk Where is it used, will I break something it is replace LogicException with RuntimeException in RateLimitException?Searched RateLimitException in ~/iminnylapp (php) (44))Thought for 2s ›Searched throw new RateLimit Excention and pertormed 1 other auervY2, 11n0 AM Giiy usage quota is exhausted. Purchase extra usage to continue using premium models. Quota resets Maywal .Your included daily usage quota is exhausted. Purchase extra usage to continue using premium models. Quota resets May 12, 11:00Ask anything (&AL)<> CodeClaude Opus 4.7 Medium2 differencesnamespace Jiminny Exceptionsuse Throwableclass RateLimitExcention extends RuntimeExcentionpublic function __constructStrina Smessage =private readonly int $retryAfter = 1,Throwahil e Sarevious = nuipublic function getRetryAfterO: intnotunn may/Cthic-snotnvAfton 1)•W Windsurf Teams 27:7 UTF-8 P 4 spaces...
|
NULL
|
-9096538731895467524
|
NULL
|
click
|
ocr
|
NULL
|
PhpStormViewINavicareCodeLaravelKeractorTOOISWindo PhpStormViewINavicareCodeLaravelKeractorTOOISWindowFV faVsco.js°9 JY-20725-handle-HS-search-rate-limitProiect v© UserAutomatedReportsController.php© OutOfBoundsException.plQuotaExceededExceptior(c) HubSoot/Service.onp© SyncCrmEntitiesTrait.php© CachedCrmServiceDecorator.phpA console [STAGING]-kateLimitexception.ong© RegistrationInvitationMisr© RequestQueuedForDeferrResponseException.phpc Job.phoRateLimitException.php© RingCentralException.php(C)PaqinationState.phpC) MatchCrmData.phpC) CrmObiectsResolver.phoRingCentralExtensionNotl•+ maxExcentionsX P CcW÷I Y.RuntimeException.php© CheскAnaketrykemotematch.pnpMatchactivitycrmData.phpx© ProviderRateLimiter.php(C) PaqinationContia.php1/141715namespace Jiminny Jods.Crm1/161717) SequenceNumberExcepti© ServicelntegrationExcept( ServiceUnavailable ExcepSidekickSettingsExceptio() Socia Account NotFounde© SocialAccountTokeninvaliSvncActivitvExcention.ohTenantisolationException© TextRelayException.php© TooManyFailedActivities.© TranscriptionNotindexed!© UnexpectedCallExceptior© UnexpectedEloquentMod© UnexpectedValueExcepti© ZipAttackException.php› D FFMpeg> D FormatsD Guards1V8 AVuse Exceptionuse Illuminate\Contracts\Queue\ShouldBeUnique;use Illuminate \Contracts \Queue \ShouldQueue;use Illuminate \Database Connection;use Illuminate \Queue\InteractsWithQueue;use Illuminate \Queue\SerializesModelsuse Illuminate\Support\Facades \Log;use Jiminny Component \Queue \Constantsuse Jiminny Exceptions \InvalidArgumentException;use Jiminny\Exceptions\RateLimitExceptionuse laiminny .iohs winh.use Jiminny\Jobs\MiddLeware\HandleHubspotRateLimit;use Jiminny Models \Activity:use Jiminny Models\Crm\Configuration:use Jiminny Repositories ActivityRepository:use Jiminny\Services\Crm\CrmActivityService:use Psr\Container\ContainerExceptionInterface:1/18171911728|- 17211-172217231727-=172— 17291-173017321733-17541735Console,Log xChanges 12 filesE.env.local app© Client.php app/Services/Crm/Hubspot© HandleHubspotRateLimit.php app/Jobs/Middleware© HandleHubspotRateLimitTest.php tests/Unit/Jobs/Middleware@ HubspotClientInterface.php app/Services/Crm/Hubspot© HubspotPaginationService.php app/Services/Crm/Hubspot/Pagination© JiminnyDebugCommand.php app/Console/CommandsTJ0 + → Side-by-side viewer •8 35f036ac app/Exceptions/RateLimitException.phpDo not ignoreHighlight wordsx 13 B?declare(strict types=1)namespace Jiminny Exceptionsclass RateLimitException extends LoqicExcentionphp loaaina.php confia© MatchActivityCrmData.php app/Jobs/Crm© PaginationState.php app/Services/Crm/Hubspot/Pagination© RateLimitException.pho app/ExceptionsC RateLimitExceptionTest.pho tests/Unit/ExceptionsUnversioned Files 9 files=env.nikilocal appenv.other aoo@ CanAccessAiReportsTest.phn tests/Unit/Policies(C) CreateMockAsk.liminnvReoortResultCommand.ohn aoo/Console/Commands/Rre tavicon ico nublic= ids tyt ann"2 raw sal auerv sal anniC)SimulateWehhookcCommand nhn ann/Console/Commands/Crm/HubsnotML WERHOOK FIL TEPING IMDI EMENTATION md andTacts naccod. 12 (todav 12:02A SF (jiminny@localhost]4 HS_local jiminny@localhost]# console [PKol)# console [eu)• Preparation tor kerl.. In 20 m100% L2• Mon 11 May 14:40:20HandleHubspotRateLimitTest v+0..[2026-05-11 11:20:43] local.ERROR: Failed to refresh HubSpot token {"account_id":1372, "updated_at": "2025 mm[2026-05-11 11:20:43] local.NOTICE: Repairing HubSpot tokens end {"total":3,"fixed":0,"failed":3} {"correlatian[2026-05-11 11:20:48] local.INF0: Jiminny\Console\Commands\Command::run Memory usage before starting command[2026-05-11 11:20:48] local.INF0: Jiminny\Console\Commands\Command::run Memory usage before starting command {"12020-05-11 11.20.48 LocaL.LNFU. LHUDspor Journal commana scaruing polling service1"correlac1on.10: 754015[2026-05-11 11:20:48] local.INFO: [HubSpot Journal Pollingl Service starting {"memory_limit":"256M", "max_execut2020-05-11 11:20:48 Local. LNFU:Hubspor Journal Polling Acqulrea polling lock ""explres ac": 2020-05-1111132026-05-11 11.20.48 LocaL.LNFU:2026-05-11 11.20.48 LocaL.LNFU:Jaminny console commanos.comnremory usage tor commano ""2826-05-11 11:20:49 Local.INF0:Jiminny\Console\Commands\Command::run Memory usage for co2826-05-11 11:20:49 Local.INF0:HubSoot Journal Auth Successfully obtained new access token «"expires_1n":2826-05-11 11:20:49 Local.INF0:2826-05-11 11:20:53 Local.INF0:Memory usage before starting command "[2026-05-11 11:20:53] Local.INF0: Jiminny\Console\Commands\Command: :run Memory usage for command {"command" : "o2026-05-11 11-20:5411LocaL.INFO"HuoSoot Journal Pollinal No data2026-05-11 11:20:5611local.INFO: Jiminny \Console\Commands \Command::run Memory usage before starting command f2026-05-11 11:20:571 Local.TNE0: Jiminnv Console Commands \Command::run Memory usage for command t"command":"cr[2026-05-11 11:20:591 local. INF0:[HubSpot Journal Pollingl No data"correlation 1d":"054d156d-8a4f-4592-a0ce2026-05-11 11:21:151 LocolINF0: HubSoot Journal Polzinal No data"correlation 1d"."054d156d-8a4f-4592-a046[2026-05-11 11:21:22] Local. INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"[2026-05-11 11:21:22] local.INF0: [ScheduleBotCommand] Number of activities to be captured: 0 1"correlation_ic[2026-05-11 11:21:22] local.INF0: Jiminny Console\Commands\Command::run Memory usage for command {"command" : "me(2026-05-11 11•21•311 1ocal TAE0• Liminnv Console Commands Command• •nun Memony ucade hefonp stantina command !2026-05-11 11•21•311 local TAS0• liminnv Console Commands Command• •nun Memony ucade fon command &"command"."dHubspot Rate Limit ReOk Where is it used, will I break something it is replace LogicException with RuntimeException in RateLimitException?Searched RateLimitException in ~/iminnylapp (php) (44))Thought for 2s ›Searched throw new RateLimit Excention and pertormed 1 other auervY2, 11n0 AM Giiy usage quota is exhausted. Purchase extra usage to continue using premium models. Quota resets Maywal .Your included daily usage quota is exhausted. Purchase extra usage to continue using premium models. Quota resets May 12, 11:00Ask anything (&AL)<> CodeClaude Opus 4.7 Medium2 differencesnamespace Jiminny Exceptionsuse Throwableclass RateLimitExcention extends RuntimeExcentionpublic function __constructStrina Smessage =private readonly int $retryAfter = 1,Throwahil e Sarevious = nuipublic function getRetryAfterO: intnotunn may/Cthic-snotnvAfton 1)•W Windsurf Teams 27:7 UTF-8 P 4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
74882
|
2654
|
39
|
2026-05-27T07:51:02.773278+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-27/1779 /Users/lukas/.screenpipe/data/data/2026-05-27/1779868262773_m1.jpg...
|
Firefox
|
Meet - [Platform] Planning I Session 📅 — Work
|
1
|
meet.google.com/tgb-pyuf-dri?authuser=lukas.kovali meet.google.com/tgb-pyuf-dri?authuser=lukas.kovalik%40jiminny.com...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Meet - [Platform] Planning I Session 📅
Close tab
N Meet - [Platform] Planning I Session 📅
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
Galya Dimitrova (Presenting, annotating)
Galya Dimitrova (Presenting, annotating)
People
9
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Unpin Galya Dimitrova's presentation from your main screen
You can't unmute someone else's presentation
More options for Galya Dimitrova
Zoom in
Open in new window
Enter Full Screen
Pin Nikolay Nikolov to your main screen
You can't unmute someone else
More options for Nikolay Nikolov
Nikolay Nikolov
Pin Stefka Stoyanova to your main screen
You can't unmute someone else
More options for Stefka Stoyanova
Stefka Stoyanova
Pin Galya Dimitrova to your main screen
Mute Galya Dimitrova's microphone
More options for Galya Dimitrova
Galya Dimitrova
User profile picture User profile picture 4 others
4 others
You’re continuously framed
Backgrounds and effects
More options for Lukas Kovalik
Lukas Kovalik
Others might see more of your background. Click to view your full video.
10:51
AM
[Platform] Planning I Session 📅
[Platform] Planning I Session 📅
Audio settings
Turn on microphone
Video settings
Turn off camera
Galya Dimitrova is presenting
Send a reaction
Turn on captions
Raise hand (ctrl + ⌘ + h)
More options
Leave call
Meeting details
Chat with everyone
Meeting tools
Your microphone is off....
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Meet - [Platform] Planning I Session 📅","depth":4,"bounds":{"left":0.0,"top":0.072222225,"width":0.033680554,"height":0.045555554},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.072222225,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.005902778,"top":0.12,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.0,"top":0.7977778,"width":0.033680554,"height":0.043333333},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.0,"top":0.8411111,"width":0.033680554,"height":0.038333334},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.0,"top":0.8794444,"width":0.033680554,"height":0.03888889},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.0,"top":0.91833335,"width":0.033680554,"height":0.038333334},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0,"top":0.95666665,"width":0.033680554,"height":0.043333333},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Galya Dimitrova (Presenting, annotating)","depth":6,"bounds":{"left":0.072569445,"top":0.101111114,"width":0.18263888,"height":0.022222223},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Galya Dimitrova (Presenting, annotating)","depth":7,"bounds":{"left":0.072569445,"top":0.10222222,"width":0.18263888,"height":0.020555556},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"People","depth":9,"bounds":{"left":0.88680553,"top":0.08944444,"width":0.04097222,"height":0.04},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"9","depth":16,"bounds":{"left":0.9145833,"top":0.101111114,"width":0.0048611113,"height":0.017222222},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Take notes with Gemini","depth":8,"bounds":{"left":0.93333334,"top":0.08944444,"width":0.025,"height":0.04},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Take notes with Gemini","depth":11,"bounds":{"left":0.9361111,"top":0.101111114,"width":0.06388891,"height":0.017222222},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini","depth":16,"bounds":{"left":0.96666664,"top":0.101111114,"width":0.028125,"height":0.017222222},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Gemini","depth":15,"bounds":{"left":0.96458334,"top":0.090555556,"width":0.023611112,"height":0.037777778},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unpin Galya Dimitrova's presentation from your main screen","depth":7,"bounds":{"left":0.346875,"top":0.5083333,"width":0.027777778,"height":0.044444446},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"You can't unmute someone else's presentation","depth":7,"bounds":{"left":0.37465277,"top":0.5061111,"width":0.030555556,"height":0.04888889},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"More options for Galya Dimitrova","depth":7,"bounds":{"left":0.40520832,"top":0.5083333,"width":0.027777778,"height":0.044444446},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Zoom in","depth":7,"bounds":{"left":0.6315972,"top":0.83111113,"width":0.027777778,"height":0.044444446},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open in new window","depth":7,"bounds":{"left":0.6649306,"top":0.83111113,"width":0.027777778,"height":0.044444446},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Enter Full Screen","depth":7,"bounds":{"left":0.6982639,"top":0.83111113,"width":0.027777778,"height":0.044444446},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Pin Nikolay Nikolov to your main screen","depth":7,"bounds":{"left":0.76180553,"top":0.25111112,"width":0.027777778,"height":0.044444446},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"You can't unmute someone else","depth":7,"bounds":{"left":0.7895833,"top":0.2488889,"width":0.030555556,"height":0.04888889},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"More options for Nikolay Nikolov","depth":7,"bounds":{"left":0.8201389,"top":0.25111112,"width":0.027777778,"height":0.044444446},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Nikolay Nikolov","depth":11,"bounds":{"left":0.75451386,"top":0.36277777,"width":0.07847222,"height":0.022777777},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Pin Stefka Stoyanova to your main screen","depth":7,"bounds":{"left":0.8875,"top":0.25111112,"width":0.027777778,"height":0.044444446},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"You can't unmute someone else","depth":7,"bounds":{"left":0.9152778,"top":0.2488889,"width":0.030555556,"height":0.04888889},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"More options for Stefka Stoyanova","depth":7,"bounds":{"left":0.9458333,"top":0.25111112,"width":0.027777778,"height":0.044444446},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Stefka Stoyanova","depth":11,"bounds":{"left":0.8802083,"top":0.36277777,"width":0.088194445,"height":0.022777777},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Pin Galya Dimitrova to your main screen","depth":7,"bounds":{"left":0.76180553,"top":0.5088889,"width":0.027777778,"height":0.044444446},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Mute Galya Dimitrova's microphone","depth":7,"bounds":{"left":0.7895833,"top":0.50666666,"width":0.030555556,"height":0.04888889},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"More options for Galya Dimitrova","depth":7,"bounds":{"left":0.8201389,"top":0.5088889,"width":0.027777778,"height":0.044444446},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Galya Dimitrova","depth":11,"bounds":{"left":0.75451386,"top":0.6205556,"width":0.08194444,"height":0.022777777},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"User profile picture User profile picture 4 others","depth":5,"bounds":{"left":0.871875,"top":0.40888888,"width":0.11736111,"height":0.24444444},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"4 others","depth":7,"bounds":{"left":0.909375,"top":0.5566667,"width":0.04236111,"height":0.022777777},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"You’re continuously framed","depth":7,"bounds":{"left":0.8229167,"top":0.7644445,"width":0.030555556,"height":0.04888889},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Backgrounds and effects","depth":7,"bounds":{"left":0.85347223,"top":0.7644445,"width":0.030555556,"height":0.04888889},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"More options for Lukas Kovalik","depth":7,"bounds":{"left":0.8840278,"top":0.76666665,"width":0.027777778,"height":0.044444446},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Lukas Kovalik","depth":11,"bounds":{"left":0.75381947,"top":0.87833333,"width":0.06875,"height":0.022777777},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Others might see more of your background. Click to view your full video.","depth":8,"bounds":{"left":0.96631944,"top":0.875,"width":0.018055556,"height":0.028888889},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"10:51","depth":6,"bounds":{"left":0.050347224,"top":0.9444444,"width":0.02638889,"height":0.022777777},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AM","depth":6,"bounds":{"left":0.08020833,"top":0.9444444,"width":0.017708333,"height":0.022777777},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"[Platform] Planning I Session 📅","depth":6,"bounds":{"left":0.115277775,"top":0.9111111,"width":0.16145833,"height":0.08888888},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[Platform] Planning I Session 📅","depth":9,"bounds":{"left":0.115277775,"top":0.9438889,"width":0.16145833,"height":0.023333333},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Audio settings","depth":7,"bounds":{"left":0.32118055,"top":0.9288889,"width":0.06111111,"height":0.053333335},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Turn on microphone","depth":7,"bounds":{"left":0.34895834,"top":0.9288889,"width":0.033333335,"height":0.053333335},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false},{"role":"AXButton","text":"Video settings","depth":7,"bounds":{"left":0.38784721,"top":0.9288889,"width":0.06111111,"height":0.053333335},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Turn off camera","depth":7,"bounds":{"left":0.415625,"top":0.9288889,"width":0.033333335,"height":0.053333335},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Galya Dimitrova is presenting","depth":6,"bounds":{"left":0.45451388,"top":0.9288889,"width":0.03888889,"height":0.053333335},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Send a reaction","depth":6,"bounds":{"left":0.49895832,"top":0.9288889,"width":0.03888889,"height":0.053333335},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Turn on captions","depth":7,"bounds":{"left":0.5434028,"top":0.9288889,"width":0.03888889,"height":0.053333335},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Raise hand (ctrl + ⌘ + h)","depth":6,"bounds":{"left":0.58784723,"top":0.9288889,"width":0.03888889,"height":0.053333335},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"More options","depth":6,"bounds":{"left":0.6322917,"top":0.9288889,"width":0.025,"height":0.053333335},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Leave call","depth":6,"bounds":{"left":0.6628472,"top":0.9288889,"width":0.05,"height":0.053333335},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Meeting details","depth":6,"bounds":{"left":0.89166665,"top":0.9288889,"width":0.033333335,"height":0.053333335},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Chat with everyone","depth":6,"bounds":{"left":0.925,"top":0.9288889,"width":0.033333335,"height":0.053333335},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Meeting tools","depth":6,"bounds":{"left":0.9583333,"top":0.9288889,"width":0.033333335,"height":0.053333335},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Your microphone is off.","depth":2,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-9096346061608388843
|
-9177780305881672904
|
visual_change
|
hybrid
|
NULL
|
Meet - [Platform] Planning I Session 📅
Close tab
N Meet - [Platform] Planning I Session 📅
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
Galya Dimitrova (Presenting, annotating)
Galya Dimitrova (Presenting, annotating)
People
9
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Unpin Galya Dimitrova's presentation from your main screen
You can't unmute someone else's presentation
More options for Galya Dimitrova
Zoom in
Open in new window
Enter Full Screen
Pin Nikolay Nikolov to your main screen
You can't unmute someone else
More options for Nikolay Nikolov
Nikolay Nikolov
Pin Stefka Stoyanova to your main screen
You can't unmute someone else
More options for Stefka Stoyanova
Stefka Stoyanova
Pin Galya Dimitrova to your main screen
Mute Galya Dimitrova's microphone
More options for Galya Dimitrova
Galya Dimitrova
User profile picture User profile picture 4 others
4 others
You’re continuously framed
Backgrounds and effects
More options for Lukas Kovalik
Lukas Kovalik
Others might see more of your background. Click to view your full video.
10:51
AM
[Platform] Planning I Session 📅
[Platform] Planning I Session 📅
Audio settings
Turn on microphone
Video settings
Turn off camera
Galya Dimitrova is presenting
Send a reaction
Turn on captions
Raise hand (ctrl + ⌘ + h)
More options
Leave call
Meeting details
Chat with everyone
Meeting tools
Your microphone is off.
FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelpmeet.google.com/tgb-pyuf-dri?authuser=lukas.kovalik%40jiminny.com100% C8• Wed 27 May 10:51:02+Galya Dimitrova (Presenting, annotating)SafariFileEditViewHistoryBookmarksDevelopWindowHelp₽$8• Wed 27 May 10:5135 HubSpot|Platform Team - Backdoajiminny.atlassian.net|AWS EUAWS AIPlanhatS. GTM Trends 2026: Top 1.88& Plans0, SpacesStarredJiminny (New)|I CD Platform TeamШ) Processing TeamШ) SE KanbaniI) Capture TeamIID Enterprise Stability I...DiscoveryProduct(9) Service-Desk® Summary|@ QueuesStarred@ Support team Queue© Platform team© Processing team> Team Priority= View all queuesService requestsfi' Seit ServiceEl Oust DocsJ AWS US[] EmollLogRocketP Postmark|Bamboo*. Campus Xotwree AlenicoFirebase* Guide to the Figma MCP.Search+ Create# UpgradeAsk RovoSpaces / Jiminny (New)Platform Team• Summary|& Timeline₴ Backlog|uII Active sprints12 Reports4 Testing Board# ListQ Search backlog |218809+8Version vEpic vType vLabel vQuick filtersA JY-20974 Jiminny MCP Server versioningJIMINNY MCP CONN...0 JY-20963 Fix foreign key violation when matching activities to deleted Salesforce opportuni...SUPPORT TICKETS@ JY-19958 Upgrade BE libraries - MayMAINTENANCE[ JY-19890 Evaluation for Exec ReportsAUTOMATE EXEC REP.A JY-20912 Fallback mechanism for users with active SF tokens for CRM MatchingREDUCE CHURNД JY-20753 User can log & save meeting information to CRMRECORDING MEETIN..A JY-20871 Users can take Notes during and after meetingsRECORDING MEETIN..A JY-20847 Users can filter Scores in Team Insights by HostAUTOMATED AI SCO.A JY-20771 Call Scoring filter for Exec reports [AUTOMATZO ALSOO.( JY-20534 AI Call Scoring quick access in Playback headerAUTOMATED AI SCO.A JY-20878 SCIM > Allow customers to manage user roles through SCIMREDUCE CHUEA JY-20500 Batch initial sync for SalesforceCRM SYNCHING IMP.A JY-20501 |Batch initial sync for HubspotCRM SYNCHING IMP+ Createmeet.google.com is sharing your screen.Stop sharing3 Forms( ComponentsMore 9+BACKLOG2=IN DEVBACKLOG1IN DEV10BACKLOGBACKLOG3BACKLOG2.5BACKLOG15BACKLOSV=д ..BACKLOG1BACKLOGBACKLOGvBACKLOG32Nikolay Nikolovtefka Stoyanova4 othersGalya DimitrovaLukas Kovalik49:0710:51 AM | [Platform] Planning | Session izLộ3...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
88733
|
3026
|
26
|
2026-05-28T17:34:28.386722+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779989668386_m1.jpg...
|
PhpStorm
|
faVsco.js – console [EU]
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
ServiceTest
Run 'ServiceTest'
Debug 'ServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
cachedClosedDealStages
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
2/6
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Sync Changes
Hide This Notification
Code changed:
Hide
75
2
22
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use HubSpot\Client\Crm\Deals\Model\CollectionResponseAssociatedId;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Models\Account;
use Exception;
use Jiminny\Component\DealInsights\Forecast\Forecast;
use Jiminny\Jobs\Crm\MatchActivitiesToNewOpportunity;
use Jiminny\Models\Contact;
use Jiminny\Models\Crm\BusinessProcess;
use Jiminny\Exceptions\CrmException;
use Jiminny\Models\Crm\Profile;
use Jiminny\Models\Opportunity;
use Illuminate\Support\Collection;
use Jiminny\Models\Stage;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Crm\Hubspot\DealFieldsService;
use Jiminny\Services\Crm\Hubspot\OpportunitySyncStrategy\HubspotSingleSyncStrategy;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Services\Crm\OpportunitySyncStrategyResolver;
use Jiminny\Utils\CurrencyFormatter;
/**
* Optimized sync methods for better performance
* These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains
*/
trait OpportunitySyncTrait
{
private const int BATCH_SIZE = 100;
private const int BATCH_PROCESS_SIZE = 800;
protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;
protected CrmEntityRepository $crmEntityRepository;
protected DealFieldsService $dealFieldsService;
private ?array $cachedClosedDealStages = null;
private array $cachedBusinessProcesses = [];
private array $cachedStages = [];
/** @var array<string, array<string>> keyed by config id */
private array $cachedOpportunitySyncableFields = [];
/** @var array<string, mixed> keyed by configId:ownerId */
private array $cachedOwnerProfiles = [];
/** @var array<string, mixed> keyed by configId:businessProcessId */
private array $cachedRecordTypes = [];
public function syncOpportunities(array $parameters, ?string $strategy = null): int
{
$startTime = microtime(true);
$strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);
$parameters['config'] = $this->config;
$syncCount = 0;
$reportedTotal = 0;
$lastSyncedId = [];
$strategyNames = [];
try {
foreach ($strategies as $strategyName => $syncStrategy) {
$strategyNames[] = $strategyName;
$this->logger->info(
'[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' . $strategyName,
['team' => $this->team->getId()]
);
$total = 0;
$lastId = null;
$buffer = [];
// HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies
foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {
$buffer[] = $hsOpportunity;
// process every 800 rows (fits < 1 000 association limit)
if (\count($buffer) >= self::BATCH_PROCESS_SIZE) {
$syncCount += $this->processOpportunityBatch($buffer);
$buffer = [];
}
}
// leftovers
if ($buffer) {
$syncCount += $this->processOpportunityBatch($buffer);
}
$reportedTotal += $total;
$lastSyncedId = $lastId;
}
} catch (\HubSpot\Client\Crm\Deals\ApiException | CrmException $e) {
$this->handleSyncException($e, $parameters);
}
$durationMs = round((microtime(true) - $startTime) * 1000, 2);
$this->logger->info(
'[HubSpot] Synced opportunities',
[
'team' => $this->team->getId(),
'strategies' => implode(',', $strategyNames),
'sync_count' => $syncCount,
'total' => $reportedTotal,
'last_synced_id' => $lastSyncedId,
'duration_ms' => $durationMs,
]
);
return $reportedTotal;
}
private function handleSyncException(\Throwable $e, array $parameters): void
{
if (($parameters['since'] ?? null) instanceof Carbon) {
$parameters['since'] = $parameters['since']->toDateTimeString();
}
$parameters['config'] = $this->config->getId();
$this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [
'teamId' => $this->team->getUuid(),
'parameters' => $parameters,
'reason' => $e->getMessage(),
]);
}
/**
* @inheritdoc
*/
public function syncOpportunity(string $crmId): ?Opportunity
{
$strategy = $this->opportunitySyncStrategyResolver->resolve(
$this->config,
OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,
);
$parameters = [
'config' => $this->config,
'crm_id' => $crmId,
];
try {
if (! $strategy instanceof HubspotSingleSyncStrategy) {
throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');
}
$hsOpportunity = $strategy->fetchOpportunity($parameters);
} catch (\HubSpot\Client\Crm\Deals\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
$hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);
return $this->importOrUpdateOpportunity($hsOpportunity);
}
/**
* Process webhook-collected opportunity batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportOpportunityBatch jobs for batch processing.
*
* @return int Number of opportunity IDs dispatched to jobs
*/
public function batchSyncOpportunities(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,
$configId
);
}
/**
* Import a batch of opportunities by their CRM IDs.
* Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().
*
* @param array<string> $crmIds HubSpot deal CRM IDs
*
* @return array{success: array, failed_ids: array, errors?: array<string, string>}
*/
public function importOpportunityBatchByIds(array $crmIds): array
{
$fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);
$allDeals = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
$deals = $this->client->getOpportunitiesByIds($chunk, $fields);
foreach ($deals as $deal) {
$allDeals[] = $deal;
}
}
// IDs not returned by HubSpot are likely deleted or inaccessible deals.
// These are not failures — retrying won't bring them back.
$fetchedIds = array_map('strval', array_column($allDeals, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allDeals),
]);
}
if (empty($allDeals)) {
return ['success' => [], 'failed_ids' => []];
}
return $this->importOpportunityBatch($allDeals);
}
private function getClosedDealStages(): array
{
if ($this->cachedClosedDealStages !== null) {
return $this->cachedClosedDealStages;
}
$stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);
$data = [
'lost' => [],
'won' => [],
];
foreach ($stages as $stage) {
if ($stage->probability == 0.00) {
$data['lost'][] = $stage->crm_provider_id;
}
if ($stage->probability == 100.00) {
$data['won'][] = $stage->crm_provider_id;
}
}
$this->cachedClosedDealStages = $data;
return $data;
}
/**
* Import deals into the database with pre-fetched associations.
*
* API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT
* caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()
* where Laravel retries the whole job with backoff. After all retries exhausted,
* failed() requeues all IDs to Redis.
*
* The per-deal loop catches exceptions individually. A deal can end up in three states:
* - success: imported/updated successfully
* - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)
* These are permanent issues — retrying won't fix them.
* - skipped (null): missing dependencies (no account, unknown pipeline/stage).
* This is acceptable — the deal cannot be imported until those exist.
*/
private function importOpportunityBatch(array $deals): array
{
$syncedOpportunities = [
'success' => [],
'failed_ids' => [],
];
$dealIds = array_column($deals, 'id');
$batchStart = microtime(true);
$slowDeals = [];
// Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the
// queue job retries the whole batch and eventually requeues all deal IDs back to Redis.
try {
$companyAssocStart = microtime(true);
$companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');
$companyAssocMs = (int) round((microtime(true) - $companyAssocStart) * 1000);
$contactAssocStart = microtime(true);
$contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');
$contactAssocMs = (int) round((microtime(true) - $contactAssocStart) * 1000);
$prepareStart = microtime(true);
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
$prepareTimings = [];
$associationsData = $this->prepareAssociatedEntities(
$companyAssociations,
$contactAssociations,
$prepareTimings
);
$prepareMs = (int) round((microtime(true) - $prepareStart) * 1000);
$missingCompanies = count(array_diff(
$allCompanyIds,
array_keys($associationsData['company_id_mappings'] ?? [])
));
$missingContacts = count(array_diff(
$allContactIds,
array_keys($associationsData['contact_id_mappings'] ?? [])
));
$existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(
$this->config,
array_map('strval', $dealIds)
);
$existingCrmIdSet = array_flip($existingCrmIds);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [
'teamId' => $this->team->getId(),
'dealCount' => count($dealIds),
'error' => $e->getMessage(),
]);
throw $e;
}
$loopStart = microtime(true);
foreach ($deals as $deal) {
$dealStart = microtime(true);
try {
$deal['associations'] = $this->prepareAssociationsForOpportunity(
$deal['id'],
$companyAssociations,
$contactAssociations,
$associationsData
);
$syncedOpportunity = $this->importOrUpdateOpportunity(
$deal,
isset($existingCrmIdSet[(string) $deal['id']])
);
if ($syncedOpportunity) {
$syncedOpportunities['success'][] = $syncedOpportunity;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [
'teamId' => $this->team->getId(),
'crmId' => $deal['id'],
'error' => $e->getMessage(),
]);
$syncedOpportunities['failed_ids'][] = $deal['id'];
$syncedOpportunities['errors'][$deal['id']] = $e->getMessage();
}
$dealMs = (int) round((microtime(true) - $dealStart) * 1000);
if ($dealMs > 1000) {
$slowDeals[] = ['crmId' => $deal['id'], 'ms' => $dealMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importOpportunityBatch timing', [
'teamId' => $this->team->getId(),
'deal_count' => count($deals),
'total_ms' => $totalMs,
'company_assoc_api_ms' => $companyAssocMs,
'contact_assoc_api_ms' => $contactAssocMs,
'prepare_entities_ms' => $prepareMs,
'prepare_accounts_ms' => $prepareTimings['accounts_ms'],
'prepare_contacts_ms' => $prepareTimings['contacts_ms'],
'total_companies' => count($allCompanyIds),
'missing_companies' => $missingCompanies,
'total_contacts' => count($allContactIds),
'missing_contacts' => $missingContacts,
'deals_loop_ms' => $loopMs,
'avg_deal_ms' => ! empty($deals) ? (int) round($loopMs / count($deals)) : 0,
'slow_deals_count' => count($slowDeals),
'slow_deals' => array_slice($slowDeals, 0, 10),
]);
return $syncedOpportunities;
}
/**
* Prepare associated entities for opportunities with optimized batch processing
* Returns structured data with CRM ID to DB ID mappings for each opportunity
*/
private function prepareAssociatedEntities(
array $companyAssociations,
array $contactAssociations,
array &$timings = []
): array {
// Step 1: Collect all unique company and contact IDs from associations
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
// Step 2: Batch sync missing entities and get CRM ID to DB ID mappings
$companyIdMappings = [];
$contactIdMappings = [];
$accountsMs = 0;
$contactsMs = 0;
if (! empty($allCompanyIds)) {
$start = microtime(true);
$companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);
$accountsMs = (int) round((microtime(true) - $start) * 1000);
}
if (! empty($allContactIds)) {
$start = microtime(true);
$contactIdMappings = $this->prepareAssociatedContacts($allContactIds);
$contactsMs = (int) round((microtime(true) - $start) * 1000);
}
$timings = [
'accounts_ms' => $accountsMs,
'contacts_ms' => $contactsMs,
];
return [
'company_id_mappings' => $companyIdMappings,
'contact_id_mappings' => $contactIdMappings,
];
}
/**
* Flatten association data to get unique IDs
*/
private function flattenAssociationIds(array $associations): array
{
$ids = [];
foreach ($associations as $dealAssociations) {
if (is_array($dealAssociations)) {
foreach ($dealAssociations as $id) {
$ids[$id] = true;
}
}
}
return array_keys($ids);
}
/**
* Batch sync missing accounts
*/
private function prepareAssociatedAccounts(array $companyIds): array
{
// Find which accounts already exist (lean covering-index lookup)
$existingAccountsData = $this->crmEntityRepository
->getExistingAccountIdsMap($this->config, $companyIds);
$missingCompanyIds = array_diff($companyIds, array_keys($existingAccountsData));
if (empty($missingCompanyIds)) {
return $existingAccountsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [
'teamId' => $this->team->getUuid(),
'total_companies' => count($companyIds),
'existing_companies' => count($existingAccountsData),
'missing_companies' => count($missingCompanyIds),
]);
// we already have limit on opportunity ids count
// Initialize variable before try block
$syncedAccountsData = [];
try {
$syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [
'size' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
$syncedAccountsData = [];
}
return $existingAccountsData + $syncedAccountsData;
}
/**
* Prepare associated contacts - find existing and sync missing ones
* Returns mapping of CRM ID to DB ID
*/
private function prepareAssociatedContacts(array $contactIds): array
{
// Find which contacts already exist (lean covering-index lookup)
$existingContactsData = $this->crmEntityRepository
->getExistingContactIdsMap($this->config, $contactIds);
$missingContactIds = array_diff($contactIds, array_keys($existingContactsData));
if (empty($missingContactIds)) {
return $existingContactsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [
'teamId' => $this->team->getUuid(),
'total_contacts' => count($contactIds),
'existing_contacts' => count($existingContactsData),
'missing_contacts' => count($missingContactIds),
]);
// Sync missing contacts using batch API
try {
$syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [
'size' => count($missingContactIds),
'error' => $e->getMessage(),
]);
$syncedContactsData = [];
}
return $existingContactsData + $syncedContactsData;
}
private function batchSyncCrmObjects(string $objectType, array $crmIds): array
{
$syncObjects = [];
$crmObjectIds = array_values($crmIds);
foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {
try {
$objects = $objectType === 'companies' ?
$this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :
$this->client->getContactsByIds($chunk, $this->getContactFields());
foreach ($objects as $objectId => $objectData) {
$this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [
'requested_count' => count($chunk),
'synced_count' => count($objects),
]);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [
'ids' => $chunk,
'error' => $e->getMessage(),
]);
}
}
return $syncObjects;
}
private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void
{
try {
$object = $objectType === 'companies' ?
$this->importAccount($objectData) :
$this->importContact($objectData);
if ($object) {
$syncObjects[$object->getCrmProviderId()] = $object->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [
'id' => $objectId,
'error' => $e->getMessage(),
]);
}
}
/**
* Prepare associations for a single opportunity
*
* The return value is an array with the following structure:
* [
* 'companies' => [
* $companyCrmId => $companyId,
* ...
* ],
* 'contacts' => [
* $contactCrmId => $contactId,
* ...
* ],
* 'account_id' => $accountId,
* ]
*/
private function prepareAssociationsForOpportunity(
string $oppCrmId,
array $companyAssociations,
array $contactAssociations,
array $associationsData
): array {
$associations = [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
$oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];
foreach ($oppCompanyIds as $companyCrmId) {
if (isset($associationsData['company_id_mappings'][$companyCrmId])) {
$associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];
// Set primary account (first company becomes primary account)
if ($associations['account_id'] === null) {
$associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];
}
}
}
$oppContactIds = $contactAssociations[$oppCrmId] ?? [];
foreach ($oppContactIds as $contactCrmId) {
if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {
$associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];
}
}
return $associations;
}
/**
* Update only associations for an opportunity
*/
private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void
{
// Update contact associations
$this->importOpportunityContacts($opportunity, $associations['contacts']);
// Update company (account) associations
$this->updateOpportunityAccount($opportunity, $associations['account_id']);
}
/**
* Remove all contact associations from an opportunity
*/
private function removeAllOpportunityContacts(Opportunity $opportunity): void
{
$currentCount = (int) $opportunity->contacts()->count();
if ($currentCount > 0) {
$opportunity->contacts()->detach();
$this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_count' => $currentCount,
]);
}
}
private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void
{
if ($accountId === null) {
// No account ID provided - keep current account
return;
}
$currentAccountId = $opportunity->getAccountId();
// Only update if account has changed
if ($currentAccountId !== $accountId) {
$opportunity->account_id = $accountId;
$opportunity->save();
$this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [
'opportunity_id' => $opportunity->getId(),
'old_account_id' => $currentAccountId,
'new_account_id' => $accountId,
]);
}
}
/**
* Find existing opportunities by external IDs (OPTIMIZED VERSION)
* Uses batch query for better performance
*/
private function findExistingOpportunities(array $crmIds): Collection
{
return $this->crmEntityRepository
->findOpportunitiesByExternalIds($this->config, $crmIds);
}
private function processOpportunityBatch(array $opportunities): int
{
$syncedOpportunities = $this->importOpportunityBatch($opportunities);
return count($syncedOpportunities['success'] ?? []);
}
/**
* Convert single deal associations from HubSpot format to internal format
* Handles both HubSpot SDK objects and array formats
*
* @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed
*
* @return array Processed associations with DB IDs
*/
private function convertDealAssociations(array $opportunityAssociations): array
{
$associations = $this->initializeAssociationsStructure();
if (empty($opportunityAssociations)) {
return $associations;
}
$associationIds = $this->extractAssociationIds($opportunityAssociations);
$this->processCompanyAssociations($associationIds, $associations);
$this->processContactAssociations($associationIds, $associations);
return $associations;
}
private function initializeAssociationsStructure(): array
{
return [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
}
private function extractAssociationIds(array $opportunityAssociations): array
{
$associationIds = [];
foreach ($opportunityAssociations as $type => $associationData) {
if (! empty($associationData)) {
$associationIds[$type] = $this->convertSingleDealAssociations($associationData);
}
}
return $associationIds;
}
private function processCompanyAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['companies'])) {
return;
}
$companyId = $associationIds['companies'][0];
$account = $this->findOrSyncAccount($companyId);
if ($account instanceof Account) {
$associations['companies'][$companyId] = $account->getId();
$associations['account_id'] = $account->getId();
}
}
private function processContactAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['contacts'])) {
return;
}
foreach ($associationIds['contacts'] as $contactId) {
$contact = $this->findOrSyncContact($contactId);
if ($contact instanceof Contact) {
$associations['contacts'][$contactId] = $contact->getId();
}
}
}
private function findOrSyncAccount(string $companyId): ?Account
{
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);
if (! $account instanceof Account) {
$account = $this->syncAccount($companyId);
}
return $account;
}
private function findOrSyncContact(string $contactId): ?Contact
{
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);
if (! $contact instanceof Contact) {
$contact = $this->syncContact($contactId);
}
return $contact;
}
private function convertSingleDealAssociations($opportunityAssociations = null): array
{
$associationData = [];
if ($opportunityAssociations === null) {
return $associationData;
}
// Handle array input (from extractAssociationIds)
if (is_array($opportunityAssociations)) {
return $opportunityAssociations;
}
// Handle CollectionResponseAssociatedId object
if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {
foreach ($opportunityAssociations->getResults() as $association) {
$associationData[] = $association->getId();
}
}
return $associationData;
}
private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity
{
if (empty($crmData['properties'])) {
return null;
}
$properties = $crmData['properties'];
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = (string) $properties['hubspot_owner_id'];
$profile = $this->getCachedOwnerProfile($ownerId);
}
$associations = $crmData['associations'] ?? [];
$accountId = $this->resolveAccountId($associations);
// Only fetch account if we need it for user_id fallback
$accountUserId = null;
if ($profile?->getUserId() === null && $accountId !== null) {
$accountUserId = $this->crmEntityRepository
->findAccountByConfigurationAndId(
$this->config,
$accountId
)?->getUserId();
}
$crmId = (string) $crmData['id'];
if (($profile?->getUserId() === null) && ($accountUserId === null)) {
$this->logger->error(
'[HubSpot] Skip import, no user_id found',
[
'id' => $crmId,
]
);
return null;
}
$opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(
$this->config,
$crmId
);
if ($opportunityExists) {
return $this->updateOpportunity(
$crmId,
$properties,
$associations,
$accountUserId
);
}
return $this->createOpportunity(
$crmId,
$properties,
$associations,
$accountUserId,
);
}
/**
* Create new opportunity
*/
private function createOpportunity(
string $crmId,
array $properties,
array $associations,
?int $accountUserId = null
): ?Opportunity {
$accountId = $this->resolveAccountId($associations);
if (! $accountId) {
return null;
}
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
if (! $businessProcess) {
return null;
}
$stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);
if (! $stage) {
return null;
}
$data = $this->buildOpportunityData(
$properties,
$accountId,
$businessProcess,
$stage,
$accountUserId
);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->importOpportunityContacts($opportunity, $associations['contacts']);
if ($opportunity->wasRecentlyCreated) {
MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());
}
return $opportunity;
}
/**
* Update existing opportunity
*/
private function updateOpportunity(
string $crmId,
array $properties,
array $associations,
?int $accountUserId = null
): Opportunity {
$accountId = $this->resolveAccountId($associations);
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
$stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;
$data = $this->buildOpportunityData(
$properties,
$accountId,
$businessProcess,
$stage,
$accountUserId
);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->updateOpportunityAssociations($opportunity, $associations);
return $opportunity;
}
private function resolveAccountId(array $associations): ?int
{
if (! empty($associations['account_id'])) {
return $associations['account_id'];
}
if (empty($associations)) {
return null;
}
// Fallback: use first company as account (currently SDK returns one company)
foreach ($associations['companies'] as $accountId) {
return $accountId;
}
return null;
}
private function buildOpportunityData(
array $properties,
?int $accountId,
?BusinessProcess $businessProcess,
?Stage $stage,
?int $accountUserId = null
): array {
$ownerId = null;
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->getCachedOwnerProfile((string) $ownerId);
}
$userId = $profile?->getUserId() ?? $accountUserId;
$name = 'Unknown';
if (isset($properties['dealname'])) {
$name = mb_strimwidth($properties['dealname'], 0, 128);
}
$amount = $this->resolveAmount($properties);
$currency = $properties['deal_currency_code'] ?? null;
$closeDate = null;
if (! empty($properties['closedate'])) {
$closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');
}
$remotelyCreatedAt = null;
if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {
$date = $this->parseCleanDatetime($properties['createdate']);
$remotelyCreatedAt = $date?->format('Y-m-d H:i:s');
}
$closedStages = $this->getClosedDealStages();
$isWon = in_array($properties['dealstage'], $closedStages['won']);
$isLost = in_array($properties['dealstage'], $closedStages['lost']);
$data = [
'team_id' => $this->team->getId(),
'user_id' => $userId,
'owner_id' => $ownerId,
'name' => $name,
'value' => ! empty($amount) ? $amount : null,
'currency_code' => CurrencyFormatter::formatCode($currency),
'close_date' => $closeDate,
'is_closed' => $isWon || $isLost,
'is_won' => $isWon,
'remotely_created_at' => $remotelyCreatedAt,
'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),
'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),
];
if ($accountId) {
$data['account_id'] = $accountId;
}
if ($stage) {
$data['stage_id'] = $stage->id;
}
if ($businessProcess) {
$recordType = $this->getCachedBusinessProcessRecordType($businessProcess);
if ($recordType) {
$data['record_type_id'] = $recordType->id;
}
}
return $data;
}
private function getCachedOwnerProfile(string $ownerId): ?Profile
{
$cacheKey = $this->config->getId() . ':' . $ownerId;
if (array_key_exists($cacheKey, $this->cachedOwnerProfiles)) {
return $this->cachedOwnerProfiles[$cacheKey];
}
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, $ownerId);
$this->cachedOwnerProfiles[$cacheKey] = $profile;
return $profile;
}
private function getCachedBusinessProcessRecordType(BusinessProcess $businessProcess): mixed
{
$cacheKey = $this->config->getId() . ':' . $businessProcess->getId();
if (array_key_exists($cacheKey, $this->cachedRecordTypes)) {
return $this->cachedRecordTypes[$cacheKey];
}
$recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);
$this->cachedRecordTypes[$cacheKey] = $recordType;
return $recordType;
}
private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess
{
if ($pipelineId === null) {
return null;
}
$cacheKey = $this->getBusinessProcessCacheKey($pipelineId);
if (isset($this->cachedBusinessProcesses[$cacheKey])) {
return $this->cachedBusinessProcesses[$cacheKey];
}
$businessProcess = $this->getBusinessProcess($pipelineId);
if (! $businessProcess instanceof BusinessProcess) {
$this->importStages();
$this->cachedClosedDealStages = null;
$businessProcess = $this->getBusinessProcess($pipelineId);
}
if (! $businessProcess instanceof BusinessProcess) {
$this->logger->info(
'[HubSpot] Deal is not attached to a pipeline',
[
'pipeline' => $pipelineId]
);
}
$this->cachedBusinessProcesses[$cacheKey] = $businessProcess;
return $businessProcess;
}
private function getBusinessProcess(string $pipelineId): ?BusinessProcess
{
return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);
}
private function getBusinessProcessCacheKey(string $pipelineId): string
{
return $this->config->getId() . '_' . $pipelineId;
}
private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage
{
if (empty($stageId)) {
return null;
}
$cacheKey = $this->config->getId() . ':' . $businessProcess->getId() . ':' . $stageId;
if (isset($this->cachedStages[$cacheKey])) {
return $this->cachedStages[$cacheKey];
}
$stage = $this->crmEntityRepository->getPipelineStageByConditions(
$businessProcess,
[
'crm_provider_id' => $stageId,
'type' => Stage::TYPE_OPPORTUNITY,
]
);
if ($stage === null) {
$this->importStages(null, $stageId);
$this->cachedClosedDealStages = null;
}
if ($stage === null) {
$this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);
}
$this->cachedStages[$cacheKey] = $stage;
return $stage;
}
private function resolveAmount(array $properties): ?string
{
$amount = null;
if (! empty($properties['amount'])) {
$amount = str_replace(',', '', $properties['amount']);
}
if ($this->config->hasDefaultCurrencyFieldSet()) {
$valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();
$amount = $properties[$valueFieldName] ?? $amount;
}
return $amount;
}
private function parseCleanDatetime(string $datetime): ?Carbon
{
// Treat pre-1980 values as invalid
$minValidDate = Carbon::parse('1980-01-01 00:00:00');
try {
$date = Carbon::parse($datetime);
if ($minValidDate->gt($date)) {
return null;
}
return $date;
} catch (Exception) {
return null; // On parse error, treat as null
}
}
private function resolveDealProbability(?string $stageProbability): int
{
if ($stageProbability === null) {
return 0;
}
$probability = (float) $stageProbability;
return $probability > 1 ? 0 : (int) ($probability * 100);
}
private function resolveForecastCategory(?string $forecastCategory): string
{
if (! $forecastCategory) {
return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;
}
$forecastCategory = str_replace('_', ' ', $forecastCategory);
return ucwords(strtolower($forecastCategory));
}
private function importExternalFieldData(array $properties, int $opportunityId): void
{
$this->importOpportunityCrmFieldData(
$properties,
$this->getCachedOpportunitySyncableFields(),
$opportunityId
);
}
private function getCachedOpportunitySyncableFields(): array
{
$cacheKey = (string) $this->config->getId();
if (! isset($this->cachedOpportunitySyncableFields[$cacheKey])) {
$this->cachedOpportunitySyncableFields[$cacheKey] = $this->getOpportunitySyncableFields();
}
return $this->cachedOpportunitySyncableFields[$cacheKey];
}
private function importOpportunityContacts(Opportunity $opportunity, array $associations): void
{
// Handle empty or missing contact associations
if (empty($associations)) {
// Remove all existing contact associations if none provided
$this->removeAllOpportunityContacts($opportunity);
return;
}
// Use differential sync approach for better performance and accuracy
$this->syncOpportunityContactsDifferential($opportunity, $associations);
}
/**
* Sync opportunity contacts using differential approach
* This compares current vs new associations and only makes necessary changes
*/
private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void
{
$currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);
$contactAssociationIds = array_keys($contactAssociations);
$contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);
$contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);
if (empty($contactsToAdd) && empty($contactsToRemove)) {
return;
}
$this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);
$this->removeContactAssociations($opportunity, $contactsToRemove);
$this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);
}
private function getCurrentContactCrmIds(Opportunity $opportunity): array
{
return $opportunity->contacts()
->pluck('contacts.crm_provider_id')
->toArray();
}
private function logContactAssociationChanges(
Opportunity $opportunity,
array $currentContactCrmIds,
array $contactAssociations,
array $contactsToAdd,
array $contactsToRemove
): void {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [
'opportunity_id' => $opportunity->getId(),
'current_contacts' => $currentContactCrmIds,
'new_contacts' => $contactAssociations,
'contacts_to_add' => $contactsToAdd,
'contacts_to_remove' => $contactsToRemove,
]);
}
private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void
{
if (empty($contactsToRemove)) {
return;
}
$contactsToDetach = $opportunity->contacts()
->whereIn('contacts.crm_provider_id', $contactsToRemove)
->pluck('contacts.id')
->toArray();
if (! empty($contactsToDetach)) {
$opportunity->contacts()->detach($contactsToDetach);
$this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_contact_crm_ids' => $contactsToRemove,
'removed_contact_count' => count($contactsToDetach),
]);
}
}
private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void
{
if (empty($contactsToAdd)) {
return;
}
$contactsAdded = [];
foreach ($contactsToAdd as $crmId) {
$id = $contactAssociations[$crmId];
if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {
$contactsAdded[] = $crmId;
}
}
$this->logAddedContacts($opportunity, $contactsAdded);
}
private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool
{
try {
return $this->performContactAttachment($opportunity, $id, $crmId);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [
'opportunity_id' => $opportunity->getId(),
'contact_crm_id' => $crmId,
'error' => $e->getMessage(),
]);
return false;
}
}
private function performContactAttachment(Opportunity $opportunity, int $contactId, string $crmId): bool
{
try {
$opportunity->contacts()->attach($contactId, [
'crm_provider_id' => $crmId,
]);
return true;
} catch (\Illuminate\Database\QueryException $e) {
if (str_contains($e->getMessage(), 'Duplicate entry')) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [
'contact_id' => $contactId,
'contact_crm_id' => $crmId,
'opportunity_id' => $opportunity->getId(),
]);
return false;
}
throw $e;
}
}
private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void
{
if (! empty($contactsAdded)) {
$this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [
'opportunity_id' => $opportunity->getId(),
'added_contact_crm_ids' => $contactsAdded,
'added_contacts_count' => count($contactsAdded),
]);
}
}
}
Execute
Explain Plan
Browse Query History...
|
[{"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":"#12121 on JY-20963-fix-import-on-deleted-entity, menu","depth":5,"on_screen":true,"help_text":"Pull request #12121 exists for current branch JY-20963-fix-import-on-deleted-entity, but pull request details loading failed","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":"ServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'ServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'ServiceTest'","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":"Show Replace Field","depth":4,"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,"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"cachedClosedDealStages","depth":4,"on_screen":true,"value":"cachedClosedDealStages","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"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,"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Words","depth":3,"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Regex","depth":3,"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.0,"top":0.0,"width":0.015277778,"height":0.024444444},"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.0,"top":0.0,"width":0.015277778,"height":0.024444444},"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.0,"top":0.0,"width":0.015277778,"height":0.024444444},"on_screen":false,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2/6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Occurrence","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Occurrence","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Filter Search Results","depth":4,"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,"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,"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":"75","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.020833334,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.016666668,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"22","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.020833334,"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\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\CollectionResponseAssociatedId;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Models\\Account;\nuse Exception;\nuse Jiminny\\Component\\DealInsights\\Forecast\\Forecast;\nuse Jiminny\\Jobs\\Crm\\MatchActivitiesToNewOpportunity;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Crm\\BusinessProcess;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Models\\Crm\\Profile;\nuse Jiminny\\Models\\Opportunity;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DealFieldsService;\nuse Jiminny\\Services\\Crm\\Hubspot\\OpportunitySyncStrategy\\HubspotSingleSyncStrategy;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Services\\Crm\\OpportunitySyncStrategyResolver;\nuse Jiminny\\Utils\\CurrencyFormatter;\n\n/**\n * Optimized sync methods for better performance\n * These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains\n */\ntrait OpportunitySyncTrait\n{\n private const int BATCH_SIZE = 100;\n private const int BATCH_PROCESS_SIZE = 800;\n\n protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;\n protected CrmEntityRepository $crmEntityRepository;\n protected DealFieldsService $dealFieldsService;\n\n private ?array $cachedClosedDealStages = null;\n private array $cachedBusinessProcesses = [];\n private array $cachedStages = [];\n /** @var array<string, array<string>> keyed by config id */\n private array $cachedOpportunitySyncableFields = [];\n /** @var array<string, mixed> keyed by configId:ownerId */\n private array $cachedOwnerProfiles = [];\n /** @var array<string, mixed> keyed by configId:businessProcessId */\n private array $cachedRecordTypes = [];\n\n public function syncOpportunities(array $parameters, ?string $strategy = null): int\n {\n $startTime = microtime(true);\n $strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);\n $parameters['config'] = $this->config;\n $syncCount = 0;\n $reportedTotal = 0;\n $lastSyncedId = [];\n $strategyNames = [];\n\n try {\n foreach ($strategies as $strategyName => $syncStrategy) {\n $strategyNames[] = $strategyName;\n $this->logger->info(\n '[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' . $strategyName,\n ['team' => $this->team->getId()]\n );\n\n $total = 0;\n $lastId = null;\n $buffer = [];\n\n // HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies\n foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {\n $buffer[] = $hsOpportunity;\n\n // process every 800 rows (fits < 1 000 association limit)\n if (\\count($buffer) >= self::BATCH_PROCESS_SIZE) {\n $syncCount += $this->processOpportunityBatch($buffer);\n $buffer = [];\n }\n }\n\n // leftovers\n if ($buffer) {\n $syncCount += $this->processOpportunityBatch($buffer);\n }\n\n $reportedTotal += $total;\n $lastSyncedId = $lastId;\n }\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException | CrmException $e) {\n $this->handleSyncException($e, $parameters);\n }\n\n $durationMs = round((microtime(true) - $startTime) * 1000, 2);\n $this->logger->info(\n '[HubSpot] Synced opportunities',\n [\n 'team' => $this->team->getId(),\n 'strategies' => implode(',', $strategyNames),\n 'sync_count' => $syncCount,\n 'total' => $reportedTotal,\n 'last_synced_id' => $lastSyncedId,\n 'duration_ms' => $durationMs,\n ]\n );\n\n return $reportedTotal;\n }\n\n private function handleSyncException(\\Throwable $e, array $parameters): void\n {\n if (($parameters['since'] ?? null) instanceof Carbon) {\n $parameters['since'] = $parameters['since']->toDateTimeString();\n }\n $parameters['config'] = $this->config->getId();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [\n 'teamId' => $this->team->getUuid(),\n 'parameters' => $parameters,\n 'reason' => $e->getMessage(),\n ]);\n }\n\n /**\n * @inheritdoc\n */\n public function syncOpportunity(string $crmId): ?Opportunity\n {\n $strategy = $this->opportunitySyncStrategyResolver->resolve(\n $this->config,\n OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,\n );\n\n $parameters = [\n 'config' => $this->config,\n 'crm_id' => $crmId,\n ];\n\n try {\n if (! $strategy instanceof HubspotSingleSyncStrategy) {\n throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');\n }\n\n $hsOpportunity = $strategy->fetchOpportunity($parameters);\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);\n\n return $this->importOrUpdateOpportunity($hsOpportunity);\n }\n\n /**\n * Process webhook-collected opportunity batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportOpportunityBatch jobs for batch processing.\n *\n * @return int Number of opportunity IDs dispatched to jobs\n */\n public function batchSyncOpportunities(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,\n $configId\n );\n }\n\n /**\n * Import a batch of opportunities by their CRM IDs.\n * Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().\n *\n * @param array<string> $crmIds HubSpot deal CRM IDs\n *\n * @return array{success: array, failed_ids: array, errors?: array<string, string>}\n */\n public function importOpportunityBatchByIds(array $crmIds): array\n {\n $fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);\n\n $allDeals = [];\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n $deals = $this->client->getOpportunitiesByIds($chunk, $fields);\n foreach ($deals as $deal) {\n $allDeals[] = $deal;\n }\n }\n\n // IDs not returned by HubSpot are likely deleted or inaccessible deals.\n // These are not failures — retrying won't bring them back.\n $fetchedIds = array_map('strval', array_column($allDeals, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allDeals),\n ]);\n }\n\n if (empty($allDeals)) {\n return ['success' => [], 'failed_ids' => []];\n }\n\n return $this->importOpportunityBatch($allDeals);\n }\n\n private function getClosedDealStages(): array\n {\n if ($this->cachedClosedDealStages !== null) {\n return $this->cachedClosedDealStages;\n }\n\n $stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);\n $data = [\n 'lost' => [],\n 'won' => [],\n ];\n\n foreach ($stages as $stage) {\n if ($stage->probability == 0.00) {\n $data['lost'][] = $stage->crm_provider_id;\n }\n if ($stage->probability == 100.00) {\n $data['won'][] = $stage->crm_provider_id;\n }\n }\n\n $this->cachedClosedDealStages = $data;\n\n return $data;\n }\n\n /**\n * Import deals into the database with pre-fetched associations.\n *\n * API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT\n * caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()\n * where Laravel retries the whole job with backoff. After all retries exhausted,\n * failed() requeues all IDs to Redis.\n *\n * The per-deal loop catches exceptions individually. A deal can end up in three states:\n * - success: imported/updated successfully\n * - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)\n * These are permanent issues — retrying won't fix them.\n * - skipped (null): missing dependencies (no account, unknown pipeline/stage).\n * This is acceptable — the deal cannot be imported until those exist.\n */\n private function importOpportunityBatch(array $deals): array\n {\n $syncedOpportunities = [\n 'success' => [],\n 'failed_ids' => [],\n ];\n $dealIds = array_column($deals, 'id');\n $batchStart = microtime(true);\n $slowDeals = [];\n\n // Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the\n // queue job retries the whole batch and eventually requeues all deal IDs back to Redis.\n try {\n $companyAssocStart = microtime(true);\n $companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');\n $companyAssocMs = (int) round((microtime(true) - $companyAssocStart) * 1000);\n\n $contactAssocStart = microtime(true);\n $contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');\n $contactAssocMs = (int) round((microtime(true) - $contactAssocStart) * 1000);\n\n $prepareStart = microtime(true);\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n $prepareTimings = [];\n $associationsData = $this->prepareAssociatedEntities(\n $companyAssociations,\n $contactAssociations,\n $prepareTimings\n );\n $prepareMs = (int) round((microtime(true) - $prepareStart) * 1000);\n\n $missingCompanies = count(array_diff(\n $allCompanyIds,\n array_keys($associationsData['company_id_mappings'] ?? [])\n ));\n $missingContacts = count(array_diff(\n $allContactIds,\n array_keys($associationsData['contact_id_mappings'] ?? [])\n ));\n\n $existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(\n $this->config,\n array_map('strval', $dealIds)\n );\n $existingCrmIdSet = array_flip($existingCrmIds);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [\n 'teamId' => $this->team->getId(),\n 'dealCount' => count($dealIds),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n $loopStart = microtime(true);\n foreach ($deals as $deal) {\n $dealStart = microtime(true);\n\n try {\n $deal['associations'] = $this->prepareAssociationsForOpportunity(\n $deal['id'],\n $companyAssociations,\n $contactAssociations,\n $associationsData\n );\n\n $syncedOpportunity = $this->importOrUpdateOpportunity(\n $deal,\n isset($existingCrmIdSet[(string) $deal['id']])\n );\n if ($syncedOpportunity) {\n $syncedOpportunities['success'][] = $syncedOpportunity;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $deal['id'],\n 'error' => $e->getMessage(),\n ]);\n $syncedOpportunities['failed_ids'][] = $deal['id'];\n $syncedOpportunities['errors'][$deal['id']] = $e->getMessage();\n }\n\n $dealMs = (int) round((microtime(true) - $dealStart) * 1000);\n if ($dealMs > 1000) {\n $slowDeals[] = ['crmId' => $deal['id'], 'ms' => $dealMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importOpportunityBatch timing', [\n 'teamId' => $this->team->getId(),\n 'deal_count' => count($deals),\n 'total_ms' => $totalMs,\n 'company_assoc_api_ms' => $companyAssocMs,\n 'contact_assoc_api_ms' => $contactAssocMs,\n 'prepare_entities_ms' => $prepareMs,\n 'prepare_accounts_ms' => $prepareTimings['accounts_ms'],\n 'prepare_contacts_ms' => $prepareTimings['contacts_ms'],\n 'total_companies' => count($allCompanyIds),\n 'missing_companies' => $missingCompanies,\n 'total_contacts' => count($allContactIds),\n 'missing_contacts' => $missingContacts,\n 'deals_loop_ms' => $loopMs,\n 'avg_deal_ms' => ! empty($deals) ? (int) round($loopMs / count($deals)) : 0,\n 'slow_deals_count' => count($slowDeals),\n 'slow_deals' => array_slice($slowDeals, 0, 10),\n ]);\n\n return $syncedOpportunities;\n }\n\n /**\n * Prepare associated entities for opportunities with optimized batch processing\n * Returns structured data with CRM ID to DB ID mappings for each opportunity\n */\n private function prepareAssociatedEntities(\n array $companyAssociations,\n array $contactAssociations,\n array &$timings = []\n ): array {\n // Step 1: Collect all unique company and contact IDs from associations\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n\n // Step 2: Batch sync missing entities and get CRM ID to DB ID mappings\n $companyIdMappings = [];\n $contactIdMappings = [];\n $accountsMs = 0;\n $contactsMs = 0;\n\n if (! empty($allCompanyIds)) {\n $start = microtime(true);\n $companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);\n $accountsMs = (int) round((microtime(true) - $start) * 1000);\n }\n\n if (! empty($allContactIds)) {\n $start = microtime(true);\n $contactIdMappings = $this->prepareAssociatedContacts($allContactIds);\n $contactsMs = (int) round((microtime(true) - $start) * 1000);\n }\n\n $timings = [\n 'accounts_ms' => $accountsMs,\n 'contacts_ms' => $contactsMs,\n ];\n\n return [\n 'company_id_mappings' => $companyIdMappings,\n 'contact_id_mappings' => $contactIdMappings,\n ];\n }\n\n /**\n * Flatten association data to get unique IDs\n */\n private function flattenAssociationIds(array $associations): array\n {\n $ids = [];\n foreach ($associations as $dealAssociations) {\n if (is_array($dealAssociations)) {\n foreach ($dealAssociations as $id) {\n $ids[$id] = true;\n }\n }\n }\n\n return array_keys($ids);\n }\n\n /**\n * Batch sync missing accounts\n */\n private function prepareAssociatedAccounts(array $companyIds): array\n {\n // Find which accounts already exist (lean covering-index lookup)\n $existingAccountsData = $this->crmEntityRepository\n ->getExistingAccountIdsMap($this->config, $companyIds);\n\n $missingCompanyIds = array_diff($companyIds, array_keys($existingAccountsData));\n\n if (empty($missingCompanyIds)) {\n return $existingAccountsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [\n 'teamId' => $this->team->getUuid(),\n 'total_companies' => count($companyIds),\n 'existing_companies' => count($existingAccountsData),\n 'missing_companies' => count($missingCompanyIds),\n ]);\n\n // we already have limit on opportunity ids count\n // Initialize variable before try block\n $syncedAccountsData = [];\n\n try {\n $syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [\n 'size' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedAccountsData = [];\n }\n\n return $existingAccountsData + $syncedAccountsData;\n }\n\n /**\n * Prepare associated contacts - find existing and sync missing ones\n * Returns mapping of CRM ID to DB ID\n */\n private function prepareAssociatedContacts(array $contactIds): array\n {\n // Find which contacts already exist (lean covering-index lookup)\n $existingContactsData = $this->crmEntityRepository\n ->getExistingContactIdsMap($this->config, $contactIds);\n\n $missingContactIds = array_diff($contactIds, array_keys($existingContactsData));\n\n if (empty($missingContactIds)) {\n return $existingContactsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [\n 'teamId' => $this->team->getUuid(),\n 'total_contacts' => count($contactIds),\n 'existing_contacts' => count($existingContactsData),\n 'missing_contacts' => count($missingContactIds),\n ]);\n\n // Sync missing contacts using batch API\n try {\n $syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [\n 'size' => count($missingContactIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedContactsData = [];\n }\n\n return $existingContactsData + $syncedContactsData;\n }\n\n private function batchSyncCrmObjects(string $objectType, array $crmIds): array\n {\n $syncObjects = [];\n $crmObjectIds = array_values($crmIds);\n\n foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {\n try {\n $objects = $objectType === 'companies' ?\n $this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :\n $this->client->getContactsByIds($chunk, $this->getContactFields());\n\n foreach ($objects as $objectId => $objectData) {\n $this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [\n 'requested_count' => count($chunk),\n 'synced_count' => count($objects),\n ]);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [\n 'ids' => $chunk,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncObjects;\n }\n\n private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void\n {\n try {\n $object = $objectType === 'companies' ?\n $this->importAccount($objectData) :\n $this->importContact($objectData);\n\n if ($object) {\n $syncObjects[$object->getCrmProviderId()] = $object->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [\n 'id' => $objectId,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n /**\n * Prepare associations for a single opportunity\n *\n * The return value is an array with the following structure:\n * [\n * 'companies' => [\n * $companyCrmId => $companyId,\n * ...\n * ],\n * 'contacts' => [\n * $contactCrmId => $contactId,\n * ...\n * ],\n * 'account_id' => $accountId,\n * ]\n */\n private function prepareAssociationsForOpportunity(\n string $oppCrmId,\n array $companyAssociations,\n array $contactAssociations,\n array $associationsData\n ): array {\n $associations = [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n\n $oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];\n foreach ($oppCompanyIds as $companyCrmId) {\n if (isset($associationsData['company_id_mappings'][$companyCrmId])) {\n $associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];\n\n // Set primary account (first company becomes primary account)\n if ($associations['account_id'] === null) {\n $associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];\n }\n }\n }\n\n $oppContactIds = $contactAssociations[$oppCrmId] ?? [];\n foreach ($oppContactIds as $contactCrmId) {\n if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {\n $associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];\n }\n }\n\n return $associations;\n }\n\n /**\n * Update only associations for an opportunity\n */\n private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void\n {\n // Update contact associations\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n // Update company (account) associations\n $this->updateOpportunityAccount($opportunity, $associations['account_id']);\n }\n\n /**\n * Remove all contact associations from an opportunity\n */\n private function removeAllOpportunityContacts(Opportunity $opportunity): void\n {\n $currentCount = (int) $opportunity->contacts()->count();\n\n if ($currentCount > 0) {\n $opportunity->contacts()->detach();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_count' => $currentCount,\n ]);\n }\n }\n\n private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void\n {\n if ($accountId === null) {\n // No account ID provided - keep current account\n return;\n }\n\n $currentAccountId = $opportunity->getAccountId();\n\n // Only update if account has changed\n if ($currentAccountId !== $accountId) {\n $opportunity->account_id = $accountId;\n $opportunity->save();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [\n 'opportunity_id' => $opportunity->getId(),\n 'old_account_id' => $currentAccountId,\n 'new_account_id' => $accountId,\n ]);\n }\n }\n\n /**\n * Find existing opportunities by external IDs (OPTIMIZED VERSION)\n * Uses batch query for better performance\n */\n private function findExistingOpportunities(array $crmIds): Collection\n {\n return $this->crmEntityRepository\n ->findOpportunitiesByExternalIds($this->config, $crmIds);\n }\n\n private function processOpportunityBatch(array $opportunities): int\n {\n $syncedOpportunities = $this->importOpportunityBatch($opportunities);\n\n return count($syncedOpportunities['success'] ?? []);\n }\n\n /**\n * Convert single deal associations from HubSpot format to internal format\n * Handles both HubSpot SDK objects and array formats\n *\n * @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed\n *\n * @return array Processed associations with DB IDs\n */\n private function convertDealAssociations(array $opportunityAssociations): array\n {\n $associations = $this->initializeAssociationsStructure();\n\n if (empty($opportunityAssociations)) {\n return $associations;\n }\n\n $associationIds = $this->extractAssociationIds($opportunityAssociations);\n\n $this->processCompanyAssociations($associationIds, $associations);\n $this->processContactAssociations($associationIds, $associations);\n\n return $associations;\n }\n\n private function initializeAssociationsStructure(): array\n {\n return [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n }\n\n private function extractAssociationIds(array $opportunityAssociations): array\n {\n $associationIds = [];\n\n foreach ($opportunityAssociations as $type => $associationData) {\n if (! empty($associationData)) {\n $associationIds[$type] = $this->convertSingleDealAssociations($associationData);\n }\n }\n\n return $associationIds;\n }\n\n private function processCompanyAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['companies'])) {\n return;\n }\n\n $companyId = $associationIds['companies'][0];\n $account = $this->findOrSyncAccount($companyId);\n\n if ($account instanceof Account) {\n $associations['companies'][$companyId] = $account->getId();\n $associations['account_id'] = $account->getId();\n }\n }\n\n private function processContactAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['contacts'])) {\n return;\n }\n\n foreach ($associationIds['contacts'] as $contactId) {\n $contact = $this->findOrSyncContact($contactId);\n\n if ($contact instanceof Contact) {\n $associations['contacts'][$contactId] = $contact->getId();\n }\n }\n }\n\n private function findOrSyncAccount(string $companyId): ?Account\n {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);\n\n if (! $account instanceof Account) {\n $account = $this->syncAccount($companyId);\n }\n\n return $account;\n }\n\n private function findOrSyncContact(string $contactId): ?Contact\n {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);\n\n if (! $contact instanceof Contact) {\n $contact = $this->syncContact($contactId);\n }\n\n return $contact;\n }\n\n private function convertSingleDealAssociations($opportunityAssociations = null): array\n {\n $associationData = [];\n\n if ($opportunityAssociations === null) {\n return $associationData;\n }\n\n // Handle array input (from extractAssociationIds)\n if (is_array($opportunityAssociations)) {\n return $opportunityAssociations;\n }\n\n // Handle CollectionResponseAssociatedId object\n if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {\n foreach ($opportunityAssociations->getResults() as $association) {\n $associationData[] = $association->getId();\n }\n }\n\n return $associationData;\n }\n\n private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity\n {\n if (empty($crmData['properties'])) {\n return null;\n }\n\n $properties = $crmData['properties'];\n\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = (string) $properties['hubspot_owner_id'];\n $profile = $this->getCachedOwnerProfile($ownerId);\n }\n\n $associations = $crmData['associations'] ?? [];\n $accountId = $this->resolveAccountId($associations);\n\n // Only fetch account if we need it for user_id fallback\n $accountUserId = null;\n if ($profile?->getUserId() === null && $accountId !== null) {\n $accountUserId = $this->crmEntityRepository\n ->findAccountByConfigurationAndId(\n $this->config,\n $accountId\n )?->getUserId();\n }\n\n $crmId = (string) $crmData['id'];\n\n if (($profile?->getUserId() === null) && ($accountUserId === null)) {\n $this->logger->error(\n '[HubSpot] Skip import, no user_id found',\n [\n 'id' => $crmId,\n ]\n );\n\n return null;\n }\n\n $opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(\n $this->config,\n $crmId\n );\n\n if ($opportunityExists) {\n return $this->updateOpportunity(\n $crmId,\n $properties,\n $associations,\n $accountUserId\n );\n }\n\n return $this->createOpportunity(\n $crmId,\n $properties,\n $associations,\n $accountUserId,\n );\n }\n\n /**\n * Create new opportunity\n */\n private function createOpportunity(\n string $crmId,\n array $properties,\n array $associations,\n ?int $accountUserId = null\n ): ?Opportunity {\n $accountId = $this->resolveAccountId($associations);\n if (! $accountId) {\n return null;\n }\n\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n if (! $businessProcess) {\n return null;\n }\n\n $stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);\n if (! $stage) {\n return null;\n }\n\n $data = $this->buildOpportunityData(\n $properties,\n $accountId,\n $businessProcess,\n $stage,\n $accountUserId\n );\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n if ($opportunity->wasRecentlyCreated) {\n MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());\n }\n\n return $opportunity;\n }\n\n /**\n * Update existing opportunity\n */\n private function updateOpportunity(\n string $crmId,\n array $properties,\n array $associations,\n ?int $accountUserId = null\n ): Opportunity {\n $accountId = $this->resolveAccountId($associations);\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n $stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;\n\n $data = $this->buildOpportunityData(\n $properties,\n $accountId,\n $businessProcess,\n $stage,\n $accountUserId\n );\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->updateOpportunityAssociations($opportunity, $associations);\n\n return $opportunity;\n }\n\n private function resolveAccountId(array $associations): ?int\n {\n if (! empty($associations['account_id'])) {\n return $associations['account_id'];\n }\n\n if (empty($associations)) {\n return null;\n }\n\n // Fallback: use first company as account (currently SDK returns one company)\n foreach ($associations['companies'] as $accountId) {\n return $accountId;\n }\n\n return null;\n }\n\n private function buildOpportunityData(\n array $properties,\n ?int $accountId,\n ?BusinessProcess $businessProcess,\n ?Stage $stage,\n ?int $accountUserId = null\n ): array {\n $ownerId = null;\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->getCachedOwnerProfile((string) $ownerId);\n }\n\n $userId = $profile?->getUserId() ?? $accountUserId;\n\n $name = 'Unknown';\n if (isset($properties['dealname'])) {\n $name = mb_strimwidth($properties['dealname'], 0, 128);\n }\n\n $amount = $this->resolveAmount($properties);\n $currency = $properties['deal_currency_code'] ?? null;\n\n $closeDate = null;\n if (! empty($properties['closedate'])) {\n $closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');\n }\n\n $remotelyCreatedAt = null;\n if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {\n $date = $this->parseCleanDatetime($properties['createdate']);\n $remotelyCreatedAt = $date?->format('Y-m-d H:i:s');\n }\n\n $closedStages = $this->getClosedDealStages();\n $isWon = in_array($properties['dealstage'], $closedStages['won']);\n $isLost = in_array($properties['dealstage'], $closedStages['lost']);\n\n $data = [\n 'team_id' => $this->team->getId(),\n 'user_id' => $userId,\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'value' => ! empty($amount) ? $amount : null,\n 'currency_code' => CurrencyFormatter::formatCode($currency),\n 'close_date' => $closeDate,\n 'is_closed' => $isWon || $isLost,\n 'is_won' => $isWon,\n 'remotely_created_at' => $remotelyCreatedAt,\n 'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),\n 'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),\n ];\n\n if ($accountId) {\n $data['account_id'] = $accountId;\n }\n\n if ($stage) {\n $data['stage_id'] = $stage->id;\n }\n\n if ($businessProcess) {\n $recordType = $this->getCachedBusinessProcessRecordType($businessProcess);\n if ($recordType) {\n $data['record_type_id'] = $recordType->id;\n }\n }\n\n return $data;\n }\n\n private function getCachedOwnerProfile(string $ownerId): ?Profile\n {\n $cacheKey = $this->config->getId() . ':' . $ownerId;\n if (array_key_exists($cacheKey, $this->cachedOwnerProfiles)) {\n return $this->cachedOwnerProfiles[$cacheKey];\n }\n\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, $ownerId);\n $this->cachedOwnerProfiles[$cacheKey] = $profile;\n\n return $profile;\n }\n\n private function getCachedBusinessProcessRecordType(BusinessProcess $businessProcess): mixed\n {\n $cacheKey = $this->config->getId() . ':' . $businessProcess->getId();\n if (array_key_exists($cacheKey, $this->cachedRecordTypes)) {\n return $this->cachedRecordTypes[$cacheKey];\n }\n\n $recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);\n $this->cachedRecordTypes[$cacheKey] = $recordType;\n\n return $recordType;\n }\n\n private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess\n {\n if ($pipelineId === null) {\n return null;\n }\n\n $cacheKey = $this->getBusinessProcessCacheKey($pipelineId);\n if (isset($this->cachedBusinessProcesses[$cacheKey])) {\n return $this->cachedBusinessProcesses[$cacheKey];\n }\n\n $businessProcess = $this->getBusinessProcess($pipelineId);\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->importStages();\n $this->cachedClosedDealStages = null;\n $businessProcess = $this->getBusinessProcess($pipelineId);\n }\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->logger->info(\n '[HubSpot] Deal is not attached to a pipeline',\n [\n 'pipeline' => $pipelineId]\n );\n }\n\n $this->cachedBusinessProcesses[$cacheKey] = $businessProcess;\n\n return $businessProcess;\n }\n\n private function getBusinessProcess(string $pipelineId): ?BusinessProcess\n {\n return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);\n }\n\n private function getBusinessProcessCacheKey(string $pipelineId): string\n {\n return $this->config->getId() . '_' . $pipelineId;\n }\n\n private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage\n {\n if (empty($stageId)) {\n return null;\n }\n\n $cacheKey = $this->config->getId() . ':' . $businessProcess->getId() . ':' . $stageId;\n if (isset($this->cachedStages[$cacheKey])) {\n return $this->cachedStages[$cacheKey];\n }\n\n $stage = $this->crmEntityRepository->getPipelineStageByConditions(\n $businessProcess,\n [\n 'crm_provider_id' => $stageId,\n 'type' => Stage::TYPE_OPPORTUNITY,\n ]\n );\n\n if ($stage === null) {\n $this->importStages(null, $stageId);\n $this->cachedClosedDealStages = null;\n }\n\n if ($stage === null) {\n $this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);\n }\n\n $this->cachedStages[$cacheKey] = $stage;\n\n return $stage;\n }\n\n private function resolveAmount(array $properties): ?string\n {\n $amount = null;\n if (! empty($properties['amount'])) {\n $amount = str_replace(',', '', $properties['amount']);\n }\n\n if ($this->config->hasDefaultCurrencyFieldSet()) {\n $valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();\n $amount = $properties[$valueFieldName] ?? $amount;\n }\n\n return $amount;\n }\n\n private function parseCleanDatetime(string $datetime): ?Carbon\n {\n // Treat pre-1980 values as invalid\n $minValidDate = Carbon::parse('1980-01-01 00:00:00');\n\n try {\n $date = Carbon::parse($datetime);\n\n if ($minValidDate->gt($date)) {\n return null;\n }\n\n return $date;\n } catch (Exception) {\n return null; // On parse error, treat as null\n }\n }\n\n private function resolveDealProbability(?string $stageProbability): int\n {\n if ($stageProbability === null) {\n return 0;\n }\n\n $probability = (float) $stageProbability;\n\n return $probability > 1 ? 0 : (int) ($probability * 100);\n }\n\n private function resolveForecastCategory(?string $forecastCategory): string\n {\n if (! $forecastCategory) {\n return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;\n }\n\n $forecastCategory = str_replace('_', ' ', $forecastCategory);\n\n return ucwords(strtolower($forecastCategory));\n }\n\n private function importExternalFieldData(array $properties, int $opportunityId): void\n {\n $this->importOpportunityCrmFieldData(\n $properties,\n $this->getCachedOpportunitySyncableFields(),\n $opportunityId\n );\n }\n\n private function getCachedOpportunitySyncableFields(): array\n {\n $cacheKey = (string) $this->config->getId();\n if (! isset($this->cachedOpportunitySyncableFields[$cacheKey])) {\n $this->cachedOpportunitySyncableFields[$cacheKey] = $this->getOpportunitySyncableFields();\n }\n\n return $this->cachedOpportunitySyncableFields[$cacheKey];\n }\n\n private function importOpportunityContacts(Opportunity $opportunity, array $associations): void\n {\n // Handle empty or missing contact associations\n if (empty($associations)) {\n // Remove all existing contact associations if none provided\n $this->removeAllOpportunityContacts($opportunity);\n\n return;\n }\n\n // Use differential sync approach for better performance and accuracy\n $this->syncOpportunityContactsDifferential($opportunity, $associations);\n }\n\n /**\n * Sync opportunity contacts using differential approach\n * This compares current vs new associations and only makes necessary changes\n */\n private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void\n {\n $currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);\n $contactAssociationIds = array_keys($contactAssociations);\n\n $contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);\n $contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);\n\n if (empty($contactsToAdd) && empty($contactsToRemove)) {\n return;\n }\n\n $this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);\n\n $this->removeContactAssociations($opportunity, $contactsToRemove);\n $this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);\n }\n\n private function getCurrentContactCrmIds(Opportunity $opportunity): array\n {\n return $opportunity->contacts()\n ->pluck('contacts.crm_provider_id')\n ->toArray();\n }\n\n private function logContactAssociationChanges(\n Opportunity $opportunity,\n array $currentContactCrmIds,\n array $contactAssociations,\n array $contactsToAdd,\n array $contactsToRemove\n ): void {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [\n 'opportunity_id' => $opportunity->getId(),\n 'current_contacts' => $currentContactCrmIds,\n 'new_contacts' => $contactAssociations,\n 'contacts_to_add' => $contactsToAdd,\n 'contacts_to_remove' => $contactsToRemove,\n ]);\n }\n\n private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void\n {\n if (empty($contactsToRemove)) {\n return;\n }\n\n $contactsToDetach = $opportunity->contacts()\n ->whereIn('contacts.crm_provider_id', $contactsToRemove)\n ->pluck('contacts.id')\n ->toArray();\n\n if (! empty($contactsToDetach)) {\n $opportunity->contacts()->detach($contactsToDetach);\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_contact_crm_ids' => $contactsToRemove,\n 'removed_contact_count' => count($contactsToDetach),\n ]);\n }\n }\n\n private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void\n {\n if (empty($contactsToAdd)) {\n return;\n }\n\n $contactsAdded = [];\n foreach ($contactsToAdd as $crmId) {\n $id = $contactAssociations[$crmId];\n\n if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {\n $contactsAdded[] = $crmId;\n }\n }\n\n $this->logAddedContacts($opportunity, $contactsAdded);\n }\n\n private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool\n {\n try {\n return $this->performContactAttachment($opportunity, $id, $crmId);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [\n 'opportunity_id' => $opportunity->getId(),\n 'contact_crm_id' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n private function performContactAttachment(Opportunity $opportunity, int $contactId, string $crmId): bool\n {\n try {\n $opportunity->contacts()->attach($contactId, [\n 'crm_provider_id' => $crmId,\n ]);\n\n return true;\n } catch (\\Illuminate\\Database\\QueryException $e) {\n if (str_contains($e->getMessage(), 'Duplicate entry')) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [\n 'contact_id' => $contactId,\n 'contact_crm_id' => $crmId,\n 'opportunity_id' => $opportunity->getId(),\n ]);\n\n return false;\n }\n\n throw $e;\n }\n }\n\n private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void\n {\n if (! empty($contactsAdded)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'added_contact_crm_ids' => $contactsAdded,\n 'added_contacts_count' => count($contactsAdded),\n ]);\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\CollectionResponseAssociatedId;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Models\\Account;\nuse Exception;\nuse Jiminny\\Component\\DealInsights\\Forecast\\Forecast;\nuse Jiminny\\Jobs\\Crm\\MatchActivitiesToNewOpportunity;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Crm\\BusinessProcess;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Models\\Crm\\Profile;\nuse Jiminny\\Models\\Opportunity;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DealFieldsService;\nuse Jiminny\\Services\\Crm\\Hubspot\\OpportunitySyncStrategy\\HubspotSingleSyncStrategy;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Services\\Crm\\OpportunitySyncStrategyResolver;\nuse Jiminny\\Utils\\CurrencyFormatter;\n\n/**\n * Optimized sync methods for better performance\n * These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains\n */\ntrait OpportunitySyncTrait\n{\n private const int BATCH_SIZE = 100;\n private const int BATCH_PROCESS_SIZE = 800;\n\n protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;\n protected CrmEntityRepository $crmEntityRepository;\n protected DealFieldsService $dealFieldsService;\n\n private ?array $cachedClosedDealStages = null;\n private array $cachedBusinessProcesses = [];\n private array $cachedStages = [];\n /** @var array<string, array<string>> keyed by config id */\n private array $cachedOpportunitySyncableFields = [];\n /** @var array<string, mixed> keyed by configId:ownerId */\n private array $cachedOwnerProfiles = [];\n /** @var array<string, mixed> keyed by configId:businessProcessId */\n private array $cachedRecordTypes = [];\n\n public function syncOpportunities(array $parameters, ?string $strategy = null): int\n {\n $startTime = microtime(true);\n $strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);\n $parameters['config'] = $this->config;\n $syncCount = 0;\n $reportedTotal = 0;\n $lastSyncedId = [];\n $strategyNames = [];\n\n try {\n foreach ($strategies as $strategyName => $syncStrategy) {\n $strategyNames[] = $strategyName;\n $this->logger->info(\n '[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' . $strategyName,\n ['team' => $this->team->getId()]\n );\n\n $total = 0;\n $lastId = null;\n $buffer = [];\n\n // HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies\n foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {\n $buffer[] = $hsOpportunity;\n\n // process every 800 rows (fits < 1 000 association limit)\n if (\\count($buffer) >= self::BATCH_PROCESS_SIZE) {\n $syncCount += $this->processOpportunityBatch($buffer);\n $buffer = [];\n }\n }\n\n // leftovers\n if ($buffer) {\n $syncCount += $this->processOpportunityBatch($buffer);\n }\n\n $reportedTotal += $total;\n $lastSyncedId = $lastId;\n }\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException | CrmException $e) {\n $this->handleSyncException($e, $parameters);\n }\n\n $durationMs = round((microtime(true) - $startTime) * 1000, 2);\n $this->logger->info(\n '[HubSpot] Synced opportunities',\n [\n 'team' => $this->team->getId(),\n 'strategies' => implode(',', $strategyNames),\n 'sync_count' => $syncCount,\n 'total' => $reportedTotal,\n 'last_synced_id' => $lastSyncedId,\n 'duration_ms' => $durationMs,\n ]\n );\n\n return $reportedTotal;\n }\n\n private function handleSyncException(\\Throwable $e, array $parameters): void\n {\n if (($parameters['since'] ?? null) instanceof Carbon) {\n $parameters['since'] = $parameters['since']->toDateTimeString();\n }\n $parameters['config'] = $this->config->getId();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [\n 'teamId' => $this->team->getUuid(),\n 'parameters' => $parameters,\n 'reason' => $e->getMessage(),\n ]);\n }\n\n /**\n * @inheritdoc\n */\n public function syncOpportunity(string $crmId): ?Opportunity\n {\n $strategy = $this->opportunitySyncStrategyResolver->resolve(\n $this->config,\n OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,\n );\n\n $parameters = [\n 'config' => $this->config,\n 'crm_id' => $crmId,\n ];\n\n try {\n if (! $strategy instanceof HubspotSingleSyncStrategy) {\n throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');\n }\n\n $hsOpportunity = $strategy->fetchOpportunity($parameters);\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);\n\n return $this->importOrUpdateOpportunity($hsOpportunity);\n }\n\n /**\n * Process webhook-collected opportunity batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportOpportunityBatch jobs for batch processing.\n *\n * @return int Number of opportunity IDs dispatched to jobs\n */\n public function batchSyncOpportunities(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,\n $configId\n );\n }\n\n /**\n * Import a batch of opportunities by their CRM IDs.\n * Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().\n *\n * @param array<string> $crmIds HubSpot deal CRM IDs\n *\n * @return array{success: array, failed_ids: array, errors?: array<string, string>}\n */\n public function importOpportunityBatchByIds(array $crmIds): array\n {\n $fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);\n\n $allDeals = [];\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n $deals = $this->client->getOpportunitiesByIds($chunk, $fields);\n foreach ($deals as $deal) {\n $allDeals[] = $deal;\n }\n }\n\n // IDs not returned by HubSpot are likely deleted or inaccessible deals.\n // These are not failures — retrying won't bring them back.\n $fetchedIds = array_map('strval', array_column($allDeals, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allDeals),\n ]);\n }\n\n if (empty($allDeals)) {\n return ['success' => [], 'failed_ids' => []];\n }\n\n return $this->importOpportunityBatch($allDeals);\n }\n\n private function getClosedDealStages(): array\n {\n if ($this->cachedClosedDealStages !== null) {\n return $this->cachedClosedDealStages;\n }\n\n $stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);\n $data = [\n 'lost' => [],\n 'won' => [],\n ];\n\n foreach ($stages as $stage) {\n if ($stage->probability == 0.00) {\n $data['lost'][] = $stage->crm_provider_id;\n }\n if ($stage->probability == 100.00) {\n $data['won'][] = $stage->crm_provider_id;\n }\n }\n\n $this->cachedClosedDealStages = $data;\n\n return $data;\n }\n\n /**\n * Import deals into the database with pre-fetched associations.\n *\n * API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT\n * caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()\n * where Laravel retries the whole job with backoff. After all retries exhausted,\n * failed() requeues all IDs to Redis.\n *\n * The per-deal loop catches exceptions individually. A deal can end up in three states:\n * - success: imported/updated successfully\n * - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)\n * These are permanent issues — retrying won't fix them.\n * - skipped (null): missing dependencies (no account, unknown pipeline/stage).\n * This is acceptable — the deal cannot be imported until those exist.\n */\n private function importOpportunityBatch(array $deals): array\n {\n $syncedOpportunities = [\n 'success' => [],\n 'failed_ids' => [],\n ];\n $dealIds = array_column($deals, 'id');\n $batchStart = microtime(true);\n $slowDeals = [];\n\n // Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the\n // queue job retries the whole batch and eventually requeues all deal IDs back to Redis.\n try {\n $companyAssocStart = microtime(true);\n $companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');\n $companyAssocMs = (int) round((microtime(true) - $companyAssocStart) * 1000);\n\n $contactAssocStart = microtime(true);\n $contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');\n $contactAssocMs = (int) round((microtime(true) - $contactAssocStart) * 1000);\n\n $prepareStart = microtime(true);\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n $prepareTimings = [];\n $associationsData = $this->prepareAssociatedEntities(\n $companyAssociations,\n $contactAssociations,\n $prepareTimings\n );\n $prepareMs = (int) round((microtime(true) - $prepareStart) * 1000);\n\n $missingCompanies = count(array_diff(\n $allCompanyIds,\n array_keys($associationsData['company_id_mappings'] ?? [])\n ));\n $missingContacts = count(array_diff(\n $allContactIds,\n array_keys($associationsData['contact_id_mappings'] ?? [])\n ));\n\n $existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(\n $this->config,\n array_map('strval', $dealIds)\n );\n $existingCrmIdSet = array_flip($existingCrmIds);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [\n 'teamId' => $this->team->getId(),\n 'dealCount' => count($dealIds),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n $loopStart = microtime(true);\n foreach ($deals as $deal) {\n $dealStart = microtime(true);\n\n try {\n $deal['associations'] = $this->prepareAssociationsForOpportunity(\n $deal['id'],\n $companyAssociations,\n $contactAssociations,\n $associationsData\n );\n\n $syncedOpportunity = $this->importOrUpdateOpportunity(\n $deal,\n isset($existingCrmIdSet[(string) $deal['id']])\n );\n if ($syncedOpportunity) {\n $syncedOpportunities['success'][] = $syncedOpportunity;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $deal['id'],\n 'error' => $e->getMessage(),\n ]);\n $syncedOpportunities['failed_ids'][] = $deal['id'];\n $syncedOpportunities['errors'][$deal['id']] = $e->getMessage();\n }\n\n $dealMs = (int) round((microtime(true) - $dealStart) * 1000);\n if ($dealMs > 1000) {\n $slowDeals[] = ['crmId' => $deal['id'], 'ms' => $dealMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importOpportunityBatch timing', [\n 'teamId' => $this->team->getId(),\n 'deal_count' => count($deals),\n 'total_ms' => $totalMs,\n 'company_assoc_api_ms' => $companyAssocMs,\n 'contact_assoc_api_ms' => $contactAssocMs,\n 'prepare_entities_ms' => $prepareMs,\n 'prepare_accounts_ms' => $prepareTimings['accounts_ms'],\n 'prepare_contacts_ms' => $prepareTimings['contacts_ms'],\n 'total_companies' => count($allCompanyIds),\n 'missing_companies' => $missingCompanies,\n 'total_contacts' => count($allContactIds),\n 'missing_contacts' => $missingContacts,\n 'deals_loop_ms' => $loopMs,\n 'avg_deal_ms' => ! empty($deals) ? (int) round($loopMs / count($deals)) : 0,\n 'slow_deals_count' => count($slowDeals),\n 'slow_deals' => array_slice($slowDeals, 0, 10),\n ]);\n\n return $syncedOpportunities;\n }\n\n /**\n * Prepare associated entities for opportunities with optimized batch processing\n * Returns structured data with CRM ID to DB ID mappings for each opportunity\n */\n private function prepareAssociatedEntities(\n array $companyAssociations,\n array $contactAssociations,\n array &$timings = []\n ): array {\n // Step 1: Collect all unique company and contact IDs from associations\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n\n // Step 2: Batch sync missing entities and get CRM ID to DB ID mappings\n $companyIdMappings = [];\n $contactIdMappings = [];\n $accountsMs = 0;\n $contactsMs = 0;\n\n if (! empty($allCompanyIds)) {\n $start = microtime(true);\n $companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);\n $accountsMs = (int) round((microtime(true) - $start) * 1000);\n }\n\n if (! empty($allContactIds)) {\n $start = microtime(true);\n $contactIdMappings = $this->prepareAssociatedContacts($allContactIds);\n $contactsMs = (int) round((microtime(true) - $start) * 1000);\n }\n\n $timings = [\n 'accounts_ms' => $accountsMs,\n 'contacts_ms' => $contactsMs,\n ];\n\n return [\n 'company_id_mappings' => $companyIdMappings,\n 'contact_id_mappings' => $contactIdMappings,\n ];\n }\n\n /**\n * Flatten association data to get unique IDs\n */\n private function flattenAssociationIds(array $associations): array\n {\n $ids = [];\n foreach ($associations as $dealAssociations) {\n if (is_array($dealAssociations)) {\n foreach ($dealAssociations as $id) {\n $ids[$id] = true;\n }\n }\n }\n\n return array_keys($ids);\n }\n\n /**\n * Batch sync missing accounts\n */\n private function prepareAssociatedAccounts(array $companyIds): array\n {\n // Find which accounts already exist (lean covering-index lookup)\n $existingAccountsData = $this->crmEntityRepository\n ->getExistingAccountIdsMap($this->config, $companyIds);\n\n $missingCompanyIds = array_diff($companyIds, array_keys($existingAccountsData));\n\n if (empty($missingCompanyIds)) {\n return $existingAccountsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [\n 'teamId' => $this->team->getUuid(),\n 'total_companies' => count($companyIds),\n 'existing_companies' => count($existingAccountsData),\n 'missing_companies' => count($missingCompanyIds),\n ]);\n\n // we already have limit on opportunity ids count\n // Initialize variable before try block\n $syncedAccountsData = [];\n\n try {\n $syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [\n 'size' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedAccountsData = [];\n }\n\n return $existingAccountsData + $syncedAccountsData;\n }\n\n /**\n * Prepare associated contacts - find existing and sync missing ones\n * Returns mapping of CRM ID to DB ID\n */\n private function prepareAssociatedContacts(array $contactIds): array\n {\n // Find which contacts already exist (lean covering-index lookup)\n $existingContactsData = $this->crmEntityRepository\n ->getExistingContactIdsMap($this->config, $contactIds);\n\n $missingContactIds = array_diff($contactIds, array_keys($existingContactsData));\n\n if (empty($missingContactIds)) {\n return $existingContactsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [\n 'teamId' => $this->team->getUuid(),\n 'total_contacts' => count($contactIds),\n 'existing_contacts' => count($existingContactsData),\n 'missing_contacts' => count($missingContactIds),\n ]);\n\n // Sync missing contacts using batch API\n try {\n $syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [\n 'size' => count($missingContactIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedContactsData = [];\n }\n\n return $existingContactsData + $syncedContactsData;\n }\n\n private function batchSyncCrmObjects(string $objectType, array $crmIds): array\n {\n $syncObjects = [];\n $crmObjectIds = array_values($crmIds);\n\n foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {\n try {\n $objects = $objectType === 'companies' ?\n $this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :\n $this->client->getContactsByIds($chunk, $this->getContactFields());\n\n foreach ($objects as $objectId => $objectData) {\n $this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [\n 'requested_count' => count($chunk),\n 'synced_count' => count($objects),\n ]);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [\n 'ids' => $chunk,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncObjects;\n }\n\n private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void\n {\n try {\n $object = $objectType === 'companies' ?\n $this->importAccount($objectData) :\n $this->importContact($objectData);\n\n if ($object) {\n $syncObjects[$object->getCrmProviderId()] = $object->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [\n 'id' => $objectId,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n /**\n * Prepare associations for a single opportunity\n *\n * The return value is an array with the following structure:\n * [\n * 'companies' => [\n * $companyCrmId => $companyId,\n * ...\n * ],\n * 'contacts' => [\n * $contactCrmId => $contactId,\n * ...\n * ],\n * 'account_id' => $accountId,\n * ]\n */\n private function prepareAssociationsForOpportunity(\n string $oppCrmId,\n array $companyAssociations,\n array $contactAssociations,\n array $associationsData\n ): array {\n $associations = [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n\n $oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];\n foreach ($oppCompanyIds as $companyCrmId) {\n if (isset($associationsData['company_id_mappings'][$companyCrmId])) {\n $associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];\n\n // Set primary account (first company becomes primary account)\n if ($associations['account_id'] === null) {\n $associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];\n }\n }\n }\n\n $oppContactIds = $contactAssociations[$oppCrmId] ?? [];\n foreach ($oppContactIds as $contactCrmId) {\n if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {\n $associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];\n }\n }\n\n return $associations;\n }\n\n /**\n * Update only associations for an opportunity\n */\n private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void\n {\n // Update contact associations\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n // Update company (account) associations\n $this->updateOpportunityAccount($opportunity, $associations['account_id']);\n }\n\n /**\n * Remove all contact associations from an opportunity\n */\n private function removeAllOpportunityContacts(Opportunity $opportunity): void\n {\n $currentCount = (int) $opportunity->contacts()->count();\n\n if ($currentCount > 0) {\n $opportunity->contacts()->detach();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_count' => $currentCount,\n ]);\n }\n }\n\n private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void\n {\n if ($accountId === null) {\n // No account ID provided - keep current account\n return;\n }\n\n $currentAccountId = $opportunity->getAccountId();\n\n // Only update if account has changed\n if ($currentAccountId !== $accountId) {\n $opportunity->account_id = $accountId;\n $opportunity->save();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [\n 'opportunity_id' => $opportunity->getId(),\n 'old_account_id' => $currentAccountId,\n 'new_account_id' => $accountId,\n ]);\n }\n }\n\n /**\n * Find existing opportunities by external IDs (OPTIMIZED VERSION)\n * Uses batch query for better performance\n */\n private function findExistingOpportunities(array $crmIds): Collection\n {\n return $this->crmEntityRepository\n ->findOpportunitiesByExternalIds($this->config, $crmIds);\n }\n\n private function processOpportunityBatch(array $opportunities): int\n {\n $syncedOpportunities = $this->importOpportunityBatch($opportunities);\n\n return count($syncedOpportunities['success'] ?? []);\n }\n\n /**\n * Convert single deal associations from HubSpot format to internal format\n * Handles both HubSpot SDK objects and array formats\n *\n * @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed\n *\n * @return array Processed associations with DB IDs\n */\n private function convertDealAssociations(array $opportunityAssociations): array\n {\n $associations = $this->initializeAssociationsStructure();\n\n if (empty($opportunityAssociations)) {\n return $associations;\n }\n\n $associationIds = $this->extractAssociationIds($opportunityAssociations);\n\n $this->processCompanyAssociations($associationIds, $associations);\n $this->processContactAssociations($associationIds, $associations);\n\n return $associations;\n }\n\n private function initializeAssociationsStructure(): array\n {\n return [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n }\n\n private function extractAssociationIds(array $opportunityAssociations): array\n {\n $associationIds = [];\n\n foreach ($opportunityAssociations as $type => $associationData) {\n if (! empty($associationData)) {\n $associationIds[$type] = $this->convertSingleDealAssociations($associationData);\n }\n }\n\n return $associationIds;\n }\n\n private function processCompanyAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['companies'])) {\n return;\n }\n\n $companyId = $associationIds['companies'][0];\n $account = $this->findOrSyncAccount($companyId);\n\n if ($account instanceof Account) {\n $associations['companies'][$companyId] = $account->getId();\n $associations['account_id'] = $account->getId();\n }\n }\n\n private function processContactAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['contacts'])) {\n return;\n }\n\n foreach ($associationIds['contacts'] as $contactId) {\n $contact = $this->findOrSyncContact($contactId);\n\n if ($contact instanceof Contact) {\n $associations['contacts'][$contactId] = $contact->getId();\n }\n }\n }\n\n private function findOrSyncAccount(string $companyId): ?Account\n {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);\n\n if (! $account instanceof Account) {\n $account = $this->syncAccount($companyId);\n }\n\n return $account;\n }\n\n private function findOrSyncContact(string $contactId): ?Contact\n {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);\n\n if (! $contact instanceof Contact) {\n $contact = $this->syncContact($contactId);\n }\n\n return $contact;\n }\n\n private function convertSingleDealAssociations($opportunityAssociations = null): array\n {\n $associationData = [];\n\n if ($opportunityAssociations === null) {\n return $associationData;\n }\n\n // Handle array input (from extractAssociationIds)\n if (is_array($opportunityAssociations)) {\n return $opportunityAssociations;\n }\n\n // Handle CollectionResponseAssociatedId object\n if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {\n foreach ($opportunityAssociations->getResults() as $association) {\n $associationData[] = $association->getId();\n }\n }\n\n return $associationData;\n }\n\n private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity\n {\n if (empty($crmData['properties'])) {\n return null;\n }\n\n $properties = $crmData['properties'];\n\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = (string) $properties['hubspot_owner_id'];\n $profile = $this->getCachedOwnerProfile($ownerId);\n }\n\n $associations = $crmData['associations'] ?? [];\n $accountId = $this->resolveAccountId($associations);\n\n // Only fetch account if we need it for user_id fallback\n $accountUserId = null;\n if ($profile?->getUserId() === null && $accountId !== null) {\n $accountUserId = $this->crmEntityRepository\n ->findAccountByConfigurationAndId(\n $this->config,\n $accountId\n )?->getUserId();\n }\n\n $crmId = (string) $crmData['id'];\n\n if (($profile?->getUserId() === null) && ($accountUserId === null)) {\n $this->logger->error(\n '[HubSpot] Skip import, no user_id found',\n [\n 'id' => $crmId,\n ]\n );\n\n return null;\n }\n\n $opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(\n $this->config,\n $crmId\n );\n\n if ($opportunityExists) {\n return $this->updateOpportunity(\n $crmId,\n $properties,\n $associations,\n $accountUserId\n );\n }\n\n return $this->createOpportunity(\n $crmId,\n $properties,\n $associations,\n $accountUserId,\n );\n }\n\n /**\n * Create new opportunity\n */\n private function createOpportunity(\n string $crmId,\n array $properties,\n array $associations,\n ?int $accountUserId = null\n ): ?Opportunity {\n $accountId = $this->resolveAccountId($associations);\n if (! $accountId) {\n return null;\n }\n\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n if (! $businessProcess) {\n return null;\n }\n\n $stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);\n if (! $stage) {\n return null;\n }\n\n $data = $this->buildOpportunityData(\n $properties,\n $accountId,\n $businessProcess,\n $stage,\n $accountUserId\n );\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n if ($opportunity->wasRecentlyCreated) {\n MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());\n }\n\n return $opportunity;\n }\n\n /**\n * Update existing opportunity\n */\n private function updateOpportunity(\n string $crmId,\n array $properties,\n array $associations,\n ?int $accountUserId = null\n ): Opportunity {\n $accountId = $this->resolveAccountId($associations);\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n $stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;\n\n $data = $this->buildOpportunityData(\n $properties,\n $accountId,\n $businessProcess,\n $stage,\n $accountUserId\n );\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->updateOpportunityAssociations($opportunity, $associations);\n\n return $opportunity;\n }\n\n private function resolveAccountId(array $associations): ?int\n {\n if (! empty($associations['account_id'])) {\n return $associations['account_id'];\n }\n\n if (empty($associations)) {\n return null;\n }\n\n // Fallback: use first company as account (currently SDK returns one company)\n foreach ($associations['companies'] as $accountId) {\n return $accountId;\n }\n\n return null;\n }\n\n private function buildOpportunityData(\n array $properties,\n ?int $accountId,\n ?BusinessProcess $businessProcess,\n ?Stage $stage,\n ?int $accountUserId = null\n ): array {\n $ownerId = null;\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->getCachedOwnerProfile((string) $ownerId);\n }\n\n $userId = $profile?->getUserId() ?? $accountUserId;\n\n $name = 'Unknown';\n if (isset($properties['dealname'])) {\n $name = mb_strimwidth($properties['dealname'], 0, 128);\n }\n\n $amount = $this->resolveAmount($properties);\n $currency = $properties['deal_currency_code'] ?? null;\n\n $closeDate = null;\n if (! empty($properties['closedate'])) {\n $closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');\n }\n\n $remotelyCreatedAt = null;\n if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {\n $date = $this->parseCleanDatetime($properties['createdate']);\n $remotelyCreatedAt = $date?->format('Y-m-d H:i:s');\n }\n\n $closedStages = $this->getClosedDealStages();\n $isWon = in_array($properties['dealstage'], $closedStages['won']);\n $isLost = in_array($properties['dealstage'], $closedStages['lost']);\n\n $data = [\n 'team_id' => $this->team->getId(),\n 'user_id' => $userId,\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'value' => ! empty($amount) ? $amount : null,\n 'currency_code' => CurrencyFormatter::formatCode($currency),\n 'close_date' => $closeDate,\n 'is_closed' => $isWon || $isLost,\n 'is_won' => $isWon,\n 'remotely_created_at' => $remotelyCreatedAt,\n 'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),\n 'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),\n ];\n\n if ($accountId) {\n $data['account_id'] = $accountId;\n }\n\n if ($stage) {\n $data['stage_id'] = $stage->id;\n }\n\n if ($businessProcess) {\n $recordType = $this->getCachedBusinessProcessRecordType($businessProcess);\n if ($recordType) {\n $data['record_type_id'] = $recordType->id;\n }\n }\n\n return $data;\n }\n\n private function getCachedOwnerProfile(string $ownerId): ?Profile\n {\n $cacheKey = $this->config->getId() . ':' . $ownerId;\n if (array_key_exists($cacheKey, $this->cachedOwnerProfiles)) {\n return $this->cachedOwnerProfiles[$cacheKey];\n }\n\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, $ownerId);\n $this->cachedOwnerProfiles[$cacheKey] = $profile;\n\n return $profile;\n }\n\n private function getCachedBusinessProcessRecordType(BusinessProcess $businessProcess): mixed\n {\n $cacheKey = $this->config->getId() . ':' . $businessProcess->getId();\n if (array_key_exists($cacheKey, $this->cachedRecordTypes)) {\n return $this->cachedRecordTypes[$cacheKey];\n }\n\n $recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);\n $this->cachedRecordTypes[$cacheKey] = $recordType;\n\n return $recordType;\n }\n\n private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess\n {\n if ($pipelineId === null) {\n return null;\n }\n\n $cacheKey = $this->getBusinessProcessCacheKey($pipelineId);\n if (isset($this->cachedBusinessProcesses[$cacheKey])) {\n return $this->cachedBusinessProcesses[$cacheKey];\n }\n\n $businessProcess = $this->getBusinessProcess($pipelineId);\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->importStages();\n $this->cachedClosedDealStages = null;\n $businessProcess = $this->getBusinessProcess($pipelineId);\n }\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->logger->info(\n '[HubSpot] Deal is not attached to a pipeline',\n [\n 'pipeline' => $pipelineId]\n );\n }\n\n $this->cachedBusinessProcesses[$cacheKey] = $businessProcess;\n\n return $businessProcess;\n }\n\n private function getBusinessProcess(string $pipelineId): ?BusinessProcess\n {\n return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);\n }\n\n private function getBusinessProcessCacheKey(string $pipelineId): string\n {\n return $this->config->getId() . '_' . $pipelineId;\n }\n\n private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage\n {\n if (empty($stageId)) {\n return null;\n }\n\n $cacheKey = $this->config->getId() . ':' . $businessProcess->getId() . ':' . $stageId;\n if (isset($this->cachedStages[$cacheKey])) {\n return $this->cachedStages[$cacheKey];\n }\n\n $stage = $this->crmEntityRepository->getPipelineStageByConditions(\n $businessProcess,\n [\n 'crm_provider_id' => $stageId,\n 'type' => Stage::TYPE_OPPORTUNITY,\n ]\n );\n\n if ($stage === null) {\n $this->importStages(null, $stageId);\n $this->cachedClosedDealStages = null;\n }\n\n if ($stage === null) {\n $this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);\n }\n\n $this->cachedStages[$cacheKey] = $stage;\n\n return $stage;\n }\n\n private function resolveAmount(array $properties): ?string\n {\n $amount = null;\n if (! empty($properties['amount'])) {\n $amount = str_replace(',', '', $properties['amount']);\n }\n\n if ($this->config->hasDefaultCurrencyFieldSet()) {\n $valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();\n $amount = $properties[$valueFieldName] ?? $amount;\n }\n\n return $amount;\n }\n\n private function parseCleanDatetime(string $datetime): ?Carbon\n {\n // Treat pre-1980 values as invalid\n $minValidDate = Carbon::parse('1980-01-01 00:00:00');\n\n try {\n $date = Carbon::parse($datetime);\n\n if ($minValidDate->gt($date)) {\n return null;\n }\n\n return $date;\n } catch (Exception) {\n return null; // On parse error, treat as null\n }\n }\n\n private function resolveDealProbability(?string $stageProbability): int\n {\n if ($stageProbability === null) {\n return 0;\n }\n\n $probability = (float) $stageProbability;\n\n return $probability > 1 ? 0 : (int) ($probability * 100);\n }\n\n private function resolveForecastCategory(?string $forecastCategory): string\n {\n if (! $forecastCategory) {\n return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;\n }\n\n $forecastCategory = str_replace('_', ' ', $forecastCategory);\n\n return ucwords(strtolower($forecastCategory));\n }\n\n private function importExternalFieldData(array $properties, int $opportunityId): void\n {\n $this->importOpportunityCrmFieldData(\n $properties,\n $this->getCachedOpportunitySyncableFields(),\n $opportunityId\n );\n }\n\n private function getCachedOpportunitySyncableFields(): array\n {\n $cacheKey = (string) $this->config->getId();\n if (! isset($this->cachedOpportunitySyncableFields[$cacheKey])) {\n $this->cachedOpportunitySyncableFields[$cacheKey] = $this->getOpportunitySyncableFields();\n }\n\n return $this->cachedOpportunitySyncableFields[$cacheKey];\n }\n\n private function importOpportunityContacts(Opportunity $opportunity, array $associations): void\n {\n // Handle empty or missing contact associations\n if (empty($associations)) {\n // Remove all existing contact associations if none provided\n $this->removeAllOpportunityContacts($opportunity);\n\n return;\n }\n\n // Use differential sync approach for better performance and accuracy\n $this->syncOpportunityContactsDifferential($opportunity, $associations);\n }\n\n /**\n * Sync opportunity contacts using differential approach\n * This compares current vs new associations and only makes necessary changes\n */\n private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void\n {\n $currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);\n $contactAssociationIds = array_keys($contactAssociations);\n\n $contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);\n $contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);\n\n if (empty($contactsToAdd) && empty($contactsToRemove)) {\n return;\n }\n\n $this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);\n\n $this->removeContactAssociations($opportunity, $contactsToRemove);\n $this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);\n }\n\n private function getCurrentContactCrmIds(Opportunity $opportunity): array\n {\n return $opportunity->contacts()\n ->pluck('contacts.crm_provider_id')\n ->toArray();\n }\n\n private function logContactAssociationChanges(\n Opportunity $opportunity,\n array $currentContactCrmIds,\n array $contactAssociations,\n array $contactsToAdd,\n array $contactsToRemove\n ): void {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [\n 'opportunity_id' => $opportunity->getId(),\n 'current_contacts' => $currentContactCrmIds,\n 'new_contacts' => $contactAssociations,\n 'contacts_to_add' => $contactsToAdd,\n 'contacts_to_remove' => $contactsToRemove,\n ]);\n }\n\n private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void\n {\n if (empty($contactsToRemove)) {\n return;\n }\n\n $contactsToDetach = $opportunity->contacts()\n ->whereIn('contacts.crm_provider_id', $contactsToRemove)\n ->pluck('contacts.id')\n ->toArray();\n\n if (! empty($contactsToDetach)) {\n $opportunity->contacts()->detach($contactsToDetach);\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_contact_crm_ids' => $contactsToRemove,\n 'removed_contact_count' => count($contactsToDetach),\n ]);\n }\n }\n\n private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void\n {\n if (empty($contactsToAdd)) {\n return;\n }\n\n $contactsAdded = [];\n foreach ($contactsToAdd as $crmId) {\n $id = $contactAssociations[$crmId];\n\n if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {\n $contactsAdded[] = $crmId;\n }\n }\n\n $this->logAddedContacts($opportunity, $contactsAdded);\n }\n\n private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool\n {\n try {\n return $this->performContactAttachment($opportunity, $id, $crmId);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [\n 'opportunity_id' => $opportunity->getId(),\n 'contact_crm_id' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n private function performContactAttachment(Opportunity $opportunity, int $contactId, string $crmId): bool\n {\n try {\n $opportunity->contacts()->attach($contactId, [\n 'crm_provider_id' => $crmId,\n ]);\n\n return true;\n } catch (\\Illuminate\\Database\\QueryException $e) {\n if (str_contains($e->getMessage(), 'Duplicate entry')) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [\n 'contact_id' => $contactId,\n 'contact_crm_id' => $crmId,\n 'opportunity_id' => $opportunity->getId(),\n ]);\n\n return false;\n }\n\n throw $e;\n }\n }\n\n private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void\n {\n if (! empty($contactsAdded)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'added_contact_crm_ids' => $contactsAdded,\n 'added_contacts_count' => count($contactsAdded),\n ]);\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"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,"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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-9095576242384424549
|
-8466282600624451230
|
typing_pause
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
ServiceTest
Run 'ServiceTest'
Debug 'ServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
cachedClosedDealStages
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
2/6
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Sync Changes
Hide This Notification
Code changed:
Hide
75
2
22
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use HubSpot\Client\Crm\Deals\Model\CollectionResponseAssociatedId;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Models\Account;
use Exception;
use Jiminny\Component\DealInsights\Forecast\Forecast;
use Jiminny\Jobs\Crm\MatchActivitiesToNewOpportunity;
use Jiminny\Models\Contact;
use Jiminny\Models\Crm\BusinessProcess;
use Jiminny\Exceptions\CrmException;
use Jiminny\Models\Crm\Profile;
use Jiminny\Models\Opportunity;
use Illuminate\Support\Collection;
use Jiminny\Models\Stage;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Crm\Hubspot\DealFieldsService;
use Jiminny\Services\Crm\Hubspot\OpportunitySyncStrategy\HubspotSingleSyncStrategy;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Services\Crm\OpportunitySyncStrategyResolver;
use Jiminny\Utils\CurrencyFormatter;
/**
* Optimized sync methods for better performance
* These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains
*/
trait OpportunitySyncTrait
{
private const int BATCH_SIZE = 100;
private const int BATCH_PROCESS_SIZE = 800;
protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;
protected CrmEntityRepository $crmEntityRepository;
protected DealFieldsService $dealFieldsService;
private ?array $cachedClosedDealStages = null;
private array $cachedBusinessProcesses = [];
private array $cachedStages = [];
/** @var array<string, array<string>> keyed by config id */
private array $cachedOpportunitySyncableFields = [];
/** @var array<string, mixed> keyed by configId:ownerId */
private array $cachedOwnerProfiles = [];
/** @var array<string, mixed> keyed by configId:businessProcessId */
private array $cachedRecordTypes = [];
public function syncOpportunities(array $parameters, ?string $strategy = null): int
{
$startTime = microtime(true);
$strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);
$parameters['config'] = $this->config;
$syncCount = 0;
$reportedTotal = 0;
$lastSyncedId = [];
$strategyNames = [];
try {
foreach ($strategies as $strategyName => $syncStrategy) {
$strategyNames[] = $strategyName;
$this->logger->info(
'[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' . $strategyName,
['team' => $this->team->getId()]
);
$total = 0;
$lastId = null;
$buffer = [];
// HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies
foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {
$buffer[] = $hsOpportunity;
// process every 800 rows (fits < 1 000 association limit)
if (\count($buffer) >= self::BATCH_PROCESS_SIZE) {
$syncCount += $this->processOpportunityBatch($buffer);
$buffer = [];
}
}
// leftovers
if ($buffer) {
$syncCount += $this->processOpportunityBatch($buffer);
}
$reportedTotal += $total;
$lastSyncedId = $lastId;
}
} catch (\HubSpot\Client\Crm\Deals\ApiException | CrmException $e) {
$this->handleSyncException($e, $parameters);
}
$durationMs = round((microtime(true) - $startTime) * 1000, 2);
$this->logger->info(
'[HubSpot] Synced opportunities',
[
'team' => $this->team->getId(),
'strategies' => implode(',', $strategyNames),
'sync_count' => $syncCount,
'total' => $reportedTotal,
'last_synced_id' => $lastSyncedId,
'duration_ms' => $durationMs,
]
);
return $reportedTotal;
}
private function handleSyncException(\Throwable $e, array $parameters): void
{
if (($parameters['since'] ?? null) instanceof Carbon) {
$parameters['since'] = $parameters['since']->toDateTimeString();
}
$parameters['config'] = $this->config->getId();
$this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [
'teamId' => $this->team->getUuid(),
'parameters' => $parameters,
'reason' => $e->getMessage(),
]);
}
/**
* @inheritdoc
*/
public function syncOpportunity(string $crmId): ?Opportunity
{
$strategy = $this->opportunitySyncStrategyResolver->resolve(
$this->config,
OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,
);
$parameters = [
'config' => $this->config,
'crm_id' => $crmId,
];
try {
if (! $strategy instanceof HubspotSingleSyncStrategy) {
throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');
}
$hsOpportunity = $strategy->fetchOpportunity($parameters);
} catch (\HubSpot\Client\Crm\Deals\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
$hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);
return $this->importOrUpdateOpportunity($hsOpportunity);
}
/**
* Process webhook-collected opportunity batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportOpportunityBatch jobs for batch processing.
*
* @return int Number of opportunity IDs dispatched to jobs
*/
public function batchSyncOpportunities(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,
$configId
);
}
/**
* Import a batch of opportunities by their CRM IDs.
* Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().
*
* @param array<string> $crmIds HubSpot deal CRM IDs
*
* @return array{success: array, failed_ids: array, errors?: array<string, string>}
*/
public function importOpportunityBatchByIds(array $crmIds): array
{
$fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);
$allDeals = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
$deals = $this->client->getOpportunitiesByIds($chunk, $fields);
foreach ($deals as $deal) {
$allDeals[] = $deal;
}
}
// IDs not returned by HubSpot are likely deleted or inaccessible deals.
// These are not failures — retrying won't bring them back.
$fetchedIds = array_map('strval', array_column($allDeals, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allDeals),
]);
}
if (empty($allDeals)) {
return ['success' => [], 'failed_ids' => []];
}
return $this->importOpportunityBatch($allDeals);
}
private function getClosedDealStages(): array
{
if ($this->cachedClosedDealStages !== null) {
return $this->cachedClosedDealStages;
}
$stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);
$data = [
'lost' => [],
'won' => [],
];
foreach ($stages as $stage) {
if ($stage->probability == 0.00) {
$data['lost'][] = $stage->crm_provider_id;
}
if ($stage->probability == 100.00) {
$data['won'][] = $stage->crm_provider_id;
}
}
$this->cachedClosedDealStages = $data;
return $data;
}
/**
* Import deals into the database with pre-fetched associations.
*
* API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT
* caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()
* where Laravel retries the whole job with backoff. After all retries exhausted,
* failed() requeues all IDs to Redis.
*
* The per-deal loop catches exceptions individually. A deal can end up in three states:
* - success: imported/updated successfully
* - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)
* These are permanent issues — retrying won't fix them.
* - skipped (null): missing dependencies (no account, unknown pipeline/stage).
* This is acceptable — the deal cannot be imported until those exist.
*/
private function importOpportunityBatch(array $deals): array
{
$syncedOpportunities = [
'success' => [],
'failed_ids' => [],
];
$dealIds = array_column($deals, 'id');
$batchStart = microtime(true);
$slowDeals = [];
// Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the
// queue job retries the whole batch and eventually requeues all deal IDs back to Redis.
try {
$companyAssocStart = microtime(true);
$companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');
$companyAssocMs = (int) round((microtime(true) - $companyAssocStart) * 1000);
$contactAssocStart = microtime(true);
$contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');
$contactAssocMs = (int) round((microtime(true) - $contactAssocStart) * 1000);
$prepareStart = microtime(true);
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
$prepareTimings = [];
$associationsData = $this->prepareAssociatedEntities(
$companyAssociations,
$contactAssociations,
$prepareTimings
);
$prepareMs = (int) round((microtime(true) - $prepareStart) * 1000);
$missingCompanies = count(array_diff(
$allCompanyIds,
array_keys($associationsData['company_id_mappings'] ?? [])
));
$missingContacts = count(array_diff(
$allContactIds,
array_keys($associationsData['contact_id_mappings'] ?? [])
));
$existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(
$this->config,
array_map('strval', $dealIds)
);
$existingCrmIdSet = array_flip($existingCrmIds);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [
'teamId' => $this->team->getId(),
'dealCount' => count($dealIds),
'error' => $e->getMessage(),
]);
throw $e;
}
$loopStart = microtime(true);
foreach ($deals as $deal) {
$dealStart = microtime(true);
try {
$deal['associations'] = $this->prepareAssociationsForOpportunity(
$deal['id'],
$companyAssociations,
$contactAssociations,
$associationsData
);
$syncedOpportunity = $this->importOrUpdateOpportunity(
$deal,
isset($existingCrmIdSet[(string) $deal['id']])
);
if ($syncedOpportunity) {
$syncedOpportunities['success'][] = $syncedOpportunity;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [
'teamId' => $this->team->getId(),
'crmId' => $deal['id'],
'error' => $e->getMessage(),
]);
$syncedOpportunities['failed_ids'][] = $deal['id'];
$syncedOpportunities['errors'][$deal['id']] = $e->getMessage();
}
$dealMs = (int) round((microtime(true) - $dealStart) * 1000);
if ($dealMs > 1000) {
$slowDeals[] = ['crmId' => $deal['id'], 'ms' => $dealMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importOpportunityBatch timing', [
'teamId' => $this->team->getId(),
'deal_count' => count($deals),
'total_ms' => $totalMs,
'company_assoc_api_ms' => $companyAssocMs,
'contact_assoc_api_ms' => $contactAssocMs,
'prepare_entities_ms' => $prepareMs,
'prepare_accounts_ms' => $prepareTimings['accounts_ms'],
'prepare_contacts_ms' => $prepareTimings['contacts_ms'],
'total_companies' => count($allCompanyIds),
'missing_companies' => $missingCompanies,
'total_contacts' => count($allContactIds),
'missing_contacts' => $missingContacts,
'deals_loop_ms' => $loopMs,
'avg_deal_ms' => ! empty($deals) ? (int) round($loopMs / count($deals)) : 0,
'slow_deals_count' => count($slowDeals),
'slow_deals' => array_slice($slowDeals, 0, 10),
]);
return $syncedOpportunities;
}
/**
* Prepare associated entities for opportunities with optimized batch processing
* Returns structured data with CRM ID to DB ID mappings for each opportunity
*/
private function prepareAssociatedEntities(
array $companyAssociations,
array $contactAssociations,
array &$timings = []
): array {
// Step 1: Collect all unique company and contact IDs from associations
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
// Step 2: Batch sync missing entities and get CRM ID to DB ID mappings
$companyIdMappings = [];
$contactIdMappings = [];
$accountsMs = 0;
$contactsMs = 0;
if (! empty($allCompanyIds)) {
$start = microtime(true);
$companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);
$accountsMs = (int) round((microtime(true) - $start) * 1000);
}
if (! empty($allContactIds)) {
$start = microtime(true);
$contactIdMappings = $this->prepareAssociatedContacts($allContactIds);
$contactsMs = (int) round((microtime(true) - $start) * 1000);
}
$timings = [
'accounts_ms' => $accountsMs,
'contacts_ms' => $contactsMs,
];
return [
'company_id_mappings' => $companyIdMappings,
'contact_id_mappings' => $contactIdMappings,
];
}
/**
* Flatten association data to get unique IDs
*/
private function flattenAssociationIds(array $associations): array
{
$ids = [];
foreach ($associations as $dealAssociations) {
if (is_array($dealAssociations)) {
foreach ($dealAssociations as $id) {
$ids[$id] = true;
}
}
}
return array_keys($ids);
}
/**
* Batch sync missing accounts
*/
private function prepareAssociatedAccounts(array $companyIds): array
{
// Find which accounts already exist (lean covering-index lookup)
$existingAccountsData = $this->crmEntityRepository
->getExistingAccountIdsMap($this->config, $companyIds);
$missingCompanyIds = array_diff($companyIds, array_keys($existingAccountsData));
if (empty($missingCompanyIds)) {
return $existingAccountsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [
'teamId' => $this->team->getUuid(),
'total_companies' => count($companyIds),
'existing_companies' => count($existingAccountsData),
'missing_companies' => count($missingCompanyIds),
]);
// we already have limit on opportunity ids count
// Initialize variable before try block
$syncedAccountsData = [];
try {
$syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [
'size' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
$syncedAccountsData = [];
}
return $existingAccountsData + $syncedAccountsData;
}
/**
* Prepare associated contacts - find existing and sync missing ones
* Returns mapping of CRM ID to DB ID
*/
private function prepareAssociatedContacts(array $contactIds): array
{
// Find which contacts already exist (lean covering-index lookup)
$existingContactsData = $this->crmEntityRepository
->getExistingContactIdsMap($this->config, $contactIds);
$missingContactIds = array_diff($contactIds, array_keys($existingContactsData));
if (empty($missingContactIds)) {
return $existingContactsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [
'teamId' => $this->team->getUuid(),
'total_contacts' => count($contactIds),
'existing_contacts' => count($existingContactsData),
'missing_contacts' => count($missingContactIds),
]);
// Sync missing contacts using batch API
try {
$syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [
'size' => count($missingContactIds),
'error' => $e->getMessage(),
]);
$syncedContactsData = [];
}
return $existingContactsData + $syncedContactsData;
}
private function batchSyncCrmObjects(string $objectType, array $crmIds): array
{
$syncObjects = [];
$crmObjectIds = array_values($crmIds);
foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {
try {
$objects = $objectType === 'companies' ?
$this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :
$this->client->getContactsByIds($chunk, $this->getContactFields());
foreach ($objects as $objectId => $objectData) {
$this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [
'requested_count' => count($chunk),
'synced_count' => count($objects),
]);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [
'ids' => $chunk,
'error' => $e->getMessage(),
]);
}
}
return $syncObjects;
}
private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void
{
try {
$object = $objectType === 'companies' ?
$this->importAccount($objectData) :
$this->importContact($objectData);
if ($object) {
$syncObjects[$object->getCrmProviderId()] = $object->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [
'id' => $objectId,
'error' => $e->getMessage(),
]);
}
}
/**
* Prepare associations for a single opportunity
*
* The return value is an array with the following structure:
* [
* 'companies' => [
* $companyCrmId => $companyId,
* ...
* ],
* 'contacts' => [
* $contactCrmId => $contactId,
* ...
* ],
* 'account_id' => $accountId,
* ]
*/
private function prepareAssociationsForOpportunity(
string $oppCrmId,
array $companyAssociations,
array $contactAssociations,
array $associationsData
): array {
$associations = [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
$oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];
foreach ($oppCompanyIds as $companyCrmId) {
if (isset($associationsData['company_id_mappings'][$companyCrmId])) {
$associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];
// Set primary account (first company becomes primary account)
if ($associations['account_id'] === null) {
$associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];
}
}
}
$oppContactIds = $contactAssociations[$oppCrmId] ?? [];
foreach ($oppContactIds as $contactCrmId) {
if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {
$associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];
}
}
return $associations;
}
/**
* Update only associations for an opportunity
*/
private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void
{
// Update contact associations
$this->importOpportunityContacts($opportunity, $associations['contacts']);
// Update company (account) associations
$this->updateOpportunityAccount($opportunity, $associations['account_id']);
}
/**
* Remove all contact associations from an opportunity
*/
private function removeAllOpportunityContacts(Opportunity $opportunity): void
{
$currentCount = (int) $opportunity->contacts()->count();
if ($currentCount > 0) {
$opportunity->contacts()->detach();
$this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_count' => $currentCount,
]);
}
}
private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void
{
if ($accountId === null) {
// No account ID provided - keep current account
return;
}
$currentAccountId = $opportunity->getAccountId();
// Only update if account has changed
if ($currentAccountId !== $accountId) {
$opportunity->account_id = $accountId;
$opportunity->save();
$this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [
'opportunity_id' => $opportunity->getId(),
'old_account_id' => $currentAccountId,
'new_account_id' => $accountId,
]);
}
}
/**
* Find existing opportunities by external IDs (OPTIMIZED VERSION)
* Uses batch query for better performance
*/
private function findExistingOpportunities(array $crmIds): Collection
{
return $this->crmEntityRepository
->findOpportunitiesByExternalIds($this->config, $crmIds);
}
private function processOpportunityBatch(array $opportunities): int
{
$syncedOpportunities = $this->importOpportunityBatch($opportunities);
return count($syncedOpportunities['success'] ?? []);
}
/**
* Convert single deal associations from HubSpot format to internal format
* Handles both HubSpot SDK objects and array formats
*
* @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed
*
* @return array Processed associations with DB IDs
*/
private function convertDealAssociations(array $opportunityAssociations): array
{
$associations = $this->initializeAssociationsStructure();
if (empty($opportunityAssociations)) {
return $associations;
}
$associationIds = $this->extractAssociationIds($opportunityAssociations);
$this->processCompanyAssociations($associationIds, $associations);
$this->processContactAssociations($associationIds, $associations);
return $associations;
}
private function initializeAssociationsStructure(): array
{
return [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
}
private function extractAssociationIds(array $opportunityAssociations): array
{
$associationIds = [];
foreach ($opportunityAssociations as $type => $associationData) {
if (! empty($associationData)) {
$associationIds[$type] = $this->convertSingleDealAssociations($associationData);
}
}
return $associationIds;
}
private function processCompanyAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['companies'])) {
return;
}
$companyId = $associationIds['companies'][0];
$account = $this->findOrSyncAccount($companyId);
if ($account instanceof Account) {
$associations['companies'][$companyId] = $account->getId();
$associations['account_id'] = $account->getId();
}
}
private function processContactAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['contacts'])) {
return;
}
foreach ($associationIds['contacts'] as $contactId) {
$contact = $this->findOrSyncContact($contactId);
if ($contact instanceof Contact) {
$associations['contacts'][$contactId] = $contact->getId();
}
}
}
private function findOrSyncAccount(string $companyId): ?Account
{
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);
if (! $account instanceof Account) {
$account = $this->syncAccount($companyId);
}
return $account;
}
private function findOrSyncContact(string $contactId): ?Contact
{
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);
if (! $contact instanceof Contact) {
$contact = $this->syncContact($contactId);
}
return $contact;
}
private function convertSingleDealAssociations($opportunityAssociations = null): array
{
$associationData = [];
if ($opportunityAssociations === null) {
return $associationData;
}
// Handle array input (from extractAssociationIds)
if (is_array($opportunityAssociations)) {
return $opportunityAssociations;
}
// Handle CollectionResponseAssociatedId object
if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {
foreach ($opportunityAssociations->getResults() as $association) {
$associationData[] = $association->getId();
}
}
return $associationData;
}
private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity
{
if (empty($crmData['properties'])) {
return null;
}
$properties = $crmData['properties'];
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = (string) $properties['hubspot_owner_id'];
$profile = $this->getCachedOwnerProfile($ownerId);
}
$associations = $crmData['associations'] ?? [];
$accountId = $this->resolveAccountId($associations);
// Only fetch account if we need it for user_id fallback
$accountUserId = null;
if ($profile?->getUserId() === null && $accountId !== null) {
$accountUserId = $this->crmEntityRepository
->findAccountByConfigurationAndId(
$this->config,
$accountId
)?->getUserId();
}
$crmId = (string) $crmData['id'];
if (($profile?->getUserId() === null) && ($accountUserId === null)) {
$this->logger->error(
'[HubSpot] Skip import, no user_id found',
[
'id' => $crmId,
]
);
return null;
}
$opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(
$this->config,
$crmId
);
if ($opportunityExists) {
return $this->updateOpportunity(
$crmId,
$properties,
$associations,
$accountUserId
);
}
return $this->createOpportunity(
$crmId,
$properties,
$associations,
$accountUserId,
);
}
/**
* Create new opportunity
*/
private function createOpportunity(
string $crmId,
array $properties,
array $associations,
?int $accountUserId = null
): ?Opportunity {
$accountId = $this->resolveAccountId($associations);
if (! $accountId) {
return null;
}
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
if (! $businessProcess) {
return null;
}
$stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);
if (! $stage) {
return null;
}
$data = $this->buildOpportunityData(
$properties,
$accountId,
$businessProcess,
$stage,
$accountUserId
);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->importOpportunityContacts($opportunity, $associations['contacts']);
if ($opportunity->wasRecentlyCreated) {
MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());
}
return $opportunity;
}
/**
* Update existing opportunity
*/
private function updateOpportunity(
string $crmId,
array $properties,
array $associations,
?int $accountUserId = null
): Opportunity {
$accountId = $this->resolveAccountId($associations);
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
$stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;
$data = $this->buildOpportunityData(
$properties,
$accountId,
$businessProcess,
$stage,
$accountUserId
);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->updateOpportunityAssociations($opportunity, $associations);
return $opportunity;
}
private function resolveAccountId(array $associations): ?int
{
if (! empty($associations['account_id'])) {
return $associations['account_id'];
}
if (empty($associations)) {
return null;
}
// Fallback: use first company as account (currently SDK returns one company)
foreach ($associations['companies'] as $accountId) {
return $accountId;
}
return null;
}
private function buildOpportunityData(
array $properties,
?int $accountId,
?BusinessProcess $businessProcess,
?Stage $stage,
?int $accountUserId = null
): array {
$ownerId = null;
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->getCachedOwnerProfile((string) $ownerId);
}
$userId = $profile?->getUserId() ?? $accountUserId;
$name = 'Unknown';
if (isset($properties['dealname'])) {
$name = mb_strimwidth($properties['dealname'], 0, 128);
}
$amount = $this->resolveAmount($properties);
$currency = $properties['deal_currency_code'] ?? null;
$closeDate = null;
if (! empty($properties['closedate'])) {
$closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');
}
$remotelyCreatedAt = null;
if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {
$date = $this->parseCleanDatetime($properties['createdate']);
$remotelyCreatedAt = $date?->format('Y-m-d H:i:s');
}
$closedStages = $this->getClosedDealStages();
$isWon = in_array($properties['dealstage'], $closedStages['won']);
$isLost = in_array($properties['dealstage'], $closedStages['lost']);
$data = [
'team_id' => $this->team->getId(),
'user_id' => $userId,
'owner_id' => $ownerId,
'name' => $name,
'value' => ! empty($amount) ? $amount : null,
'currency_code' => CurrencyFormatter::formatCode($currency),
'close_date' => $closeDate,
'is_closed' => $isWon || $isLost,
'is_won' => $isWon,
'remotely_created_at' => $remotelyCreatedAt,
'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),
'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),
];
if ($accountId) {
$data['account_id'] = $accountId;
}
if ($stage) {
$data['stage_id'] = $stage->id;
}
if ($businessProcess) {
$recordType = $this->getCachedBusinessProcessRecordType($businessProcess);
if ($recordType) {
$data['record_type_id'] = $recordType->id;
}
}
return $data;
}
private function getCachedOwnerProfile(string $ownerId): ?Profile
{
$cacheKey = $this->config->getId() . ':' . $ownerId;
if (array_key_exists($cacheKey, $this->cachedOwnerProfiles)) {
return $this->cachedOwnerProfiles[$cacheKey];
}
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, $ownerId);
$this->cachedOwnerProfiles[$cacheKey] = $profile;
return $profile;
}
private function getCachedBusinessProcessRecordType(BusinessProcess $businessProcess): mixed
{
$cacheKey = $this->config->getId() . ':' . $businessProcess->getId();
if (array_key_exists($cacheKey, $this->cachedRecordTypes)) {
return $this->cachedRecordTypes[$cacheKey];
}
$recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);
$this->cachedRecordTypes[$cacheKey] = $recordType;
return $recordType;
}
private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess
{
if ($pipelineId === null) {
return null;
}
$cacheKey = $this->getBusinessProcessCacheKey($pipelineId);
if (isset($this->cachedBusinessProcesses[$cacheKey])) {
return $this->cachedBusinessProcesses[$cacheKey];
}
$businessProcess = $this->getBusinessProcess($pipelineId);
if (! $businessProcess instanceof BusinessProcess) {
$this->importStages();
$this->cachedClosedDealStages = null;
$businessProcess = $this->getBusinessProcess($pipelineId);
}
if (! $businessProcess instanceof BusinessProcess) {
$this->logger->info(
'[HubSpot] Deal is not attached to a pipeline',
[
'pipeline' => $pipelineId]
);
}
$this->cachedBusinessProcesses[$cacheKey] = $businessProcess;
return $businessProcess;
}
private function getBusinessProcess(string $pipelineId): ?BusinessProcess
{
return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);
}
private function getBusinessProcessCacheKey(string $pipelineId): string
{
return $this->config->getId() . '_' . $pipelineId;
}
private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage
{
if (empty($stageId)) {
return null;
}
$cacheKey = $this->config->getId() . ':' . $businessProcess->getId() . ':' . $stageId;
if (isset($this->cachedStages[$cacheKey])) {
return $this->cachedStages[$cacheKey];
}
$stage = $this->crmEntityRepository->getPipelineStageByConditions(
$businessProcess,
[
'crm_provider_id' => $stageId,
'type' => Stage::TYPE_OPPORTUNITY,
]
);
if ($stage === null) {
$this->importStages(null, $stageId);
$this->cachedClosedDealStages = null;
}
if ($stage === null) {
$this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);
}
$this->cachedStages[$cacheKey] = $stage;
return $stage;
}
private function resolveAmount(array $properties): ?string
{
$amount = null;
if (! empty($properties['amount'])) {
$amount = str_replace(',', '', $properties['amount']);
}
if ($this->config->hasDefaultCurrencyFieldSet()) {
$valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();
$amount = $properties[$valueFieldName] ?? $amount;
}
return $amount;
}
private function parseCleanDatetime(string $datetime): ?Carbon
{
// Treat pre-1980 values as invalid
$minValidDate = Carbon::parse('1980-01-01 00:00:00');
try {
$date = Carbon::parse($datetime);
if ($minValidDate->gt($date)) {
return null;
}
return $date;
} catch (Exception) {
return null; // On parse error, treat as null
}
}
private function resolveDealProbability(?string $stageProbability): int
{
if ($stageProbability === null) {
return 0;
}
$probability = (float) $stageProbability;
return $probability > 1 ? 0 : (int) ($probability * 100);
}
private function resolveForecastCategory(?string $forecastCategory): string
{
if (! $forecastCategory) {
return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;
}
$forecastCategory = str_replace('_', ' ', $forecastCategory);
return ucwords(strtolower($forecastCategory));
}
private function importExternalFieldData(array $properties, int $opportunityId): void
{
$this->importOpportunityCrmFieldData(
$properties,
$this->getCachedOpportunitySyncableFields(),
$opportunityId
);
}
private function getCachedOpportunitySyncableFields(): array
{
$cacheKey = (string) $this->config->getId();
if (! isset($this->cachedOpportunitySyncableFields[$cacheKey])) {
$this->cachedOpportunitySyncableFields[$cacheKey] = $this->getOpportunitySyncableFields();
}
return $this->cachedOpportunitySyncableFields[$cacheKey];
}
private function importOpportunityContacts(Opportunity $opportunity, array $associations): void
{
// Handle empty or missing contact associations
if (empty($associations)) {
// Remove all existing contact associations if none provided
$this->removeAllOpportunityContacts($opportunity);
return;
}
// Use differential sync approach for better performance and accuracy
$this->syncOpportunityContactsDifferential($opportunity, $associations);
}
/**
* Sync opportunity contacts using differential approach
* This compares current vs new associations and only makes necessary changes
*/
private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void
{
$currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);
$contactAssociationIds = array_keys($contactAssociations);
$contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);
$contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);
if (empty($contactsToAdd) && empty($contactsToRemove)) {
return;
}
$this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);
$this->removeContactAssociations($opportunity, $contactsToRemove);
$this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);
}
private function getCurrentContactCrmIds(Opportunity $opportunity): array
{
return $opportunity->contacts()
->pluck('contacts.crm_provider_id')
->toArray();
}
private function logContactAssociationChanges(
Opportunity $opportunity,
array $currentContactCrmIds,
array $contactAssociations,
array $contactsToAdd,
array $contactsToRemove
): void {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [
'opportunity_id' => $opportunity->getId(),
'current_contacts' => $currentContactCrmIds,
'new_contacts' => $contactAssociations,
'contacts_to_add' => $contactsToAdd,
'contacts_to_remove' => $contactsToRemove,
]);
}
private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void
{
if (empty($contactsToRemove)) {
return;
}
$contactsToDetach = $opportunity->contacts()
->whereIn('contacts.crm_provider_id', $contactsToRemove)
->pluck('contacts.id')
->toArray();
if (! empty($contactsToDetach)) {
$opportunity->contacts()->detach($contactsToDetach);
$this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_contact_crm_ids' => $contactsToRemove,
'removed_contact_count' => count($contactsToDetach),
]);
}
}
private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void
{
if (empty($contactsToAdd)) {
return;
}
$contactsAdded = [];
foreach ($contactsToAdd as $crmId) {
$id = $contactAssociations[$crmId];
if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {
$contactsAdded[] = $crmId;
}
}
$this->logAddedContacts($opportunity, $contactsAdded);
}
private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool
{
try {
return $this->performContactAttachment($opportunity, $id, $crmId);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [
'opportunity_id' => $opportunity->getId(),
'contact_crm_id' => $crmId,
'error' => $e->getMessage(),
]);
return false;
}
}
private function performContactAttachment(Opportunity $opportunity, int $contactId, string $crmId): bool
{
try {
$opportunity->contacts()->attach($contactId, [
'crm_provider_id' => $crmId,
]);
return true;
} catch (\Illuminate\Database\QueryException $e) {
if (str_contains($e->getMessage(), 'Duplicate entry')) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [
'contact_id' => $contactId,
'contact_crm_id' => $crmId,
'opportunity_id' => $opportunity->getId(),
]);
return false;
}
throw $e;
}
}
private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void
{
if (! empty($contactsAdded)) {
$this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [
'opportunity_id' => $opportunity->getId(),
'added_contact_crm_ids' => $contactsAdded,
'added_contacts_count' => count($contactsAdded),
]);
}
}
}
Execute
Explain Plan
Browse Query History...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
88744
|
3026
|
32
|
2026-05-28T17:34:54.377666+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779989694377_m1.jpg...
|
PhpStorm
|
faVsco.js – console [EU]
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
ServiceTest
Run 'ServiceTest'
Debug 'ServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
cachedClosedDealStages
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
2/6
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Sync Changes
Hide This Notification
Code changed:
Hide
75
2
22
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use HubSpot\Client\Crm\Deals\Model\CollectionResponseAssociatedId;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Models\Account;
use Exception;
use Jiminny\Component\DealInsights\Forecast\Forecast;
use Jiminny\Jobs\Crm\MatchActivitiesToNewOpportunity;
use Jiminny\Models\Contact;
use Jiminny\Models\Crm\BusinessProcess;
use Jiminny\Exceptions\CrmException;
use Jiminny\Models\Crm\Profile;
use Jiminny\Models\Opportunity;
use Illuminate\Support\Collection;
use Jiminny\Models\Stage;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Crm\Hubspot\DealFieldsService;
use Jiminny\Services\Crm\Hubspot\OpportunitySyncStrategy\HubspotSingleSyncStrategy;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Services\Crm\OpportunitySyncStrategyResolver;
use Jiminny\Utils\CurrencyFormatter;
/**
* Optimized sync methods for better performance
* These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains
*/
trait OpportunitySyncTrait
{
private const int BATCH_SIZE = 100;
private const int BATCH_PROCESS_SIZE = 800;
protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;
protected CrmEntityRepository $crmEntityRepository;
protected DealFieldsService $dealFieldsService;
private ?array $cachedClosedDealStages = null;
private array $cachedBusinessProcesses = [];
private array $cachedStages = [];
/** @var array<string, array<string>> keyed by config id */
private array $cachedOpportunitySyncableFields = [];
/** @var array<string, mixed> keyed by configId:ownerId */
private array $cachedOwnerProfiles = [];
/** @var array<string, mixed> keyed by configId:businessProcessId */
private array $cachedRecordTypes = [];
public function syncOpportunities(array $parameters, ?string $strategy = null): int
{
$startTime = microtime(true);
$strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);
$parameters['config'] = $this->config;
$syncCount = 0;
$reportedTotal = 0;
$lastSyncedId = [];
$strategyNames = [];
try {
foreach ($strategies as $strategyName => $syncStrategy) {
$strategyNames[] = $strategyName;
$this->logger->info(
'[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' . $strategyName,
['team' => $this->team->getId()]
);
$total = 0;
$lastId = null;
$buffer = [];
// HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies
foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {
$buffer[] = $hsOpportunity;
// process every 800 rows (fits < 1 000 association limit)
if (\count($buffer) >= self::BATCH_PROCESS_SIZE) {
$syncCount += $this->processOpportunityBatch($buffer);
$buffer = [];
}
}
// leftovers
if ($buffer) {
$syncCount += $this->processOpportunityBatch($buffer);
}
$reportedTotal += $total;
$lastSyncedId = $lastId;
}
} catch (\HubSpot\Client\Crm\Deals\ApiException | CrmException $e) {
$this->handleSyncException($e, $parameters);
}
$durationMs = round((microtime(true) - $startTime) * 1000, 2);
$this->logger->info(
'[HubSpot] Synced opportunities',
[
'team' => $this->team->getId(),
'strategies' => implode(',', $strategyNames),
'sync_count' => $syncCount,
'total' => $reportedTotal,
'last_synced_id' => $lastSyncedId,
'duration_ms' => $durationMs,
]
);
return $reportedTotal;
}
private function handleSyncException(\Throwable $e, array $parameters): void
{
if (($parameters['since'] ?? null) instanceof Carbon) {
$parameters['since'] = $parameters['since']->toDateTimeString();
}
$parameters['config'] = $this->config->getId();
$this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [
'teamId' => $this->team->getUuid(),
'parameters' => $parameters,
'reason' => $e->getMessage(),
]);
}
/**
* @inheritdoc
*/
public function syncOpportunity(string $crmId): ?Opportunity
{
$strategy = $this->opportunitySyncStrategyResolver->resolve(
$this->config,
OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,
);
$parameters = [
'config' => $this->config,
'crm_id' => $crmId,
];
try {
if (! $strategy instanceof HubspotSingleSyncStrategy) {
throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');
}
$hsOpportunity = $strategy->fetchOpportunity($parameters);
} catch (\HubSpot\Client\Crm\Deals\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
$hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);
return $this->importOrUpdateOpportunity($hsOpportunity);
}
/**
* Process webhook-collected opportunity batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportOpportunityBatch jobs for batch processing.
*
* @return int Number of opportunity IDs dispatched to jobs
*/
public function batchSyncOpportunities(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,
$configId
);
}
/**
* Import a batch of opportunities by their CRM IDs.
* Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().
*
* @param array<string> $crmIds HubSpot deal CRM IDs
*
* @return array{success: array, failed_ids: array, errors?: array<string, string>}
*/
public function importOpportunityBatchByIds(array $crmIds): array
{
$fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);
$allDeals = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
$deals = $this->client->getOpportunitiesByIds($chunk, $fields);
foreach ($deals as $deal) {
$allDeals[] = $deal;
}
}
// IDs not returned by HubSpot are likely deleted or inaccessible deals.
// These are not failures — retrying won't bring them back.
$fetchedIds = array_map('strval', array_column($allDeals, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allDeals),
]);
}
if (empty($allDeals)) {
return ['success' => [], 'failed_ids' => []];
}
return $this->importOpportunityBatch($allDeals);
}
private function getClosedDealStages(): array
{
if ($this->cachedClosedDealStages !== null) {
return $this->cachedClosedDealStages;
}
$stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);
$data = [
'lost' => [],
'won' => [],
];
foreach ($stages as $stage) {
if ($stage->probability == 0.00) {
$data['lost'][] = $stage->crm_provider_id;
}
if ($stage->probability == 100.00) {
$data['won'][] = $stage->crm_provider_id;
}
}
$this->cachedClosedDealStages = $data;
return $data;
}
/**
* Import deals into the database with pre-fetched associations.
*
* API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT
* caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()
* where Laravel retries the whole job with backoff. After all retries exhausted,
* failed() requeues all IDs to Redis.
*
* The per-deal loop catches exceptions individually. A deal can end up in three states:
* - success: imported/updated successfully
* - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)
* These are permanent issues — retrying won't fix them.
* - skipped (null): missing dependencies (no account, unknown pipeline/stage).
* This is acceptable — the deal cannot be imported until those exist.
*/
private function importOpportunityBatch(array $deals): array
{
$syncedOpportunities = [
'success' => [],
'failed_ids' => [],
];
$dealIds = array_column($deals, 'id');
$batchStart = microtime(true);
$slowDeals = [];
// Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the
// queue job retries the whole batch and eventually requeues all deal IDs back to Redis.
try {
$companyAssocStart = microtime(true);
$companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');
$companyAssocMs = (int) round((microtime(true) - $companyAssocStart) * 1000);
$contactAssocStart = microtime(true);
$contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');
$contactAssocMs = (int) round((microtime(true) - $contactAssocStart) * 1000);
$prepareStart = microtime(true);
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
$prepareTimings = [];
$associationsData = $this->prepareAssociatedEntities(
$companyAssociations,
$contactAssociations,
$prepareTimings
);
$prepareMs = (int) round((microtime(true) - $prepareStart) * 1000);
$missingCompanies = count(array_diff(
$allCompanyIds,
array_keys($associationsData['company_id_mappings'] ?? [])
));
$missingContacts = count(array_diff(
$allContactIds,
array_keys($associationsData['contact_id_mappings'] ?? [])
));
$existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(
$this->config,
array_map('strval', $dealIds)
);
$existingCrmIdSet = array_flip($existingCrmIds);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [
'teamId' => $this->team->getId(),
'dealCount' => count($dealIds),
'error' => $e->getMessage(),
]);
throw $e;
}
$loopStart = microtime(true);
foreach ($deals as $deal) {
$dealStart = microtime(true);
try {
$deal['associations'] = $this->prepareAssociationsForOpportunity(
$deal['id'],
$companyAssociations,
$contactAssociations,
$associationsData
);
$syncedOpportunity = $this->importOrUpdateOpportunity(
$deal,
isset($existingCrmIdSet[(string) $deal['id']])
);
if ($syncedOpportunity) {
$syncedOpportunities['success'][] = $syncedOpportunity;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [
'teamId' => $this->team->getId(),
'crmId' => $deal['id'],
'error' => $e->getMessage(),
]);
$syncedOpportunities['failed_ids'][] = $deal['id'];
$syncedOpportunities['errors'][$deal['id']] = $e->getMessage();
}
$dealMs = (int) round((microtime(true) - $dealStart) * 1000);
if ($dealMs > 1000) {
$slowDeals[] = ['crmId' => $deal['id'], 'ms' => $dealMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importOpportunityBatch timing', [
'teamId' => $this->team->getId(),
'deal_count' => count($deals),
'total_ms' => $totalMs,
'company_assoc_api_ms' => $companyAssocMs,
'contact_assoc_api_ms' => $contactAssocMs,
'prepare_entities_ms' => $prepareMs,
'prepare_accounts_ms' => $prepareTimings['accounts_ms'],
'prepare_contacts_ms' => $prepareTimings['contacts_ms'],
'total_companies' => count($allCompanyIds),
'missing_companies' => $missingCompanies,
'total_contacts' => count($allContactIds),
'missing_contacts' => $missingContacts,
'deals_loop_ms' => $loopMs,
'avg_deal_ms' => ! empty($deals) ? (int) round($loopMs / count($deals)) : 0,
'slow_deals_count' => count($slowDeals),
'slow_deals' => array_slice($slowDeals, 0, 10),
]);
return $syncedOpportunities;
}
/**
* Prepare associated entities for opportunities with optimized batch processing
* Returns structured data with CRM ID to DB ID mappings for each opportunity
*/
private function prepareAssociatedEntities(
array $companyAssociations,
array $contactAssociations,
array &$timings = []
): array {
// Step 1: Collect all unique company and contact IDs from associations
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
// Step 2: Batch sync missing entities and get CRM ID to DB ID mappings
$companyIdMappings = [];
$contactIdMappings = [];
$accountsMs = 0;
$contactsMs = 0;
if (! empty($allCompanyIds)) {
$start = microtime(true);
$companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);
$accountsMs = (int) round((microtime(true) - $start) * 1000);
}
if (! empty($allContactIds)) {
$start = microtime(true);
$contactIdMappings = $this->prepareAssociatedContacts($allContactIds);
$contactsMs = (int) round((microtime(true) - $start) * 1000);
}
$timings = [
'accounts_ms' => $accountsMs,
'contacts_ms' => $contactsMs,
];
return [
'company_id_mappings' => $companyIdMappings,
'contact_id_mappings' => $contactIdMappings,
];
}
/**
* Flatten association data to get unique IDs
*/
private function flattenAssociationIds(array $associations): array
{
$ids = [];
foreach ($associations as $dealAssociations) {
if (is_array($dealAssociations)) {
foreach ($dealAssociations as $id) {
$ids[$id] = true;
}
}
}
return array_keys($ids);
}
/**
* Batch sync missing accounts
*/
private function prepareAssociatedAccounts(array $companyIds): array
{
// Find which accounts already exist (lean covering-index lookup)
$existingAccountsData = $this->crmEntityRepository
->getExistingAccountIdsMap($this->config, $companyIds);
$missingCompanyIds = array_diff($companyIds, array_keys($existingAccountsData));
if (empty($missingCompanyIds)) {
return $existingAccountsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [
'teamId' => $this->team->getUuid(),
'total_companies' => count($companyIds),
'existing_companies' => count($existingAccountsData),
'missing_companies' => count($missingCompanyIds),
]);
// we already have limit on opportunity ids count
// Initialize variable before try block
$syncedAccountsData = [];
try {
$syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [
'size' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
$syncedAccountsData = [];
}
return $existingAccountsData + $syncedAccountsData;
}
/**
* Prepare associated contacts - find existing and sync missing ones
* Returns mapping of CRM ID to DB ID
*/
private function prepareAssociatedContacts(array $contactIds): array
{
// Find which contacts already exist (lean covering-index lookup)
$existingContactsData = $this->crmEntityRepository
->getExistingContactIdsMap($this->config, $contactIds);
$missingContactIds = array_diff($contactIds, array_keys($existingContactsData));
if (empty($missingContactIds)) {
return $existingContactsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [
'teamId' => $this->team->getUuid(),
'total_contacts' => count($contactIds),
'existing_contacts' => count($existingContactsData),
'missing_contacts' => count($missingContactIds),
]);
// Sync missing contacts using batch API
try {
$syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [
'size' => count($missingContactIds),
'error' => $e->getMessage(),
]);
$syncedContactsData = [];
}
return $existingContactsData + $syncedContactsData;
}
private function batchSyncCrmObjects(string $objectType, array $crmIds): array
{
$syncObjects = [];
$crmObjectIds = array_values($crmIds);
foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {
try {
$objects = $objectType === 'companies' ?
$this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :
$this->client->getContactsByIds($chunk, $this->getContactFields());
foreach ($objects as $objectId => $objectData) {
$this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [
'requested_count' => count($chunk),
'synced_count' => count($objects),
]);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [
'ids' => $chunk,
'error' => $e->getMessage(),
]);
}
}
return $syncObjects;
}
private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void
{
try {
$object = $objectType === 'companies' ?
$this->importAccount($objectData) :
$this->importContact($objectData);
if ($object) {
$syncObjects[$object->getCrmProviderId()] = $object->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [
'id' => $objectId,
'error' => $e->getMessage(),
]);
}
}
/**
* Prepare associations for a single opportunity
*
* The return value is an array with the following structure:
* [
* 'companies' => [
* $companyCrmId => $companyId,
* ...
* ],
* 'contacts' => [
* $contactCrmId => $contactId,
* ...
* ],
* 'account_id' => $accountId,
* ]
*/
private function prepareAssociationsForOpportunity(
string $oppCrmId,
array $companyAssociations,
array $contactAssociations,
array $associationsData
): array {
$associations = [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
$oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];
foreach ($oppCompanyIds as $companyCrmId) {
if (isset($associationsData['company_id_mappings'][$companyCrmId])) {
$associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];
// Set primary account (first company becomes primary account)
if ($associations['account_id'] === null) {
$associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];
}
}
}
$oppContactIds = $contactAssociations[$oppCrmId] ?? [];
foreach ($oppContactIds as $contactCrmId) {
if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {
$associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];
}
}
return $associations;
}
/**
* Update only associations for an opportunity
*/
private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void
{
// Update contact associations
$this->importOpportunityContacts($opportunity, $associations['contacts']);
// Update company (account) associations
$this->updateOpportunityAccount($opportunity, $associations['account_id']);
}
/**
* Remove all contact associations from an opportunity
*/
private function removeAllOpportunityContacts(Opportunity $opportunity): void
{
$currentCount = (int) $opportunity->contacts()->count();
if ($currentCount > 0) {
$opportunity->contacts()->detach();
$this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_count' => $currentCount,
]);
}
}
private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void
{
if ($accountId === null) {
// No account ID provided - keep current account
return;
}
$currentAccountId = $opportunity->getAccountId();
// Only update if account has changed
if ($currentAccountId !== $accountId) {
$opportunity->account_id = $accountId;
$opportunity->save();
$this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [
'opportunity_id' => $opportunity->getId(),
'old_account_id' => $currentAccountId,
'new_account_id' => $accountId,
]);
}
}
/**
* Find existing opportunities by external IDs (OPTIMIZED VERSION)
* Uses batch query for better performance
*/
private function findExistingOpportunities(array $crmIds): Collection
{
return $this->crmEntityRepository
->findOpportunitiesByExternalIds($this->config, $crmIds);
}
private function processOpportunityBatch(array $opportunities): int
{
$syncedOpportunities = $this->importOpportunityBatch($opportunities);
return count($syncedOpportunities['success'] ?? []);
}
/**
* Convert single deal associations from HubSpot format to internal format
* Handles both HubSpot SDK objects and array formats
*
* @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed
*
* @return array Processed associations with DB IDs
*/
private function convertDealAssociations(array $opportunityAssociations): array
{
$associations = $this->initializeAssociationsStructure();
if (empty($opportunityAssociations)) {
return $associations;
}
$associationIds = $this->extractAssociationIds($opportunityAssociations);
$this->processCompanyAssociations($associationIds, $associations);
$this->processContactAssociations($associationIds, $associations);
return $associations;
}
private function initializeAssociationsStructure(): array
{
return [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
}
private function extractAssociationIds(array $opportunityAssociations): array
{
$associationIds = [];
foreach ($opportunityAssociations as $type => $associationData) {
if (! empty($associationData)) {
$associationIds[$type] = $this->convertSingleDealAssociations($associationData);
}
}
return $associationIds;
}
private function processCompanyAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['companies'])) {
return;
}
$companyId = $associationIds['companies'][0];
$account = $this->findOrSyncAccount($companyId);
if ($account instanceof Account) {
$associations['companies'][$companyId] = $account->getId();
$associations['account_id'] = $account->getId();
}
}
private function processContactAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['contacts'])) {
return;
}
foreach ($associationIds['contacts'] as $contactId) {
$contact = $this->findOrSyncContact($contactId);
if ($contact instanceof Contact) {
$associations['contacts'][$contactId] = $contact->getId();
}
}
}
private function findOrSyncAccount(string $companyId): ?Account
{
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);
if (! $account instanceof Account) {
$account = $this->syncAccount($companyId);
}
return $account;
}
private function findOrSyncContact(string $contactId): ?Contact
{
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);
if (! $contact instanceof Contact) {
$contact = $this->syncContact($contactId);
}
return $contact;
}
private function convertSingleDealAssociations($opportunityAssociations = null): array
{
$associationData = [];
if ($opportunityAssociations === null) {
return $associationData;
}
// Handle array input (from extractAssociationIds)
if (is_array($opportunityAssociations)) {
return $opportunityAssociations;
}
// Handle CollectionResponseAssociatedId object
if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {
foreach ($opportunityAssociations->getResults() as $association) {
$associationData[] = $association->getId();
}
}
return $associationData;
}
private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity
{
if (empty($crmData['properties'])) {
return null;
}
$properties = $crmData['properties'];
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = (string) $properties['hubspot_owner_id'];
$profile = $this->getCachedOwnerProfile($ownerId);
}
$associations = $crmData['associations'] ?? [];
$accountId = $this->resolveAccountId($associations);
// Only fetch account if we need it for user_id fallback
$accountUserId = null;
if ($profile?->getUserId() === null && $accountId !== null) {
$accountUserId = $this->crmEntityRepository
->findAccountByConfigurationAndId(
$this->config,
$accountId
)?->getUserId();
}
$crmId = (string) $crmData['id'];
if (($profile?->getUserId() === null) && ($accountUserId === null)) {
$this->logger->error(
'[HubSpot] Skip import, no user_id found',
[
'id' => $crmId,
]
);
return null;
}
$opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(
$this->config,
$crmId
);
if ($opportunityExists) {
return $this->updateOpportunity(
$crmId,
$properties,
$associations,
$accountUserId
);
}
return $this->createOpportunity(
$crmId,
$properties,
$associations,
$accountUserId,
);
}
/**
* Create new opportunity
*/
private function createOpportunity(
string $crmId,
array $properties,
array $associations,
?int $accountUserId = null
): ?Opportunity {
$accountId = $this->resolveAccountId($associations);
if (! $accountId) {
return null;
}
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
if (! $businessProcess) {
return null;
}
$stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);
if (! $stage) {
return null;
}
$data = $this->buildOpportunityData(
$properties,
$accountId,
$businessProcess,
$stage,
$accountUserId
);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->importOpportunityContacts($opportunity, $associations['contacts']);
if ($opportunity->wasRecentlyCreated) {
MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());
}
return $opportunity;
}
/**
* Update existing opportunity
*/
private function updateOpportunity(
string $crmId,
array $properties,
array $associations,
?int $accountUserId = null
): Opportunity {
$accountId = $this->resolveAccountId($associations);
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
$stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;
$data = $this->buildOpportunityData(
$properties,
$accountId,
$businessProcess,
$stage,
$accountUserId
);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->updateOpportunityAssociations($opportunity, $associations);
return $opportunity;
}
private function resolveAccountId(array $associations): ?int
{
if (! empty($associations['account_id'])) {
return $associations['account_id'];
}
if (empty($associations)) {
return null;
}
// Fallback: use first company as account (currently SDK returns one company)
foreach ($associations['companies'] as $accountId) {
return $accountId;
}
return null;
}
private function buildOpportunityData(
array $properties,
?int $accountId,
?BusinessProcess $businessProcess,
?Stage $stage,
?int $accountUserId = null
): array {
$ownerId = null;
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->getCachedOwnerProfile((string) $ownerId);
}
$userId = $profile?->getUserId() ?? $accountUserId;
$name = 'Unknown';
if (isset($properties['dealname'])) {
$name = mb_strimwidth($properties['dealname'], 0, 128);
}
$amount = $this->resolveAmount($properties);
$currency = $properties['deal_currency_code'] ?? null;
$closeDate = null;
if (! empty($properties['closedate'])) {
$closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');
}
$remotelyCreatedAt = null;
if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {
$date = $this->parseCleanDatetime($properties['createdate']);
$remotelyCreatedAt = $date?->format('Y-m-d H:i:s');
}
$closedStages = $this->getClosedDealStages();
$isWon = in_array($properties['dealstage'], $closedStages['won']);
$isLost = in_array($properties['dealstage'], $closedStages['lost']);
$data = [
'team_id' => $this->team->getId(),
'user_id' => $userId,
'owner_id' => $ownerId,
'name' => $name,
'value' => ! empty($amount) ? $amount : null,
'currency_code' => CurrencyFormatter::formatCode($currency),
'close_date' => $closeDate,
'is_closed' => $isWon || $isLost,
'is_won' => $isWon,
'remotely_created_at' => $remotelyCreatedAt,
'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),
'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),
];
if ($accountId) {
$data['account_id'] = $accountId;
}
if ($stage) {
$data['stage_id'] = $stage->id;
}
if ($businessProcess) {
$recordType = $this->getCachedBusinessProcessRecordType($businessProcess);
if ($recordType) {
$data['record_type_id'] = $recordType->id;
}
}
return $data;
}
private function getCachedOwnerProfile(string $ownerId): ?Profile
{
$cacheKey = $this->config->getId() . ':' . $ownerId;
if (array_key_exists($cacheKey, $this->cachedOwnerProfiles)) {
return $this->cachedOwnerProfiles[$cacheKey];
}
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, $ownerId);
$this->cachedOwnerProfiles[$cacheKey] = $profile;
return $profile;
}
private function getCachedBusinessProcessRecordType(BusinessProcess $businessProcess): mixed
{
$cacheKey = $this->config->getId() . ':' . $businessProcess->getId();
if (array_key_exists($cacheKey, $this->cachedRecordTypes)) {
return $this->cachedRecordTypes[$cacheKey];
}
$recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);
$this->cachedRecordTypes[$cacheKey] = $recordType;
return $recordType;
}
private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess
{
if ($pipelineId === null) {
return null;
}
$cacheKey = $this->getBusinessProcessCacheKey($pipelineId);
if (isset($this->cachedBusinessProcesses[$cacheKey])) {
return $this->cachedBusinessProcesses[$cacheKey];
}
$businessProcess = $this->getBusinessProcess($pipelineId);
if (! $businessProcess instanceof BusinessProcess) {
$this->importStages();
$this->cachedClosedDealStages = null;
$businessProcess = $this->getBusinessProcess($pipelineId);
}
if (! $businessProcess instanceof BusinessProcess) {
$this->logger->info(
'[HubSpot] Deal is not attached to a pipeline',
[
'pipeline' => $pipelineId]
);
}
$this->cachedBusinessProcesses[$cacheKey] = $businessProcess;
return $businessProcess;
}
private function getBusinessProcess(string $pipelineId): ?BusinessProcess
{
return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);
}
private function getBusinessProcessCacheKey(string $pipelineId): string
{
return $this->config->getId() . '_' . $pipelineId;
}
private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage
{
if (empty($stageId)) {
return null;
}
$cacheKey = $this->config->getId() . ':' . $businessProcess->getId() . ':' . $stageId;
if (isset($this->cachedStages[$cacheKey])) {
return $this->cachedStages[$cacheKey];
}
$stage = $this->crmEntityRepository->getPipelineStageByConditions(
$businessProcess,
[
'crm_provider_id' => $stageId,
'type' => Stage::TYPE_OPPORTUNITY,
]
);
if ($stage === null) {
$this->importStages(null, $stageId);
$this->cachedClosedDealStages = null;
}
if ($stage === null) {
$this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);
}
$this->cachedStages[$cacheKey] = $stage;
return $stage;
}
private function resolveAmount(array $properties): ?string
{
$amount = null;
if (! empty($properties['amount'])) {
$amount = str_replace(',', '', $properties['amount']);
}
if ($this->config->hasDefaultCurrencyFieldSet()) {
$valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();
$amount = $properties[$valueFieldName] ?? $amount;
}
return $amount;
}
private function parseCleanDatetime(string $datetime): ?Carbon
{
// Treat pre-1980 values as invalid
$minValidDate = Carbon::parse('1980-01-01 00:00:00');
try {
$date = Carbon::parse($datetime);
if ($minValidDate->gt($date)) {
return null;
}
return $date;
} catch (Exception) {
return null; // On parse error, treat as null
}
}
private function resolveDealProbability(?string $stageProbability): int
{
if ($stageProbability === null) {
return 0;
}
$probability = (float) $stageProbability;
return $probability > 1 ? 0 : (int) ($probability * 100);
}
private function resolveForecastCategory(?string $forecastCategory): string
{
if (! $forecastCategory) {
return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;
}
$forecastCategory = str_replace('_', ' ', $forecastCategory);
return ucwords(strtolower($forecastCategory));
}
private function importExternalFieldData(array $properties, int $opportunityId): void
{
$this->importOpportunityCrmFieldData(
$properties,
$this->getCachedOpportunitySyncableFields(),
$opportunityId
);
}
private function getCachedOpportunitySyncableFields(): array
{
$cacheKey = (string) $this->config->getId();
if (! isset($this->cachedOpportunitySyncableFields[$cacheKey])) {
$this->cachedOpportunitySyncableFields[$cacheKey] = $this->getOpportunitySyncableFields();
}
return $this->cachedOpportunitySyncableFields[$cacheKey];
}
private function importOpportunityContacts(Opportunity $opportunity, array $associations): void
{
// Handle empty or missing contact associations
if (empty($associations)) {
// Remove all existing contact associations if none provided
$this->removeAllOpportunityContacts($opportunity);
return;
}
// Use differential sync approach for better performance and accuracy
$this->syncOpportunityContactsDifferential($opportunity, $associations);
}
/**
* Sync opportunity contacts using differential approach
* This compares current vs new associations and only makes necessary changes
*/
private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void
{
$currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);
$contactAssociationIds = array_keys($contactAssociations);
$contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);
$contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);
if (empty($contactsToAdd) && empty($contactsToRemove)) {
return;
}
$this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);
$this->removeContactAssociations($opportunity, $contactsToRemove);
$this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);
}
private function getCurrentContactCrmIds(Opportunity $opportunity): array
{
return $opportunity->contacts()
->pluck('contacts.crm_provider_id')
->toArray();
}
private function logContactAssociationChanges(
Opportunity $opportunity,
array $currentContactCrmIds,
array $contactAssociations,
array $contactsToAdd,
array $contactsToRemove
): void {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [
'opportunity_id' => $opportunity->getId(),
'current_contacts' => $currentContactCrmIds,
'new_contacts' => $contactAssociations,
'contacts_to_add' => $contactsToAdd,
'contacts_to_remove' => $contactsToRemove,
]);
}
private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void
{
if (empty($contactsToRemove)) {
return;
}
$contactsToDetach = $opportunity->contacts()
->whereIn('contacts.crm_provider_id', $contactsToRemove)
->pluck('contacts.id')
->toArray();
if (! empty($contactsToDetach)) {
$opportunity->contacts()->detach($contactsToDetach);
$this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_contact_crm_ids' => $contactsToRemove,
'removed_contact_count' => count($contactsToDetach),
]);
}
}
private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void
{
if (empty($contactsToAdd)) {
return;
}
$contactsAdded = [];
foreach ($contactsToAdd as $crmId) {
$id = $contactAssociations[$crmId];
if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {
$contactsAdded[] = $crmId;
}
}
$this->logAddedContacts($opportunity, $contactsAdded);
}
private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool
{
try {
return $this->performContactAttachment($opportunity, $id, $crmId);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [
'opportunity_id' => $opportunity->getId(),
'contact_crm_id' => $crmId,
'error' => $e->getMessage(),
]);
return false;
}
}
private function performContactAttachment(Opportunity $opportunity, int $contactId, string $crmId): bool
{
try {
$opportunity->contacts()->attach($contactId, [
'crm_provider_id' => $crmId,
]);
return true;
} catch (\Illuminate\Database\QueryException $e) {
if (str_contains($e->getMessage(), 'Duplicate entry')) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [
'contact_id' => $contactId,
'contact_crm_id' => $crmId,
'opportunity_id' => $opportunity->getId(),
]);
return false;
}
throw $e;
}
}
private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void
{
if (! empty($contactsAdded)) {
$this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [
'opportunity_id' => $opportunity->getId(),
'added_contact_crm_ids' => $contactsAdded,
'added_contacts_count' => count($contactsAdded),
]);
}
}
}
Execute
Explain Plan
Browse Query History...
|
[{"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":"#12121 on JY-20963-fix-import-on-deleted-entity, menu","depth":5,"on_screen":true,"help_text":"Pull request #12121 exists for current branch JY-20963-fix-import-on-deleted-entity, but pull request details loading failed","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":"ServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'ServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'ServiceTest'","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":"Show Replace Field","depth":4,"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,"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"cachedClosedDealStages","depth":4,"on_screen":true,"value":"cachedClosedDealStages","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"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,"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Words","depth":3,"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Regex","depth":3,"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.0,"top":0.0,"width":0.015277778,"height":0.024444444},"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.0,"top":0.0,"width":0.015277778,"height":0.024444444},"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.0,"top":0.0,"width":0.015277778,"height":0.024444444},"on_screen":false,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2/6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Occurrence","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Occurrence","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Filter Search Results","depth":4,"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,"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,"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":"75","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.020833334,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.016666668,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"22","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.020833334,"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\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\CollectionResponseAssociatedId;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Models\\Account;\nuse Exception;\nuse Jiminny\\Component\\DealInsights\\Forecast\\Forecast;\nuse Jiminny\\Jobs\\Crm\\MatchActivitiesToNewOpportunity;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Crm\\BusinessProcess;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Models\\Crm\\Profile;\nuse Jiminny\\Models\\Opportunity;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DealFieldsService;\nuse Jiminny\\Services\\Crm\\Hubspot\\OpportunitySyncStrategy\\HubspotSingleSyncStrategy;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Services\\Crm\\OpportunitySyncStrategyResolver;\nuse Jiminny\\Utils\\CurrencyFormatter;\n\n/**\n * Optimized sync methods for better performance\n * These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains\n */\ntrait OpportunitySyncTrait\n{\n private const int BATCH_SIZE = 100;\n private const int BATCH_PROCESS_SIZE = 800;\n\n protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;\n protected CrmEntityRepository $crmEntityRepository;\n protected DealFieldsService $dealFieldsService;\n\n private ?array $cachedClosedDealStages = null;\n private array $cachedBusinessProcesses = [];\n private array $cachedStages = [];\n /** @var array<string, array<string>> keyed by config id */\n private array $cachedOpportunitySyncableFields = [];\n /** @var array<string, mixed> keyed by configId:ownerId */\n private array $cachedOwnerProfiles = [];\n /** @var array<string, mixed> keyed by configId:businessProcessId */\n private array $cachedRecordTypes = [];\n\n public function syncOpportunities(array $parameters, ?string $strategy = null): int\n {\n $startTime = microtime(true);\n $strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);\n $parameters['config'] = $this->config;\n $syncCount = 0;\n $reportedTotal = 0;\n $lastSyncedId = [];\n $strategyNames = [];\n\n try {\n foreach ($strategies as $strategyName => $syncStrategy) {\n $strategyNames[] = $strategyName;\n $this->logger->info(\n '[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' . $strategyName,\n ['team' => $this->team->getId()]\n );\n\n $total = 0;\n $lastId = null;\n $buffer = [];\n\n // HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies\n foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {\n $buffer[] = $hsOpportunity;\n\n // process every 800 rows (fits < 1 000 association limit)\n if (\\count($buffer) >= self::BATCH_PROCESS_SIZE) {\n $syncCount += $this->processOpportunityBatch($buffer);\n $buffer = [];\n }\n }\n\n // leftovers\n if ($buffer) {\n $syncCount += $this->processOpportunityBatch($buffer);\n }\n\n $reportedTotal += $total;\n $lastSyncedId = $lastId;\n }\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException | CrmException $e) {\n $this->handleSyncException($e, $parameters);\n }\n\n $durationMs = round((microtime(true) - $startTime) * 1000, 2);\n $this->logger->info(\n '[HubSpot] Synced opportunities',\n [\n 'team' => $this->team->getId(),\n 'strategies' => implode(',', $strategyNames),\n 'sync_count' => $syncCount,\n 'total' => $reportedTotal,\n 'last_synced_id' => $lastSyncedId,\n 'duration_ms' => $durationMs,\n ]\n );\n\n return $reportedTotal;\n }\n\n private function handleSyncException(\\Throwable $e, array $parameters): void\n {\n if (($parameters['since'] ?? null) instanceof Carbon) {\n $parameters['since'] = $parameters['since']->toDateTimeString();\n }\n $parameters['config'] = $this->config->getId();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [\n 'teamId' => $this->team->getUuid(),\n 'parameters' => $parameters,\n 'reason' => $e->getMessage(),\n ]);\n }\n\n /**\n * @inheritdoc\n */\n public function syncOpportunity(string $crmId): ?Opportunity\n {\n $strategy = $this->opportunitySyncStrategyResolver->resolve(\n $this->config,\n OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,\n );\n\n $parameters = [\n 'config' => $this->config,\n 'crm_id' => $crmId,\n ];\n\n try {\n if (! $strategy instanceof HubspotSingleSyncStrategy) {\n throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');\n }\n\n $hsOpportunity = $strategy->fetchOpportunity($parameters);\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);\n\n return $this->importOrUpdateOpportunity($hsOpportunity);\n }\n\n /**\n * Process webhook-collected opportunity batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportOpportunityBatch jobs for batch processing.\n *\n * @return int Number of opportunity IDs dispatched to jobs\n */\n public function batchSyncOpportunities(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,\n $configId\n );\n }\n\n /**\n * Import a batch of opportunities by their CRM IDs.\n * Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().\n *\n * @param array<string> $crmIds HubSpot deal CRM IDs\n *\n * @return array{success: array, failed_ids: array, errors?: array<string, string>}\n */\n public function importOpportunityBatchByIds(array $crmIds): array\n {\n $fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);\n\n $allDeals = [];\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n $deals = $this->client->getOpportunitiesByIds($chunk, $fields);\n foreach ($deals as $deal) {\n $allDeals[] = $deal;\n }\n }\n\n // IDs not returned by HubSpot are likely deleted or inaccessible deals.\n // These are not failures — retrying won't bring them back.\n $fetchedIds = array_map('strval', array_column($allDeals, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allDeals),\n ]);\n }\n\n if (empty($allDeals)) {\n return ['success' => [], 'failed_ids' => []];\n }\n\n return $this->importOpportunityBatch($allDeals);\n }\n\n private function getClosedDealStages(): array\n {\n if ($this->cachedClosedDealStages !== null) {\n return $this->cachedClosedDealStages;\n }\n\n $stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);\n $data = [\n 'lost' => [],\n 'won' => [],\n ];\n\n foreach ($stages as $stage) {\n if ($stage->probability == 0.00) {\n $data['lost'][] = $stage->crm_provider_id;\n }\n if ($stage->probability == 100.00) {\n $data['won'][] = $stage->crm_provider_id;\n }\n }\n\n $this->cachedClosedDealStages = $data;\n\n return $data;\n }\n\n /**\n * Import deals into the database with pre-fetched associations.\n *\n * API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT\n * caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()\n * where Laravel retries the whole job with backoff. After all retries exhausted,\n * failed() requeues all IDs to Redis.\n *\n * The per-deal loop catches exceptions individually. A deal can end up in three states:\n * - success: imported/updated successfully\n * - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)\n * These are permanent issues — retrying won't fix them.\n * - skipped (null): missing dependencies (no account, unknown pipeline/stage).\n * This is acceptable — the deal cannot be imported until those exist.\n */\n private function importOpportunityBatch(array $deals): array\n {\n $syncedOpportunities = [\n 'success' => [],\n 'failed_ids' => [],\n ];\n $dealIds = array_column($deals, 'id');\n $batchStart = microtime(true);\n $slowDeals = [];\n\n // Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the\n // queue job retries the whole batch and eventually requeues all deal IDs back to Redis.\n try {\n $companyAssocStart = microtime(true);\n $companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');\n $companyAssocMs = (int) round((microtime(true) - $companyAssocStart) * 1000);\n\n $contactAssocStart = microtime(true);\n $contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');\n $contactAssocMs = (int) round((microtime(true) - $contactAssocStart) * 1000);\n\n $prepareStart = microtime(true);\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n $prepareTimings = [];\n $associationsData = $this->prepareAssociatedEntities(\n $companyAssociations,\n $contactAssociations,\n $prepareTimings\n );\n $prepareMs = (int) round((microtime(true) - $prepareStart) * 1000);\n\n $missingCompanies = count(array_diff(\n $allCompanyIds,\n array_keys($associationsData['company_id_mappings'] ?? [])\n ));\n $missingContacts = count(array_diff(\n $allContactIds,\n array_keys($associationsData['contact_id_mappings'] ?? [])\n ));\n\n $existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(\n $this->config,\n array_map('strval', $dealIds)\n );\n $existingCrmIdSet = array_flip($existingCrmIds);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [\n 'teamId' => $this->team->getId(),\n 'dealCount' => count($dealIds),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n $loopStart = microtime(true);\n foreach ($deals as $deal) {\n $dealStart = microtime(true);\n\n try {\n $deal['associations'] = $this->prepareAssociationsForOpportunity(\n $deal['id'],\n $companyAssociations,\n $contactAssociations,\n $associationsData\n );\n\n $syncedOpportunity = $this->importOrUpdateOpportunity(\n $deal,\n isset($existingCrmIdSet[(string) $deal['id']])\n );\n if ($syncedOpportunity) {\n $syncedOpportunities['success'][] = $syncedOpportunity;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $deal['id'],\n 'error' => $e->getMessage(),\n ]);\n $syncedOpportunities['failed_ids'][] = $deal['id'];\n $syncedOpportunities['errors'][$deal['id']] = $e->getMessage();\n }\n\n $dealMs = (int) round((microtime(true) - $dealStart) * 1000);\n if ($dealMs > 1000) {\n $slowDeals[] = ['crmId' => $deal['id'], 'ms' => $dealMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importOpportunityBatch timing', [\n 'teamId' => $this->team->getId(),\n 'deal_count' => count($deals),\n 'total_ms' => $totalMs,\n 'company_assoc_api_ms' => $companyAssocMs,\n 'contact_assoc_api_ms' => $contactAssocMs,\n 'prepare_entities_ms' => $prepareMs,\n 'prepare_accounts_ms' => $prepareTimings['accounts_ms'],\n 'prepare_contacts_ms' => $prepareTimings['contacts_ms'],\n 'total_companies' => count($allCompanyIds),\n 'missing_companies' => $missingCompanies,\n 'total_contacts' => count($allContactIds),\n 'missing_contacts' => $missingContacts,\n 'deals_loop_ms' => $loopMs,\n 'avg_deal_ms' => ! empty($deals) ? (int) round($loopMs / count($deals)) : 0,\n 'slow_deals_count' => count($slowDeals),\n 'slow_deals' => array_slice($slowDeals, 0, 10),\n ]);\n\n return $syncedOpportunities;\n }\n\n /**\n * Prepare associated entities for opportunities with optimized batch processing\n * Returns structured data with CRM ID to DB ID mappings for each opportunity\n */\n private function prepareAssociatedEntities(\n array $companyAssociations,\n array $contactAssociations,\n array &$timings = []\n ): array {\n // Step 1: Collect all unique company and contact IDs from associations\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n\n // Step 2: Batch sync missing entities and get CRM ID to DB ID mappings\n $companyIdMappings = [];\n $contactIdMappings = [];\n $accountsMs = 0;\n $contactsMs = 0;\n\n if (! empty($allCompanyIds)) {\n $start = microtime(true);\n $companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);\n $accountsMs = (int) round((microtime(true) - $start) * 1000);\n }\n\n if (! empty($allContactIds)) {\n $start = microtime(true);\n $contactIdMappings = $this->prepareAssociatedContacts($allContactIds);\n $contactsMs = (int) round((microtime(true) - $start) * 1000);\n }\n\n $timings = [\n 'accounts_ms' => $accountsMs,\n 'contacts_ms' => $contactsMs,\n ];\n\n return [\n 'company_id_mappings' => $companyIdMappings,\n 'contact_id_mappings' => $contactIdMappings,\n ];\n }\n\n /**\n * Flatten association data to get unique IDs\n */\n private function flattenAssociationIds(array $associations): array\n {\n $ids = [];\n foreach ($associations as $dealAssociations) {\n if (is_array($dealAssociations)) {\n foreach ($dealAssociations as $id) {\n $ids[$id] = true;\n }\n }\n }\n\n return array_keys($ids);\n }\n\n /**\n * Batch sync missing accounts\n */\n private function prepareAssociatedAccounts(array $companyIds): array\n {\n // Find which accounts already exist (lean covering-index lookup)\n $existingAccountsData = $this->crmEntityRepository\n ->getExistingAccountIdsMap($this->config, $companyIds);\n\n $missingCompanyIds = array_diff($companyIds, array_keys($existingAccountsData));\n\n if (empty($missingCompanyIds)) {\n return $existingAccountsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [\n 'teamId' => $this->team->getUuid(),\n 'total_companies' => count($companyIds),\n 'existing_companies' => count($existingAccountsData),\n 'missing_companies' => count($missingCompanyIds),\n ]);\n\n // we already have limit on opportunity ids count\n // Initialize variable before try block\n $syncedAccountsData = [];\n\n try {\n $syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [\n 'size' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedAccountsData = [];\n }\n\n return $existingAccountsData + $syncedAccountsData;\n }\n\n /**\n * Prepare associated contacts - find existing and sync missing ones\n * Returns mapping of CRM ID to DB ID\n */\n private function prepareAssociatedContacts(array $contactIds): array\n {\n // Find which contacts already exist (lean covering-index lookup)\n $existingContactsData = $this->crmEntityRepository\n ->getExistingContactIdsMap($this->config, $contactIds);\n\n $missingContactIds = array_diff($contactIds, array_keys($existingContactsData));\n\n if (empty($missingContactIds)) {\n return $existingContactsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [\n 'teamId' => $this->team->getUuid(),\n 'total_contacts' => count($contactIds),\n 'existing_contacts' => count($existingContactsData),\n 'missing_contacts' => count($missingContactIds),\n ]);\n\n // Sync missing contacts using batch API\n try {\n $syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [\n 'size' => count($missingContactIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedContactsData = [];\n }\n\n return $existingContactsData + $syncedContactsData;\n }\n\n private function batchSyncCrmObjects(string $objectType, array $crmIds): array\n {\n $syncObjects = [];\n $crmObjectIds = array_values($crmIds);\n\n foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {\n try {\n $objects = $objectType === 'companies' ?\n $this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :\n $this->client->getContactsByIds($chunk, $this->getContactFields());\n\n foreach ($objects as $objectId => $objectData) {\n $this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [\n 'requested_count' => count($chunk),\n 'synced_count' => count($objects),\n ]);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [\n 'ids' => $chunk,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncObjects;\n }\n\n private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void\n {\n try {\n $object = $objectType === 'companies' ?\n $this->importAccount($objectData) :\n $this->importContact($objectData);\n\n if ($object) {\n $syncObjects[$object->getCrmProviderId()] = $object->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [\n 'id' => $objectId,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n /**\n * Prepare associations for a single opportunity\n *\n * The return value is an array with the following structure:\n * [\n * 'companies' => [\n * $companyCrmId => $companyId,\n * ...\n * ],\n * 'contacts' => [\n * $contactCrmId => $contactId,\n * ...\n * ],\n * 'account_id' => $accountId,\n * ]\n */\n private function prepareAssociationsForOpportunity(\n string $oppCrmId,\n array $companyAssociations,\n array $contactAssociations,\n array $associationsData\n ): array {\n $associations = [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n\n $oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];\n foreach ($oppCompanyIds as $companyCrmId) {\n if (isset($associationsData['company_id_mappings'][$companyCrmId])) {\n $associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];\n\n // Set primary account (first company becomes primary account)\n if ($associations['account_id'] === null) {\n $associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];\n }\n }\n }\n\n $oppContactIds = $contactAssociations[$oppCrmId] ?? [];\n foreach ($oppContactIds as $contactCrmId) {\n if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {\n $associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];\n }\n }\n\n return $associations;\n }\n\n /**\n * Update only associations for an opportunity\n */\n private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void\n {\n // Update contact associations\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n // Update company (account) associations\n $this->updateOpportunityAccount($opportunity, $associations['account_id']);\n }\n\n /**\n * Remove all contact associations from an opportunity\n */\n private function removeAllOpportunityContacts(Opportunity $opportunity): void\n {\n $currentCount = (int) $opportunity->contacts()->count();\n\n if ($currentCount > 0) {\n $opportunity->contacts()->detach();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_count' => $currentCount,\n ]);\n }\n }\n\n private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void\n {\n if ($accountId === null) {\n // No account ID provided - keep current account\n return;\n }\n\n $currentAccountId = $opportunity->getAccountId();\n\n // Only update if account has changed\n if ($currentAccountId !== $accountId) {\n $opportunity->account_id = $accountId;\n $opportunity->save();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [\n 'opportunity_id' => $opportunity->getId(),\n 'old_account_id' => $currentAccountId,\n 'new_account_id' => $accountId,\n ]);\n }\n }\n\n /**\n * Find existing opportunities by external IDs (OPTIMIZED VERSION)\n * Uses batch query for better performance\n */\n private function findExistingOpportunities(array $crmIds): Collection\n {\n return $this->crmEntityRepository\n ->findOpportunitiesByExternalIds($this->config, $crmIds);\n }\n\n private function processOpportunityBatch(array $opportunities): int\n {\n $syncedOpportunities = $this->importOpportunityBatch($opportunities);\n\n return count($syncedOpportunities['success'] ?? []);\n }\n\n /**\n * Convert single deal associations from HubSpot format to internal format\n * Handles both HubSpot SDK objects and array formats\n *\n * @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed\n *\n * @return array Processed associations with DB IDs\n */\n private function convertDealAssociations(array $opportunityAssociations): array\n {\n $associations = $this->initializeAssociationsStructure();\n\n if (empty($opportunityAssociations)) {\n return $associations;\n }\n\n $associationIds = $this->extractAssociationIds($opportunityAssociations);\n\n $this->processCompanyAssociations($associationIds, $associations);\n $this->processContactAssociations($associationIds, $associations);\n\n return $associations;\n }\n\n private function initializeAssociationsStructure(): array\n {\n return [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n }\n\n private function extractAssociationIds(array $opportunityAssociations): array\n {\n $associationIds = [];\n\n foreach ($opportunityAssociations as $type => $associationData) {\n if (! empty($associationData)) {\n $associationIds[$type] = $this->convertSingleDealAssociations($associationData);\n }\n }\n\n return $associationIds;\n }\n\n private function processCompanyAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['companies'])) {\n return;\n }\n\n $companyId = $associationIds['companies'][0];\n $account = $this->findOrSyncAccount($companyId);\n\n if ($account instanceof Account) {\n $associations['companies'][$companyId] = $account->getId();\n $associations['account_id'] = $account->getId();\n }\n }\n\n private function processContactAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['contacts'])) {\n return;\n }\n\n foreach ($associationIds['contacts'] as $contactId) {\n $contact = $this->findOrSyncContact($contactId);\n\n if ($contact instanceof Contact) {\n $associations['contacts'][$contactId] = $contact->getId();\n }\n }\n }\n\n private function findOrSyncAccount(string $companyId): ?Account\n {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);\n\n if (! $account instanceof Account) {\n $account = $this->syncAccount($companyId);\n }\n\n return $account;\n }\n\n private function findOrSyncContact(string $contactId): ?Contact\n {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);\n\n if (! $contact instanceof Contact) {\n $contact = $this->syncContact($contactId);\n }\n\n return $contact;\n }\n\n private function convertSingleDealAssociations($opportunityAssociations = null): array\n {\n $associationData = [];\n\n if ($opportunityAssociations === null) {\n return $associationData;\n }\n\n // Handle array input (from extractAssociationIds)\n if (is_array($opportunityAssociations)) {\n return $opportunityAssociations;\n }\n\n // Handle CollectionResponseAssociatedId object\n if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {\n foreach ($opportunityAssociations->getResults() as $association) {\n $associationData[] = $association->getId();\n }\n }\n\n return $associationData;\n }\n\n private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity\n {\n if (empty($crmData['properties'])) {\n return null;\n }\n\n $properties = $crmData['properties'];\n\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = (string) $properties['hubspot_owner_id'];\n $profile = $this->getCachedOwnerProfile($ownerId);\n }\n\n $associations = $crmData['associations'] ?? [];\n $accountId = $this->resolveAccountId($associations);\n\n // Only fetch account if we need it for user_id fallback\n $accountUserId = null;\n if ($profile?->getUserId() === null && $accountId !== null) {\n $accountUserId = $this->crmEntityRepository\n ->findAccountByConfigurationAndId(\n $this->config,\n $accountId\n )?->getUserId();\n }\n\n $crmId = (string) $crmData['id'];\n\n if (($profile?->getUserId() === null) && ($accountUserId === null)) {\n $this->logger->error(\n '[HubSpot] Skip import, no user_id found',\n [\n 'id' => $crmId,\n ]\n );\n\n return null;\n }\n\n $opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(\n $this->config,\n $crmId\n );\n\n if ($opportunityExists) {\n return $this->updateOpportunity(\n $crmId,\n $properties,\n $associations,\n $accountUserId\n );\n }\n\n return $this->createOpportunity(\n $crmId,\n $properties,\n $associations,\n $accountUserId,\n );\n }\n\n /**\n * Create new opportunity\n */\n private function createOpportunity(\n string $crmId,\n array $properties,\n array $associations,\n ?int $accountUserId = null\n ): ?Opportunity {\n $accountId = $this->resolveAccountId($associations);\n if (! $accountId) {\n return null;\n }\n\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n if (! $businessProcess) {\n return null;\n }\n\n $stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);\n if (! $stage) {\n return null;\n }\n\n $data = $this->buildOpportunityData(\n $properties,\n $accountId,\n $businessProcess,\n $stage,\n $accountUserId\n );\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n if ($opportunity->wasRecentlyCreated) {\n MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());\n }\n\n return $opportunity;\n }\n\n /**\n * Update existing opportunity\n */\n private function updateOpportunity(\n string $crmId,\n array $properties,\n array $associations,\n ?int $accountUserId = null\n ): Opportunity {\n $accountId = $this->resolveAccountId($associations);\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n $stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;\n\n $data = $this->buildOpportunityData(\n $properties,\n $accountId,\n $businessProcess,\n $stage,\n $accountUserId\n );\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->updateOpportunityAssociations($opportunity, $associations);\n\n return $opportunity;\n }\n\n private function resolveAccountId(array $associations): ?int\n {\n if (! empty($associations['account_id'])) {\n return $associations['account_id'];\n }\n\n if (empty($associations)) {\n return null;\n }\n\n // Fallback: use first company as account (currently SDK returns one company)\n foreach ($associations['companies'] as $accountId) {\n return $accountId;\n }\n\n return null;\n }\n\n private function buildOpportunityData(\n array $properties,\n ?int $accountId,\n ?BusinessProcess $businessProcess,\n ?Stage $stage,\n ?int $accountUserId = null\n ): array {\n $ownerId = null;\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->getCachedOwnerProfile((string) $ownerId);\n }\n\n $userId = $profile?->getUserId() ?? $accountUserId;\n\n $name = 'Unknown';\n if (isset($properties['dealname'])) {\n $name = mb_strimwidth($properties['dealname'], 0, 128);\n }\n\n $amount = $this->resolveAmount($properties);\n $currency = $properties['deal_currency_code'] ?? null;\n\n $closeDate = null;\n if (! empty($properties['closedate'])) {\n $closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');\n }\n\n $remotelyCreatedAt = null;\n if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {\n $date = $this->parseCleanDatetime($properties['createdate']);\n $remotelyCreatedAt = $date?->format('Y-m-d H:i:s');\n }\n\n $closedStages = $this->getClosedDealStages();\n $isWon = in_array($properties['dealstage'], $closedStages['won']);\n $isLost = in_array($properties['dealstage'], $closedStages['lost']);\n\n $data = [\n 'team_id' => $this->team->getId(),\n 'user_id' => $userId,\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'value' => ! empty($amount) ? $amount : null,\n 'currency_code' => CurrencyFormatter::formatCode($currency),\n 'close_date' => $closeDate,\n 'is_closed' => $isWon || $isLost,\n 'is_won' => $isWon,\n 'remotely_created_at' => $remotelyCreatedAt,\n 'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),\n 'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),\n ];\n\n if ($accountId) {\n $data['account_id'] = $accountId;\n }\n\n if ($stage) {\n $data['stage_id'] = $stage->id;\n }\n\n if ($businessProcess) {\n $recordType = $this->getCachedBusinessProcessRecordType($businessProcess);\n if ($recordType) {\n $data['record_type_id'] = $recordType->id;\n }\n }\n\n return $data;\n }\n\n private function getCachedOwnerProfile(string $ownerId): ?Profile\n {\n $cacheKey = $this->config->getId() . ':' . $ownerId;\n if (array_key_exists($cacheKey, $this->cachedOwnerProfiles)) {\n return $this->cachedOwnerProfiles[$cacheKey];\n }\n\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, $ownerId);\n $this->cachedOwnerProfiles[$cacheKey] = $profile;\n\n return $profile;\n }\n\n private function getCachedBusinessProcessRecordType(BusinessProcess $businessProcess): mixed\n {\n $cacheKey = $this->config->getId() . ':' . $businessProcess->getId();\n if (array_key_exists($cacheKey, $this->cachedRecordTypes)) {\n return $this->cachedRecordTypes[$cacheKey];\n }\n\n $recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);\n $this->cachedRecordTypes[$cacheKey] = $recordType;\n\n return $recordType;\n }\n\n private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess\n {\n if ($pipelineId === null) {\n return null;\n }\n\n $cacheKey = $this->getBusinessProcessCacheKey($pipelineId);\n if (isset($this->cachedBusinessProcesses[$cacheKey])) {\n return $this->cachedBusinessProcesses[$cacheKey];\n }\n\n $businessProcess = $this->getBusinessProcess($pipelineId);\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->importStages();\n $this->cachedClosedDealStages = null;\n $businessProcess = $this->getBusinessProcess($pipelineId);\n }\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->logger->info(\n '[HubSpot] Deal is not attached to a pipeline',\n [\n 'pipeline' => $pipelineId]\n );\n }\n\n $this->cachedBusinessProcesses[$cacheKey] = $businessProcess;\n\n return $businessProcess;\n }\n\n private function getBusinessProcess(string $pipelineId): ?BusinessProcess\n {\n return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);\n }\n\n private function getBusinessProcessCacheKey(string $pipelineId): string\n {\n return $this->config->getId() . '_' . $pipelineId;\n }\n\n private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage\n {\n if (empty($stageId)) {\n return null;\n }\n\n $cacheKey = $this->config->getId() . ':' . $businessProcess->getId() . ':' . $stageId;\n if (isset($this->cachedStages[$cacheKey])) {\n return $this->cachedStages[$cacheKey];\n }\n\n $stage = $this->crmEntityRepository->getPipelineStageByConditions(\n $businessProcess,\n [\n 'crm_provider_id' => $stageId,\n 'type' => Stage::TYPE_OPPORTUNITY,\n ]\n );\n\n if ($stage === null) {\n $this->importStages(null, $stageId);\n $this->cachedClosedDealStages = null;\n }\n\n if ($stage === null) {\n $this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);\n }\n\n $this->cachedStages[$cacheKey] = $stage;\n\n return $stage;\n }\n\n private function resolveAmount(array $properties): ?string\n {\n $amount = null;\n if (! empty($properties['amount'])) {\n $amount = str_replace(',', '', $properties['amount']);\n }\n\n if ($this->config->hasDefaultCurrencyFieldSet()) {\n $valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();\n $amount = $properties[$valueFieldName] ?? $amount;\n }\n\n return $amount;\n }\n\n private function parseCleanDatetime(string $datetime): ?Carbon\n {\n // Treat pre-1980 values as invalid\n $minValidDate = Carbon::parse('1980-01-01 00:00:00');\n\n try {\n $date = Carbon::parse($datetime);\n\n if ($minValidDate->gt($date)) {\n return null;\n }\n\n return $date;\n } catch (Exception) {\n return null; // On parse error, treat as null\n }\n }\n\n private function resolveDealProbability(?string $stageProbability): int\n {\n if ($stageProbability === null) {\n return 0;\n }\n\n $probability = (float) $stageProbability;\n\n return $probability > 1 ? 0 : (int) ($probability * 100);\n }\n\n private function resolveForecastCategory(?string $forecastCategory): string\n {\n if (! $forecastCategory) {\n return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;\n }\n\n $forecastCategory = str_replace('_', ' ', $forecastCategory);\n\n return ucwords(strtolower($forecastCategory));\n }\n\n private function importExternalFieldData(array $properties, int $opportunityId): void\n {\n $this->importOpportunityCrmFieldData(\n $properties,\n $this->getCachedOpportunitySyncableFields(),\n $opportunityId\n );\n }\n\n private function getCachedOpportunitySyncableFields(): array\n {\n $cacheKey = (string) $this->config->getId();\n if (! isset($this->cachedOpportunitySyncableFields[$cacheKey])) {\n $this->cachedOpportunitySyncableFields[$cacheKey] = $this->getOpportunitySyncableFields();\n }\n\n return $this->cachedOpportunitySyncableFields[$cacheKey];\n }\n\n private function importOpportunityContacts(Opportunity $opportunity, array $associations): void\n {\n // Handle empty or missing contact associations\n if (empty($associations)) {\n // Remove all existing contact associations if none provided\n $this->removeAllOpportunityContacts($opportunity);\n\n return;\n }\n\n // Use differential sync approach for better performance and accuracy\n $this->syncOpportunityContactsDifferential($opportunity, $associations);\n }\n\n /**\n * Sync opportunity contacts using differential approach\n * This compares current vs new associations and only makes necessary changes\n */\n private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void\n {\n $currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);\n $contactAssociationIds = array_keys($contactAssociations);\n\n $contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);\n $contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);\n\n if (empty($contactsToAdd) && empty($contactsToRemove)) {\n return;\n }\n\n $this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);\n\n $this->removeContactAssociations($opportunity, $contactsToRemove);\n $this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);\n }\n\n private function getCurrentContactCrmIds(Opportunity $opportunity): array\n {\n return $opportunity->contacts()\n ->pluck('contacts.crm_provider_id')\n ->toArray();\n }\n\n private function logContactAssociationChanges(\n Opportunity $opportunity,\n array $currentContactCrmIds,\n array $contactAssociations,\n array $contactsToAdd,\n array $contactsToRemove\n ): void {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [\n 'opportunity_id' => $opportunity->getId(),\n 'current_contacts' => $currentContactCrmIds,\n 'new_contacts' => $contactAssociations,\n 'contacts_to_add' => $contactsToAdd,\n 'contacts_to_remove' => $contactsToRemove,\n ]);\n }\n\n private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void\n {\n if (empty($contactsToRemove)) {\n return;\n }\n\n $contactsToDetach = $opportunity->contacts()\n ->whereIn('contacts.crm_provider_id', $contactsToRemove)\n ->pluck('contacts.id')\n ->toArray();\n\n if (! empty($contactsToDetach)) {\n $opportunity->contacts()->detach($contactsToDetach);\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_contact_crm_ids' => $contactsToRemove,\n 'removed_contact_count' => count($contactsToDetach),\n ]);\n }\n }\n\n private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void\n {\n if (empty($contactsToAdd)) {\n return;\n }\n\n $contactsAdded = [];\n foreach ($contactsToAdd as $crmId) {\n $id = $contactAssociations[$crmId];\n\n if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {\n $contactsAdded[] = $crmId;\n }\n }\n\n $this->logAddedContacts($opportunity, $contactsAdded);\n }\n\n private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool\n {\n try {\n return $this->performContactAttachment($opportunity, $id, $crmId);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [\n 'opportunity_id' => $opportunity->getId(),\n 'contact_crm_id' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n private function performContactAttachment(Opportunity $opportunity, int $contactId, string $crmId): bool\n {\n try {\n $opportunity->contacts()->attach($contactId, [\n 'crm_provider_id' => $crmId,\n ]);\n\n return true;\n } catch (\\Illuminate\\Database\\QueryException $e) {\n if (str_contains($e->getMessage(), 'Duplicate entry')) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [\n 'contact_id' => $contactId,\n 'contact_crm_id' => $crmId,\n 'opportunity_id' => $opportunity->getId(),\n ]);\n\n return false;\n }\n\n throw $e;\n }\n }\n\n private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void\n {\n if (! empty($contactsAdded)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'added_contact_crm_ids' => $contactsAdded,\n 'added_contacts_count' => count($contactsAdded),\n ]);\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\CollectionResponseAssociatedId;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Models\\Account;\nuse Exception;\nuse Jiminny\\Component\\DealInsights\\Forecast\\Forecast;\nuse Jiminny\\Jobs\\Crm\\MatchActivitiesToNewOpportunity;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Crm\\BusinessProcess;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Models\\Crm\\Profile;\nuse Jiminny\\Models\\Opportunity;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DealFieldsService;\nuse Jiminny\\Services\\Crm\\Hubspot\\OpportunitySyncStrategy\\HubspotSingleSyncStrategy;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Services\\Crm\\OpportunitySyncStrategyResolver;\nuse Jiminny\\Utils\\CurrencyFormatter;\n\n/**\n * Optimized sync methods for better performance\n * These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains\n */\ntrait OpportunitySyncTrait\n{\n private const int BATCH_SIZE = 100;\n private const int BATCH_PROCESS_SIZE = 800;\n\n protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;\n protected CrmEntityRepository $crmEntityRepository;\n protected DealFieldsService $dealFieldsService;\n\n private ?array $cachedClosedDealStages = null;\n private array $cachedBusinessProcesses = [];\n private array $cachedStages = [];\n /** @var array<string, array<string>> keyed by config id */\n private array $cachedOpportunitySyncableFields = [];\n /** @var array<string, mixed> keyed by configId:ownerId */\n private array $cachedOwnerProfiles = [];\n /** @var array<string, mixed> keyed by configId:businessProcessId */\n private array $cachedRecordTypes = [];\n\n public function syncOpportunities(array $parameters, ?string $strategy = null): int\n {\n $startTime = microtime(true);\n $strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);\n $parameters['config'] = $this->config;\n $syncCount = 0;\n $reportedTotal = 0;\n $lastSyncedId = [];\n $strategyNames = [];\n\n try {\n foreach ($strategies as $strategyName => $syncStrategy) {\n $strategyNames[] = $strategyName;\n $this->logger->info(\n '[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' . $strategyName,\n ['team' => $this->team->getId()]\n );\n\n $total = 0;\n $lastId = null;\n $buffer = [];\n\n // HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies\n foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {\n $buffer[] = $hsOpportunity;\n\n // process every 800 rows (fits < 1 000 association limit)\n if (\\count($buffer) >= self::BATCH_PROCESS_SIZE) {\n $syncCount += $this->processOpportunityBatch($buffer);\n $buffer = [];\n }\n }\n\n // leftovers\n if ($buffer) {\n $syncCount += $this->processOpportunityBatch($buffer);\n }\n\n $reportedTotal += $total;\n $lastSyncedId = $lastId;\n }\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException | CrmException $e) {\n $this->handleSyncException($e, $parameters);\n }\n\n $durationMs = round((microtime(true) - $startTime) * 1000, 2);\n $this->logger->info(\n '[HubSpot] Synced opportunities',\n [\n 'team' => $this->team->getId(),\n 'strategies' => implode(',', $strategyNames),\n 'sync_count' => $syncCount,\n 'total' => $reportedTotal,\n 'last_synced_id' => $lastSyncedId,\n 'duration_ms' => $durationMs,\n ]\n );\n\n return $reportedTotal;\n }\n\n private function handleSyncException(\\Throwable $e, array $parameters): void\n {\n if (($parameters['since'] ?? null) instanceof Carbon) {\n $parameters['since'] = $parameters['since']->toDateTimeString();\n }\n $parameters['config'] = $this->config->getId();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [\n 'teamId' => $this->team->getUuid(),\n 'parameters' => $parameters,\n 'reason' => $e->getMessage(),\n ]);\n }\n\n /**\n * @inheritdoc\n */\n public function syncOpportunity(string $crmId): ?Opportunity\n {\n $strategy = $this->opportunitySyncStrategyResolver->resolve(\n $this->config,\n OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,\n );\n\n $parameters = [\n 'config' => $this->config,\n 'crm_id' => $crmId,\n ];\n\n try {\n if (! $strategy instanceof HubspotSingleSyncStrategy) {\n throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');\n }\n\n $hsOpportunity = $strategy->fetchOpportunity($parameters);\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);\n\n return $this->importOrUpdateOpportunity($hsOpportunity);\n }\n\n /**\n * Process webhook-collected opportunity batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportOpportunityBatch jobs for batch processing.\n *\n * @return int Number of opportunity IDs dispatched to jobs\n */\n public function batchSyncOpportunities(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,\n $configId\n );\n }\n\n /**\n * Import a batch of opportunities by their CRM IDs.\n * Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().\n *\n * @param array<string> $crmIds HubSpot deal CRM IDs\n *\n * @return array{success: array, failed_ids: array, errors?: array<string, string>}\n */\n public function importOpportunityBatchByIds(array $crmIds): array\n {\n $fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);\n\n $allDeals = [];\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n $deals = $this->client->getOpportunitiesByIds($chunk, $fields);\n foreach ($deals as $deal) {\n $allDeals[] = $deal;\n }\n }\n\n // IDs not returned by HubSpot are likely deleted or inaccessible deals.\n // These are not failures — retrying won't bring them back.\n $fetchedIds = array_map('strval', array_column($allDeals, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allDeals),\n ]);\n }\n\n if (empty($allDeals)) {\n return ['success' => [], 'failed_ids' => []];\n }\n\n return $this->importOpportunityBatch($allDeals);\n }\n\n private function getClosedDealStages(): array\n {\n if ($this->cachedClosedDealStages !== null) {\n return $this->cachedClosedDealStages;\n }\n\n $stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);\n $data = [\n 'lost' => [],\n 'won' => [],\n ];\n\n foreach ($stages as $stage) {\n if ($stage->probability == 0.00) {\n $data['lost'][] = $stage->crm_provider_id;\n }\n if ($stage->probability == 100.00) {\n $data['won'][] = $stage->crm_provider_id;\n }\n }\n\n $this->cachedClosedDealStages = $data;\n\n return $data;\n }\n\n /**\n * Import deals into the database with pre-fetched associations.\n *\n * API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT\n * caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()\n * where Laravel retries the whole job with backoff. After all retries exhausted,\n * failed() requeues all IDs to Redis.\n *\n * The per-deal loop catches exceptions individually. A deal can end up in three states:\n * - success: imported/updated successfully\n * - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)\n * These are permanent issues — retrying won't fix them.\n * - skipped (null): missing dependencies (no account, unknown pipeline/stage).\n * This is acceptable — the deal cannot be imported until those exist.\n */\n private function importOpportunityBatch(array $deals): array\n {\n $syncedOpportunities = [\n 'success' => [],\n 'failed_ids' => [],\n ];\n $dealIds = array_column($deals, 'id');\n $batchStart = microtime(true);\n $slowDeals = [];\n\n // Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the\n // queue job retries the whole batch and eventually requeues all deal IDs back to Redis.\n try {\n $companyAssocStart = microtime(true);\n $companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');\n $companyAssocMs = (int) round((microtime(true) - $companyAssocStart) * 1000);\n\n $contactAssocStart = microtime(true);\n $contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');\n $contactAssocMs = (int) round((microtime(true) - $contactAssocStart) * 1000);\n\n $prepareStart = microtime(true);\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n $prepareTimings = [];\n $associationsData = $this->prepareAssociatedEntities(\n $companyAssociations,\n $contactAssociations,\n $prepareTimings\n );\n $prepareMs = (int) round((microtime(true) - $prepareStart) * 1000);\n\n $missingCompanies = count(array_diff(\n $allCompanyIds,\n array_keys($associationsData['company_id_mappings'] ?? [])\n ));\n $missingContacts = count(array_diff(\n $allContactIds,\n array_keys($associationsData['contact_id_mappings'] ?? [])\n ));\n\n $existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(\n $this->config,\n array_map('strval', $dealIds)\n );\n $existingCrmIdSet = array_flip($existingCrmIds);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [\n 'teamId' => $this->team->getId(),\n 'dealCount' => count($dealIds),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n $loopStart = microtime(true);\n foreach ($deals as $deal) {\n $dealStart = microtime(true);\n\n try {\n $deal['associations'] = $this->prepareAssociationsForOpportunity(\n $deal['id'],\n $companyAssociations,\n $contactAssociations,\n $associationsData\n );\n\n $syncedOpportunity = $this->importOrUpdateOpportunity(\n $deal,\n isset($existingCrmIdSet[(string) $deal['id']])\n );\n if ($syncedOpportunity) {\n $syncedOpportunities['success'][] = $syncedOpportunity;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $deal['id'],\n 'error' => $e->getMessage(),\n ]);\n $syncedOpportunities['failed_ids'][] = $deal['id'];\n $syncedOpportunities['errors'][$deal['id']] = $e->getMessage();\n }\n\n $dealMs = (int) round((microtime(true) - $dealStart) * 1000);\n if ($dealMs > 1000) {\n $slowDeals[] = ['crmId' => $deal['id'], 'ms' => $dealMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importOpportunityBatch timing', [\n 'teamId' => $this->team->getId(),\n 'deal_count' => count($deals),\n 'total_ms' => $totalMs,\n 'company_assoc_api_ms' => $companyAssocMs,\n 'contact_assoc_api_ms' => $contactAssocMs,\n 'prepare_entities_ms' => $prepareMs,\n 'prepare_accounts_ms' => $prepareTimings['accounts_ms'],\n 'prepare_contacts_ms' => $prepareTimings['contacts_ms'],\n 'total_companies' => count($allCompanyIds),\n 'missing_companies' => $missingCompanies,\n 'total_contacts' => count($allContactIds),\n 'missing_contacts' => $missingContacts,\n 'deals_loop_ms' => $loopMs,\n 'avg_deal_ms' => ! empty($deals) ? (int) round($loopMs / count($deals)) : 0,\n 'slow_deals_count' => count($slowDeals),\n 'slow_deals' => array_slice($slowDeals, 0, 10),\n ]);\n\n return $syncedOpportunities;\n }\n\n /**\n * Prepare associated entities for opportunities with optimized batch processing\n * Returns structured data with CRM ID to DB ID mappings for each opportunity\n */\n private function prepareAssociatedEntities(\n array $companyAssociations,\n array $contactAssociations,\n array &$timings = []\n ): array {\n // Step 1: Collect all unique company and contact IDs from associations\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n\n // Step 2: Batch sync missing entities and get CRM ID to DB ID mappings\n $companyIdMappings = [];\n $contactIdMappings = [];\n $accountsMs = 0;\n $contactsMs = 0;\n\n if (! empty($allCompanyIds)) {\n $start = microtime(true);\n $companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);\n $accountsMs = (int) round((microtime(true) - $start) * 1000);\n }\n\n if (! empty($allContactIds)) {\n $start = microtime(true);\n $contactIdMappings = $this->prepareAssociatedContacts($allContactIds);\n $contactsMs = (int) round((microtime(true) - $start) * 1000);\n }\n\n $timings = [\n 'accounts_ms' => $accountsMs,\n 'contacts_ms' => $contactsMs,\n ];\n\n return [\n 'company_id_mappings' => $companyIdMappings,\n 'contact_id_mappings' => $contactIdMappings,\n ];\n }\n\n /**\n * Flatten association data to get unique IDs\n */\n private function flattenAssociationIds(array $associations): array\n {\n $ids = [];\n foreach ($associations as $dealAssociations) {\n if (is_array($dealAssociations)) {\n foreach ($dealAssociations as $id) {\n $ids[$id] = true;\n }\n }\n }\n\n return array_keys($ids);\n }\n\n /**\n * Batch sync missing accounts\n */\n private function prepareAssociatedAccounts(array $companyIds): array\n {\n // Find which accounts already exist (lean covering-index lookup)\n $existingAccountsData = $this->crmEntityRepository\n ->getExistingAccountIdsMap($this->config, $companyIds);\n\n $missingCompanyIds = array_diff($companyIds, array_keys($existingAccountsData));\n\n if (empty($missingCompanyIds)) {\n return $existingAccountsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [\n 'teamId' => $this->team->getUuid(),\n 'total_companies' => count($companyIds),\n 'existing_companies' => count($existingAccountsData),\n 'missing_companies' => count($missingCompanyIds),\n ]);\n\n // we already have limit on opportunity ids count\n // Initialize variable before try block\n $syncedAccountsData = [];\n\n try {\n $syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [\n 'size' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedAccountsData = [];\n }\n\n return $existingAccountsData + $syncedAccountsData;\n }\n\n /**\n * Prepare associated contacts - find existing and sync missing ones\n * Returns mapping of CRM ID to DB ID\n */\n private function prepareAssociatedContacts(array $contactIds): array\n {\n // Find which contacts already exist (lean covering-index lookup)\n $existingContactsData = $this->crmEntityRepository\n ->getExistingContactIdsMap($this->config, $contactIds);\n\n $missingContactIds = array_diff($contactIds, array_keys($existingContactsData));\n\n if (empty($missingContactIds)) {\n return $existingContactsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [\n 'teamId' => $this->team->getUuid(),\n 'total_contacts' => count($contactIds),\n 'existing_contacts' => count($existingContactsData),\n 'missing_contacts' => count($missingContactIds),\n ]);\n\n // Sync missing contacts using batch API\n try {\n $syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [\n 'size' => count($missingContactIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedContactsData = [];\n }\n\n return $existingContactsData + $syncedContactsData;\n }\n\n private function batchSyncCrmObjects(string $objectType, array $crmIds): array\n {\n $syncObjects = [];\n $crmObjectIds = array_values($crmIds);\n\n foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {\n try {\n $objects = $objectType === 'companies' ?\n $this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :\n $this->client->getContactsByIds($chunk, $this->getContactFields());\n\n foreach ($objects as $objectId => $objectData) {\n $this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [\n 'requested_count' => count($chunk),\n 'synced_count' => count($objects),\n ]);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [\n 'ids' => $chunk,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncObjects;\n }\n\n private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void\n {\n try {\n $object = $objectType === 'companies' ?\n $this->importAccount($objectData) :\n $this->importContact($objectData);\n\n if ($object) {\n $syncObjects[$object->getCrmProviderId()] = $object->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [\n 'id' => $objectId,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n /**\n * Prepare associations for a single opportunity\n *\n * The return value is an array with the following structure:\n * [\n * 'companies' => [\n * $companyCrmId => $companyId,\n * ...\n * ],\n * 'contacts' => [\n * $contactCrmId => $contactId,\n * ...\n * ],\n * 'account_id' => $accountId,\n * ]\n */\n private function prepareAssociationsForOpportunity(\n string $oppCrmId,\n array $companyAssociations,\n array $contactAssociations,\n array $associationsData\n ): array {\n $associations = [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n\n $oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];\n foreach ($oppCompanyIds as $companyCrmId) {\n if (isset($associationsData['company_id_mappings'][$companyCrmId])) {\n $associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];\n\n // Set primary account (first company becomes primary account)\n if ($associations['account_id'] === null) {\n $associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];\n }\n }\n }\n\n $oppContactIds = $contactAssociations[$oppCrmId] ?? [];\n foreach ($oppContactIds as $contactCrmId) {\n if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {\n $associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];\n }\n }\n\n return $associations;\n }\n\n /**\n * Update only associations for an opportunity\n */\n private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void\n {\n // Update contact associations\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n // Update company (account) associations\n $this->updateOpportunityAccount($opportunity, $associations['account_id']);\n }\n\n /**\n * Remove all contact associations from an opportunity\n */\n private function removeAllOpportunityContacts(Opportunity $opportunity): void\n {\n $currentCount = (int) $opportunity->contacts()->count();\n\n if ($currentCount > 0) {\n $opportunity->contacts()->detach();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_count' => $currentCount,\n ]);\n }\n }\n\n private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void\n {\n if ($accountId === null) {\n // No account ID provided - keep current account\n return;\n }\n\n $currentAccountId = $opportunity->getAccountId();\n\n // Only update if account has changed\n if ($currentAccountId !== $accountId) {\n $opportunity->account_id = $accountId;\n $opportunity->save();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [\n 'opportunity_id' => $opportunity->getId(),\n 'old_account_id' => $currentAccountId,\n 'new_account_id' => $accountId,\n ]);\n }\n }\n\n /**\n * Find existing opportunities by external IDs (OPTIMIZED VERSION)\n * Uses batch query for better performance\n */\n private function findExistingOpportunities(array $crmIds): Collection\n {\n return $this->crmEntityRepository\n ->findOpportunitiesByExternalIds($this->config, $crmIds);\n }\n\n private function processOpportunityBatch(array $opportunities): int\n {\n $syncedOpportunities = $this->importOpportunityBatch($opportunities);\n\n return count($syncedOpportunities['success'] ?? []);\n }\n\n /**\n * Convert single deal associations from HubSpot format to internal format\n * Handles both HubSpot SDK objects and array formats\n *\n * @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed\n *\n * @return array Processed associations with DB IDs\n */\n private function convertDealAssociations(array $opportunityAssociations): array\n {\n $associations = $this->initializeAssociationsStructure();\n\n if (empty($opportunityAssociations)) {\n return $associations;\n }\n\n $associationIds = $this->extractAssociationIds($opportunityAssociations);\n\n $this->processCompanyAssociations($associationIds, $associations);\n $this->processContactAssociations($associationIds, $associations);\n\n return $associations;\n }\n\n private function initializeAssociationsStructure(): array\n {\n return [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n }\n\n private function extractAssociationIds(array $opportunityAssociations): array\n {\n $associationIds = [];\n\n foreach ($opportunityAssociations as $type => $associationData) {\n if (! empty($associationData)) {\n $associationIds[$type] = $this->convertSingleDealAssociations($associationData);\n }\n }\n\n return $associationIds;\n }\n\n private function processCompanyAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['companies'])) {\n return;\n }\n\n $companyId = $associationIds['companies'][0];\n $account = $this->findOrSyncAccount($companyId);\n\n if ($account instanceof Account) {\n $associations['companies'][$companyId] = $account->getId();\n $associations['account_id'] = $account->getId();\n }\n }\n\n private function processContactAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['contacts'])) {\n return;\n }\n\n foreach ($associationIds['contacts'] as $contactId) {\n $contact = $this->findOrSyncContact($contactId);\n\n if ($contact instanceof Contact) {\n $associations['contacts'][$contactId] = $contact->getId();\n }\n }\n }\n\n private function findOrSyncAccount(string $companyId): ?Account\n {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);\n\n if (! $account instanceof Account) {\n $account = $this->syncAccount($companyId);\n }\n\n return $account;\n }\n\n private function findOrSyncContact(string $contactId): ?Contact\n {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);\n\n if (! $contact instanceof Contact) {\n $contact = $this->syncContact($contactId);\n }\n\n return $contact;\n }\n\n private function convertSingleDealAssociations($opportunityAssociations = null): array\n {\n $associationData = [];\n\n if ($opportunityAssociations === null) {\n return $associationData;\n }\n\n // Handle array input (from extractAssociationIds)\n if (is_array($opportunityAssociations)) {\n return $opportunityAssociations;\n }\n\n // Handle CollectionResponseAssociatedId object\n if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {\n foreach ($opportunityAssociations->getResults() as $association) {\n $associationData[] = $association->getId();\n }\n }\n\n return $associationData;\n }\n\n private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity\n {\n if (empty($crmData['properties'])) {\n return null;\n }\n\n $properties = $crmData['properties'];\n\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = (string) $properties['hubspot_owner_id'];\n $profile = $this->getCachedOwnerProfile($ownerId);\n }\n\n $associations = $crmData['associations'] ?? [];\n $accountId = $this->resolveAccountId($associations);\n\n // Only fetch account if we need it for user_id fallback\n $accountUserId = null;\n if ($profile?->getUserId() === null && $accountId !== null) {\n $accountUserId = $this->crmEntityRepository\n ->findAccountByConfigurationAndId(\n $this->config,\n $accountId\n )?->getUserId();\n }\n\n $crmId = (string) $crmData['id'];\n\n if (($profile?->getUserId() === null) && ($accountUserId === null)) {\n $this->logger->error(\n '[HubSpot] Skip import, no user_id found',\n [\n 'id' => $crmId,\n ]\n );\n\n return null;\n }\n\n $opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(\n $this->config,\n $crmId\n );\n\n if ($opportunityExists) {\n return $this->updateOpportunity(\n $crmId,\n $properties,\n $associations,\n $accountUserId\n );\n }\n\n return $this->createOpportunity(\n $crmId,\n $properties,\n $associations,\n $accountUserId,\n );\n }\n\n /**\n * Create new opportunity\n */\n private function createOpportunity(\n string $crmId,\n array $properties,\n array $associations,\n ?int $accountUserId = null\n ): ?Opportunity {\n $accountId = $this->resolveAccountId($associations);\n if (! $accountId) {\n return null;\n }\n\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n if (! $businessProcess) {\n return null;\n }\n\n $stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);\n if (! $stage) {\n return null;\n }\n\n $data = $this->buildOpportunityData(\n $properties,\n $accountId,\n $businessProcess,\n $stage,\n $accountUserId\n );\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n if ($opportunity->wasRecentlyCreated) {\n MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());\n }\n\n return $opportunity;\n }\n\n /**\n * Update existing opportunity\n */\n private function updateOpportunity(\n string $crmId,\n array $properties,\n array $associations,\n ?int $accountUserId = null\n ): Opportunity {\n $accountId = $this->resolveAccountId($associations);\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n $stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;\n\n $data = $this->buildOpportunityData(\n $properties,\n $accountId,\n $businessProcess,\n $stage,\n $accountUserId\n );\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->updateOpportunityAssociations($opportunity, $associations);\n\n return $opportunity;\n }\n\n private function resolveAccountId(array $associations): ?int\n {\n if (! empty($associations['account_id'])) {\n return $associations['account_id'];\n }\n\n if (empty($associations)) {\n return null;\n }\n\n // Fallback: use first company as account (currently SDK returns one company)\n foreach ($associations['companies'] as $accountId) {\n return $accountId;\n }\n\n return null;\n }\n\n private function buildOpportunityData(\n array $properties,\n ?int $accountId,\n ?BusinessProcess $businessProcess,\n ?Stage $stage,\n ?int $accountUserId = null\n ): array {\n $ownerId = null;\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->getCachedOwnerProfile((string) $ownerId);\n }\n\n $userId = $profile?->getUserId() ?? $accountUserId;\n\n $name = 'Unknown';\n if (isset($properties['dealname'])) {\n $name = mb_strimwidth($properties['dealname'], 0, 128);\n }\n\n $amount = $this->resolveAmount($properties);\n $currency = $properties['deal_currency_code'] ?? null;\n\n $closeDate = null;\n if (! empty($properties['closedate'])) {\n $closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');\n }\n\n $remotelyCreatedAt = null;\n if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {\n $date = $this->parseCleanDatetime($properties['createdate']);\n $remotelyCreatedAt = $date?->format('Y-m-d H:i:s');\n }\n\n $closedStages = $this->getClosedDealStages();\n $isWon = in_array($properties['dealstage'], $closedStages['won']);\n $isLost = in_array($properties['dealstage'], $closedStages['lost']);\n\n $data = [\n 'team_id' => $this->team->getId(),\n 'user_id' => $userId,\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'value' => ! empty($amount) ? $amount : null,\n 'currency_code' => CurrencyFormatter::formatCode($currency),\n 'close_date' => $closeDate,\n 'is_closed' => $isWon || $isLost,\n 'is_won' => $isWon,\n 'remotely_created_at' => $remotelyCreatedAt,\n 'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),\n 'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),\n ];\n\n if ($accountId) {\n $data['account_id'] = $accountId;\n }\n\n if ($stage) {\n $data['stage_id'] = $stage->id;\n }\n\n if ($businessProcess) {\n $recordType = $this->getCachedBusinessProcessRecordType($businessProcess);\n if ($recordType) {\n $data['record_type_id'] = $recordType->id;\n }\n }\n\n return $data;\n }\n\n private function getCachedOwnerProfile(string $ownerId): ?Profile\n {\n $cacheKey = $this->config->getId() . ':' . $ownerId;\n if (array_key_exists($cacheKey, $this->cachedOwnerProfiles)) {\n return $this->cachedOwnerProfiles[$cacheKey];\n }\n\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, $ownerId);\n $this->cachedOwnerProfiles[$cacheKey] = $profile;\n\n return $profile;\n }\n\n private function getCachedBusinessProcessRecordType(BusinessProcess $businessProcess): mixed\n {\n $cacheKey = $this->config->getId() . ':' . $businessProcess->getId();\n if (array_key_exists($cacheKey, $this->cachedRecordTypes)) {\n return $this->cachedRecordTypes[$cacheKey];\n }\n\n $recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);\n $this->cachedRecordTypes[$cacheKey] = $recordType;\n\n return $recordType;\n }\n\n private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess\n {\n if ($pipelineId === null) {\n return null;\n }\n\n $cacheKey = $this->getBusinessProcessCacheKey($pipelineId);\n if (isset($this->cachedBusinessProcesses[$cacheKey])) {\n return $this->cachedBusinessProcesses[$cacheKey];\n }\n\n $businessProcess = $this->getBusinessProcess($pipelineId);\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->importStages();\n $this->cachedClosedDealStages = null;\n $businessProcess = $this->getBusinessProcess($pipelineId);\n }\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->logger->info(\n '[HubSpot] Deal is not attached to a pipeline',\n [\n 'pipeline' => $pipelineId]\n );\n }\n\n $this->cachedBusinessProcesses[$cacheKey] = $businessProcess;\n\n return $businessProcess;\n }\n\n private function getBusinessProcess(string $pipelineId): ?BusinessProcess\n {\n return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);\n }\n\n private function getBusinessProcessCacheKey(string $pipelineId): string\n {\n return $this->config->getId() . '_' . $pipelineId;\n }\n\n private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage\n {\n if (empty($stageId)) {\n return null;\n }\n\n $cacheKey = $this->config->getId() . ':' . $businessProcess->getId() . ':' . $stageId;\n if (isset($this->cachedStages[$cacheKey])) {\n return $this->cachedStages[$cacheKey];\n }\n\n $stage = $this->crmEntityRepository->getPipelineStageByConditions(\n $businessProcess,\n [\n 'crm_provider_id' => $stageId,\n 'type' => Stage::TYPE_OPPORTUNITY,\n ]\n );\n\n if ($stage === null) {\n $this->importStages(null, $stageId);\n $this->cachedClosedDealStages = null;\n }\n\n if ($stage === null) {\n $this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);\n }\n\n $this->cachedStages[$cacheKey] = $stage;\n\n return $stage;\n }\n\n private function resolveAmount(array $properties): ?string\n {\n $amount = null;\n if (! empty($properties['amount'])) {\n $amount = str_replace(',', '', $properties['amount']);\n }\n\n if ($this->config->hasDefaultCurrencyFieldSet()) {\n $valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();\n $amount = $properties[$valueFieldName] ?? $amount;\n }\n\n return $amount;\n }\n\n private function parseCleanDatetime(string $datetime): ?Carbon\n {\n // Treat pre-1980 values as invalid\n $minValidDate = Carbon::parse('1980-01-01 00:00:00');\n\n try {\n $date = Carbon::parse($datetime);\n\n if ($minValidDate->gt($date)) {\n return null;\n }\n\n return $date;\n } catch (Exception) {\n return null; // On parse error, treat as null\n }\n }\n\n private function resolveDealProbability(?string $stageProbability): int\n {\n if ($stageProbability === null) {\n return 0;\n }\n\n $probability = (float) $stageProbability;\n\n return $probability > 1 ? 0 : (int) ($probability * 100);\n }\n\n private function resolveForecastCategory(?string $forecastCategory): string\n {\n if (! $forecastCategory) {\n return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;\n }\n\n $forecastCategory = str_replace('_', ' ', $forecastCategory);\n\n return ucwords(strtolower($forecastCategory));\n }\n\n private function importExternalFieldData(array $properties, int $opportunityId): void\n {\n $this->importOpportunityCrmFieldData(\n $properties,\n $this->getCachedOpportunitySyncableFields(),\n $opportunityId\n );\n }\n\n private function getCachedOpportunitySyncableFields(): array\n {\n $cacheKey = (string) $this->config->getId();\n if (! isset($this->cachedOpportunitySyncableFields[$cacheKey])) {\n $this->cachedOpportunitySyncableFields[$cacheKey] = $this->getOpportunitySyncableFields();\n }\n\n return $this->cachedOpportunitySyncableFields[$cacheKey];\n }\n\n private function importOpportunityContacts(Opportunity $opportunity, array $associations): void\n {\n // Handle empty or missing contact associations\n if (empty($associations)) {\n // Remove all existing contact associations if none provided\n $this->removeAllOpportunityContacts($opportunity);\n\n return;\n }\n\n // Use differential sync approach for better performance and accuracy\n $this->syncOpportunityContactsDifferential($opportunity, $associations);\n }\n\n /**\n * Sync opportunity contacts using differential approach\n * This compares current vs new associations and only makes necessary changes\n */\n private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void\n {\n $currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);\n $contactAssociationIds = array_keys($contactAssociations);\n\n $contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);\n $contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);\n\n if (empty($contactsToAdd) && empty($contactsToRemove)) {\n return;\n }\n\n $this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);\n\n $this->removeContactAssociations($opportunity, $contactsToRemove);\n $this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);\n }\n\n private function getCurrentContactCrmIds(Opportunity $opportunity): array\n {\n return $opportunity->contacts()\n ->pluck('contacts.crm_provider_id')\n ->toArray();\n }\n\n private function logContactAssociationChanges(\n Opportunity $opportunity,\n array $currentContactCrmIds,\n array $contactAssociations,\n array $contactsToAdd,\n array $contactsToRemove\n ): void {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [\n 'opportunity_id' => $opportunity->getId(),\n 'current_contacts' => $currentContactCrmIds,\n 'new_contacts' => $contactAssociations,\n 'contacts_to_add' => $contactsToAdd,\n 'contacts_to_remove' => $contactsToRemove,\n ]);\n }\n\n private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void\n {\n if (empty($contactsToRemove)) {\n return;\n }\n\n $contactsToDetach = $opportunity->contacts()\n ->whereIn('contacts.crm_provider_id', $contactsToRemove)\n ->pluck('contacts.id')\n ->toArray();\n\n if (! empty($contactsToDetach)) {\n $opportunity->contacts()->detach($contactsToDetach);\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_contact_crm_ids' => $contactsToRemove,\n 'removed_contact_count' => count($contactsToDetach),\n ]);\n }\n }\n\n private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void\n {\n if (empty($contactsToAdd)) {\n return;\n }\n\n $contactsAdded = [];\n foreach ($contactsToAdd as $crmId) {\n $id = $contactAssociations[$crmId];\n\n if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {\n $contactsAdded[] = $crmId;\n }\n }\n\n $this->logAddedContacts($opportunity, $contactsAdded);\n }\n\n private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool\n {\n try {\n return $this->performContactAttachment($opportunity, $id, $crmId);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [\n 'opportunity_id' => $opportunity->getId(),\n 'contact_crm_id' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n private function performContactAttachment(Opportunity $opportunity, int $contactId, string $crmId): bool\n {\n try {\n $opportunity->contacts()->attach($contactId, [\n 'crm_provider_id' => $crmId,\n ]);\n\n return true;\n } catch (\\Illuminate\\Database\\QueryException $e) {\n if (str_contains($e->getMessage(), 'Duplicate entry')) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [\n 'contact_id' => $contactId,\n 'contact_crm_id' => $crmId,\n 'opportunity_id' => $opportunity->getId(),\n ]);\n\n return false;\n }\n\n throw $e;\n }\n }\n\n private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void\n {\n if (! empty($contactsAdded)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'added_contact_crm_ids' => $contactsAdded,\n 'added_contacts_count' => count($contactsAdded),\n ]);\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"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,"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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-9095576242384424549
|
-8466282600624451230
|
typing_pause
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12121 on JY-20963-fix-im Project: faVsco.js, menu
#12121 on JY-20963-fix-import-on-deleted-entity, menu
Start Listening for PHP Debug Connections
ServiceTest
Run 'ServiceTest'
Debug 'ServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
cachedClosedDealStages
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
2/6
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Sync Changes
Hide This Notification
Code changed:
Hide
75
2
22
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use HubSpot\Client\Crm\Deals\Model\CollectionResponseAssociatedId;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Models\Account;
use Exception;
use Jiminny\Component\DealInsights\Forecast\Forecast;
use Jiminny\Jobs\Crm\MatchActivitiesToNewOpportunity;
use Jiminny\Models\Contact;
use Jiminny\Models\Crm\BusinessProcess;
use Jiminny\Exceptions\CrmException;
use Jiminny\Models\Crm\Profile;
use Jiminny\Models\Opportunity;
use Illuminate\Support\Collection;
use Jiminny\Models\Stage;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Crm\Hubspot\DealFieldsService;
use Jiminny\Services\Crm\Hubspot\OpportunitySyncStrategy\HubspotSingleSyncStrategy;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Services\Crm\OpportunitySyncStrategyResolver;
use Jiminny\Utils\CurrencyFormatter;
/**
* Optimized sync methods for better performance
* These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains
*/
trait OpportunitySyncTrait
{
private const int BATCH_SIZE = 100;
private const int BATCH_PROCESS_SIZE = 800;
protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;
protected CrmEntityRepository $crmEntityRepository;
protected DealFieldsService $dealFieldsService;
private ?array $cachedClosedDealStages = null;
private array $cachedBusinessProcesses = [];
private array $cachedStages = [];
/** @var array<string, array<string>> keyed by config id */
private array $cachedOpportunitySyncableFields = [];
/** @var array<string, mixed> keyed by configId:ownerId */
private array $cachedOwnerProfiles = [];
/** @var array<string, mixed> keyed by configId:businessProcessId */
private array $cachedRecordTypes = [];
public function syncOpportunities(array $parameters, ?string $strategy = null): int
{
$startTime = microtime(true);
$strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);
$parameters['config'] = $this->config;
$syncCount = 0;
$reportedTotal = 0;
$lastSyncedId = [];
$strategyNames = [];
try {
foreach ($strategies as $strategyName => $syncStrategy) {
$strategyNames[] = $strategyName;
$this->logger->info(
'[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' . $strategyName,
['team' => $this->team->getId()]
);
$total = 0;
$lastId = null;
$buffer = [];
// HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies
foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {
$buffer[] = $hsOpportunity;
// process every 800 rows (fits < 1 000 association limit)
if (\count($buffer) >= self::BATCH_PROCESS_SIZE) {
$syncCount += $this->processOpportunityBatch($buffer);
$buffer = [];
}
}
// leftovers
if ($buffer) {
$syncCount += $this->processOpportunityBatch($buffer);
}
$reportedTotal += $total;
$lastSyncedId = $lastId;
}
} catch (\HubSpot\Client\Crm\Deals\ApiException | CrmException $e) {
$this->handleSyncException($e, $parameters);
}
$durationMs = round((microtime(true) - $startTime) * 1000, 2);
$this->logger->info(
'[HubSpot] Synced opportunities',
[
'team' => $this->team->getId(),
'strategies' => implode(',', $strategyNames),
'sync_count' => $syncCount,
'total' => $reportedTotal,
'last_synced_id' => $lastSyncedId,
'duration_ms' => $durationMs,
]
);
return $reportedTotal;
}
private function handleSyncException(\Throwable $e, array $parameters): void
{
if (($parameters['since'] ?? null) instanceof Carbon) {
$parameters['since'] = $parameters['since']->toDateTimeString();
}
$parameters['config'] = $this->config->getId();
$this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [
'teamId' => $this->team->getUuid(),
'parameters' => $parameters,
'reason' => $e->getMessage(),
]);
}
/**
* @inheritdoc
*/
public function syncOpportunity(string $crmId): ?Opportunity
{
$strategy = $this->opportunitySyncStrategyResolver->resolve(
$this->config,
OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,
);
$parameters = [
'config' => $this->config,
'crm_id' => $crmId,
];
try {
if (! $strategy instanceof HubspotSingleSyncStrategy) {
throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');
}
$hsOpportunity = $strategy->fetchOpportunity($parameters);
} catch (\HubSpot\Client\Crm\Deals\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
$hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);
return $this->importOrUpdateOpportunity($hsOpportunity);
}
/**
* Process webhook-collected opportunity batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportOpportunityBatch jobs for batch processing.
*
* @return int Number of opportunity IDs dispatched to jobs
*/
public function batchSyncOpportunities(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,
$configId
);
}
/**
* Import a batch of opportunities by their CRM IDs.
* Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().
*
* @param array<string> $crmIds HubSpot deal CRM IDs
*
* @return array{success: array, failed_ids: array, errors?: array<string, string>}
*/
public function importOpportunityBatchByIds(array $crmIds): array
{
$fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);
$allDeals = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
$deals = $this->client->getOpportunitiesByIds($chunk, $fields);
foreach ($deals as $deal) {
$allDeals[] = $deal;
}
}
// IDs not returned by HubSpot are likely deleted or inaccessible deals.
// These are not failures — retrying won't bring them back.
$fetchedIds = array_map('strval', array_column($allDeals, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allDeals),
]);
}
if (empty($allDeals)) {
return ['success' => [], 'failed_ids' => []];
}
return $this->importOpportunityBatch($allDeals);
}
private function getClosedDealStages(): array
{
if ($this->cachedClosedDealStages !== null) {
return $this->cachedClosedDealStages;
}
$stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);
$data = [
'lost' => [],
'won' => [],
];
foreach ($stages as $stage) {
if ($stage->probability == 0.00) {
$data['lost'][] = $stage->crm_provider_id;
}
if ($stage->probability == 100.00) {
$data['won'][] = $stage->crm_provider_id;
}
}
$this->cachedClosedDealStages = $data;
return $data;
}
/**
* Import deals into the database with pre-fetched associations.
*
* API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT
* caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()
* where Laravel retries the whole job with backoff. After all retries exhausted,
* failed() requeues all IDs to Redis.
*
* The per-deal loop catches exceptions individually. A deal can end up in three states:
* - success: imported/updated successfully
* - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)
* These are permanent issues — retrying won't fix them.
* - skipped (null): missing dependencies (no account, unknown pipeline/stage).
* This is acceptable — the deal cannot be imported until those exist.
*/
private function importOpportunityBatch(array $deals): array
{
$syncedOpportunities = [
'success' => [],
'failed_ids' => [],
];
$dealIds = array_column($deals, 'id');
$batchStart = microtime(true);
$slowDeals = [];
// Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the
// queue job retries the whole batch and eventually requeues all deal IDs back to Redis.
try {
$companyAssocStart = microtime(true);
$companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');
$companyAssocMs = (int) round((microtime(true) - $companyAssocStart) * 1000);
$contactAssocStart = microtime(true);
$contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');
$contactAssocMs = (int) round((microtime(true) - $contactAssocStart) * 1000);
$prepareStart = microtime(true);
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
$prepareTimings = [];
$associationsData = $this->prepareAssociatedEntities(
$companyAssociations,
$contactAssociations,
$prepareTimings
);
$prepareMs = (int) round((microtime(true) - $prepareStart) * 1000);
$missingCompanies = count(array_diff(
$allCompanyIds,
array_keys($associationsData['company_id_mappings'] ?? [])
));
$missingContacts = count(array_diff(
$allContactIds,
array_keys($associationsData['contact_id_mappings'] ?? [])
));
$existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(
$this->config,
array_map('strval', $dealIds)
);
$existingCrmIdSet = array_flip($existingCrmIds);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [
'teamId' => $this->team->getId(),
'dealCount' => count($dealIds),
'error' => $e->getMessage(),
]);
throw $e;
}
$loopStart = microtime(true);
foreach ($deals as $deal) {
$dealStart = microtime(true);
try {
$deal['associations'] = $this->prepareAssociationsForOpportunity(
$deal['id'],
$companyAssociations,
$contactAssociations,
$associationsData
);
$syncedOpportunity = $this->importOrUpdateOpportunity(
$deal,
isset($existingCrmIdSet[(string) $deal['id']])
);
if ($syncedOpportunity) {
$syncedOpportunities['success'][] = $syncedOpportunity;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [
'teamId' => $this->team->getId(),
'crmId' => $deal['id'],
'error' => $e->getMessage(),
]);
$syncedOpportunities['failed_ids'][] = $deal['id'];
$syncedOpportunities['errors'][$deal['id']] = $e->getMessage();
}
$dealMs = (int) round((microtime(true) - $dealStart) * 1000);
if ($dealMs > 1000) {
$slowDeals[] = ['crmId' => $deal['id'], 'ms' => $dealMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importOpportunityBatch timing', [
'teamId' => $this->team->getId(),
'deal_count' => count($deals),
'total_ms' => $totalMs,
'company_assoc_api_ms' => $companyAssocMs,
'contact_assoc_api_ms' => $contactAssocMs,
'prepare_entities_ms' => $prepareMs,
'prepare_accounts_ms' => $prepareTimings['accounts_ms'],
'prepare_contacts_ms' => $prepareTimings['contacts_ms'],
'total_companies' => count($allCompanyIds),
'missing_companies' => $missingCompanies,
'total_contacts' => count($allContactIds),
'missing_contacts' => $missingContacts,
'deals_loop_ms' => $loopMs,
'avg_deal_ms' => ! empty($deals) ? (int) round($loopMs / count($deals)) : 0,
'slow_deals_count' => count($slowDeals),
'slow_deals' => array_slice($slowDeals, 0, 10),
]);
return $syncedOpportunities;
}
/**
* Prepare associated entities for opportunities with optimized batch processing
* Returns structured data with CRM ID to DB ID mappings for each opportunity
*/
private function prepareAssociatedEntities(
array $companyAssociations,
array $contactAssociations,
array &$timings = []
): array {
// Step 1: Collect all unique company and contact IDs from associations
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
// Step 2: Batch sync missing entities and get CRM ID to DB ID mappings
$companyIdMappings = [];
$contactIdMappings = [];
$accountsMs = 0;
$contactsMs = 0;
if (! empty($allCompanyIds)) {
$start = microtime(true);
$companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);
$accountsMs = (int) round((microtime(true) - $start) * 1000);
}
if (! empty($allContactIds)) {
$start = microtime(true);
$contactIdMappings = $this->prepareAssociatedContacts($allContactIds);
$contactsMs = (int) round((microtime(true) - $start) * 1000);
}
$timings = [
'accounts_ms' => $accountsMs,
'contacts_ms' => $contactsMs,
];
return [
'company_id_mappings' => $companyIdMappings,
'contact_id_mappings' => $contactIdMappings,
];
}
/**
* Flatten association data to get unique IDs
*/
private function flattenAssociationIds(array $associations): array
{
$ids = [];
foreach ($associations as $dealAssociations) {
if (is_array($dealAssociations)) {
foreach ($dealAssociations as $id) {
$ids[$id] = true;
}
}
}
return array_keys($ids);
}
/**
* Batch sync missing accounts
*/
private function prepareAssociatedAccounts(array $companyIds): array
{
// Find which accounts already exist (lean covering-index lookup)
$existingAccountsData = $this->crmEntityRepository
->getExistingAccountIdsMap($this->config, $companyIds);
$missingCompanyIds = array_diff($companyIds, array_keys($existingAccountsData));
if (empty($missingCompanyIds)) {
return $existingAccountsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [
'teamId' => $this->team->getUuid(),
'total_companies' => count($companyIds),
'existing_companies' => count($existingAccountsData),
'missing_companies' => count($missingCompanyIds),
]);
// we already have limit on opportunity ids count
// Initialize variable before try block
$syncedAccountsData = [];
try {
$syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [
'size' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
$syncedAccountsData = [];
}
return $existingAccountsData + $syncedAccountsData;
}
/**
* Prepare associated contacts - find existing and sync missing ones
* Returns mapping of CRM ID to DB ID
*/
private function prepareAssociatedContacts(array $contactIds): array
{
// Find which contacts already exist (lean covering-index lookup)
$existingContactsData = $this->crmEntityRepository
->getExistingContactIdsMap($this->config, $contactIds);
$missingContactIds = array_diff($contactIds, array_keys($existingContactsData));
if (empty($missingContactIds)) {
return $existingContactsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [
'teamId' => $this->team->getUuid(),
'total_contacts' => count($contactIds),
'existing_contacts' => count($existingContactsData),
'missing_contacts' => count($missingContactIds),
]);
// Sync missing contacts using batch API
try {
$syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [
'size' => count($missingContactIds),
'error' => $e->getMessage(),
]);
$syncedContactsData = [];
}
return $existingContactsData + $syncedContactsData;
}
private function batchSyncCrmObjects(string $objectType, array $crmIds): array
{
$syncObjects = [];
$crmObjectIds = array_values($crmIds);
foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {
try {
$objects = $objectType === 'companies' ?
$this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :
$this->client->getContactsByIds($chunk, $this->getContactFields());
foreach ($objects as $objectId => $objectData) {
$this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [
'requested_count' => count($chunk),
'synced_count' => count($objects),
]);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [
'ids' => $chunk,
'error' => $e->getMessage(),
]);
}
}
return $syncObjects;
}
private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void
{
try {
$object = $objectType === 'companies' ?
$this->importAccount($objectData) :
$this->importContact($objectData);
if ($object) {
$syncObjects[$object->getCrmProviderId()] = $object->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [
'id' => $objectId,
'error' => $e->getMessage(),
]);
}
}
/**
* Prepare associations for a single opportunity
*
* The return value is an array with the following structure:
* [
* 'companies' => [
* $companyCrmId => $companyId,
* ...
* ],
* 'contacts' => [
* $contactCrmId => $contactId,
* ...
* ],
* 'account_id' => $accountId,
* ]
*/
private function prepareAssociationsForOpportunity(
string $oppCrmId,
array $companyAssociations,
array $contactAssociations,
array $associationsData
): array {
$associations = [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
$oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];
foreach ($oppCompanyIds as $companyCrmId) {
if (isset($associationsData['company_id_mappings'][$companyCrmId])) {
$associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];
// Set primary account (first company becomes primary account)
if ($associations['account_id'] === null) {
$associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];
}
}
}
$oppContactIds = $contactAssociations[$oppCrmId] ?? [];
foreach ($oppContactIds as $contactCrmId) {
if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {
$associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];
}
}
return $associations;
}
/**
* Update only associations for an opportunity
*/
private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void
{
// Update contact associations
$this->importOpportunityContacts($opportunity, $associations['contacts']);
// Update company (account) associations
$this->updateOpportunityAccount($opportunity, $associations['account_id']);
}
/**
* Remove all contact associations from an opportunity
*/
private function removeAllOpportunityContacts(Opportunity $opportunity): void
{
$currentCount = (int) $opportunity->contacts()->count();
if ($currentCount > 0) {
$opportunity->contacts()->detach();
$this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_count' => $currentCount,
]);
}
}
private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void
{
if ($accountId === null) {
// No account ID provided - keep current account
return;
}
$currentAccountId = $opportunity->getAccountId();
// Only update if account has changed
if ($currentAccountId !== $accountId) {
$opportunity->account_id = $accountId;
$opportunity->save();
$this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [
'opportunity_id' => $opportunity->getId(),
'old_account_id' => $currentAccountId,
'new_account_id' => $accountId,
]);
}
}
/**
* Find existing opportunities by external IDs (OPTIMIZED VERSION)
* Uses batch query for better performance
*/
private function findExistingOpportunities(array $crmIds): Collection
{
return $this->crmEntityRepository
->findOpportunitiesByExternalIds($this->config, $crmIds);
}
private function processOpportunityBatch(array $opportunities): int
{
$syncedOpportunities = $this->importOpportunityBatch($opportunities);
return count($syncedOpportunities['success'] ?? []);
}
/**
* Convert single deal associations from HubSpot format to internal format
* Handles both HubSpot SDK objects and array formats
*
* @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed
*
* @return array Processed associations with DB IDs
*/
private function convertDealAssociations(array $opportunityAssociations): array
{
$associations = $this->initializeAssociationsStructure();
if (empty($opportunityAssociations)) {
return $associations;
}
$associationIds = $this->extractAssociationIds($opportunityAssociations);
$this->processCompanyAssociations($associationIds, $associations);
$this->processContactAssociations($associationIds, $associations);
return $associations;
}
private function initializeAssociationsStructure(): array
{
return [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
}
private function extractAssociationIds(array $opportunityAssociations): array
{
$associationIds = [];
foreach ($opportunityAssociations as $type => $associationData) {
if (! empty($associationData)) {
$associationIds[$type] = $this->convertSingleDealAssociations($associationData);
}
}
return $associationIds;
}
private function processCompanyAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['companies'])) {
return;
}
$companyId = $associationIds['companies'][0];
$account = $this->findOrSyncAccount($companyId);
if ($account instanceof Account) {
$associations['companies'][$companyId] = $account->getId();
$associations['account_id'] = $account->getId();
}
}
private function processContactAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['contacts'])) {
return;
}
foreach ($associationIds['contacts'] as $contactId) {
$contact = $this->findOrSyncContact($contactId);
if ($contact instanceof Contact) {
$associations['contacts'][$contactId] = $contact->getId();
}
}
}
private function findOrSyncAccount(string $companyId): ?Account
{
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);
if (! $account instanceof Account) {
$account = $this->syncAccount($companyId);
}
return $account;
}
private function findOrSyncContact(string $contactId): ?Contact
{
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);
if (! $contact instanceof Contact) {
$contact = $this->syncContact($contactId);
}
return $contact;
}
private function convertSingleDealAssociations($opportunityAssociations = null): array
{
$associationData = [];
if ($opportunityAssociations === null) {
return $associationData;
}
// Handle array input (from extractAssociationIds)
if (is_array($opportunityAssociations)) {
return $opportunityAssociations;
}
// Handle CollectionResponseAssociatedId object
if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {
foreach ($opportunityAssociations->getResults() as $association) {
$associationData[] = $association->getId();
}
}
return $associationData;
}
private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity
{
if (empty($crmData['properties'])) {
return null;
}
$properties = $crmData['properties'];
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = (string) $properties['hubspot_owner_id'];
$profile = $this->getCachedOwnerProfile($ownerId);
}
$associations = $crmData['associations'] ?? [];
$accountId = $this->resolveAccountId($associations);
// Only fetch account if we need it for user_id fallback
$accountUserId = null;
if ($profile?->getUserId() === null && $accountId !== null) {
$accountUserId = $this->crmEntityRepository
->findAccountByConfigurationAndId(
$this->config,
$accountId
)?->getUserId();
}
$crmId = (string) $crmData['id'];
if (($profile?->getUserId() === null) && ($accountUserId === null)) {
$this->logger->error(
'[HubSpot] Skip import, no user_id found',
[
'id' => $crmId,
]
);
return null;
}
$opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(
$this->config,
$crmId
);
if ($opportunityExists) {
return $this->updateOpportunity(
$crmId,
$properties,
$associations,
$accountUserId
);
}
return $this->createOpportunity(
$crmId,
$properties,
$associations,
$accountUserId,
);
}
/**
* Create new opportunity
*/
private function createOpportunity(
string $crmId,
array $properties,
array $associations,
?int $accountUserId = null
): ?Opportunity {
$accountId = $this->resolveAccountId($associations);
if (! $accountId) {
return null;
}
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
if (! $businessProcess) {
return null;
}
$stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);
if (! $stage) {
return null;
}
$data = $this->buildOpportunityData(
$properties,
$accountId,
$businessProcess,
$stage,
$accountUserId
);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->importOpportunityContacts($opportunity, $associations['contacts']);
if ($opportunity->wasRecentlyCreated) {
MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());
}
return $opportunity;
}
/**
* Update existing opportunity
*/
private function updateOpportunity(
string $crmId,
array $properties,
array $associations,
?int $accountUserId = null
): Opportunity {
$accountId = $this->resolveAccountId($associations);
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
$stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;
$data = $this->buildOpportunityData(
$properties,
$accountId,
$businessProcess,
$stage,
$accountUserId
);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->updateOpportunityAssociations($opportunity, $associations);
return $opportunity;
}
private function resolveAccountId(array $associations): ?int
{
if (! empty($associations['account_id'])) {
return $associations['account_id'];
}
if (empty($associations)) {
return null;
}
// Fallback: use first company as account (currently SDK returns one company)
foreach ($associations['companies'] as $accountId) {
return $accountId;
}
return null;
}
private function buildOpportunityData(
array $properties,
?int $accountId,
?BusinessProcess $businessProcess,
?Stage $stage,
?int $accountUserId = null
): array {
$ownerId = null;
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->getCachedOwnerProfile((string) $ownerId);
}
$userId = $profile?->getUserId() ?? $accountUserId;
$name = 'Unknown';
if (isset($properties['dealname'])) {
$name = mb_strimwidth($properties['dealname'], 0, 128);
}
$amount = $this->resolveAmount($properties);
$currency = $properties['deal_currency_code'] ?? null;
$closeDate = null;
if (! empty($properties['closedate'])) {
$closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');
}
$remotelyCreatedAt = null;
if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {
$date = $this->parseCleanDatetime($properties['createdate']);
$remotelyCreatedAt = $date?->format('Y-m-d H:i:s');
}
$closedStages = $this->getClosedDealStages();
$isWon = in_array($properties['dealstage'], $closedStages['won']);
$isLost = in_array($properties['dealstage'], $closedStages['lost']);
$data = [
'team_id' => $this->team->getId(),
'user_id' => $userId,
'owner_id' => $ownerId,
'name' => $name,
'value' => ! empty($amount) ? $amount : null,
'currency_code' => CurrencyFormatter::formatCode($currency),
'close_date' => $closeDate,
'is_closed' => $isWon || $isLost,
'is_won' => $isWon,
'remotely_created_at' => $remotelyCreatedAt,
'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),
'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),
];
if ($accountId) {
$data['account_id'] = $accountId;
}
if ($stage) {
$data['stage_id'] = $stage->id;
}
if ($businessProcess) {
$recordType = $this->getCachedBusinessProcessRecordType($businessProcess);
if ($recordType) {
$data['record_type_id'] = $recordType->id;
}
}
return $data;
}
private function getCachedOwnerProfile(string $ownerId): ?Profile
{
$cacheKey = $this->config->getId() . ':' . $ownerId;
if (array_key_exists($cacheKey, $this->cachedOwnerProfiles)) {
return $this->cachedOwnerProfiles[$cacheKey];
}
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, $ownerId);
$this->cachedOwnerProfiles[$cacheKey] = $profile;
return $profile;
}
private function getCachedBusinessProcessRecordType(BusinessProcess $businessProcess): mixed
{
$cacheKey = $this->config->getId() . ':' . $businessProcess->getId();
if (array_key_exists($cacheKey, $this->cachedRecordTypes)) {
return $this->cachedRecordTypes[$cacheKey];
}
$recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);
$this->cachedRecordTypes[$cacheKey] = $recordType;
return $recordType;
}
private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess
{
if ($pipelineId === null) {
return null;
}
$cacheKey = $this->getBusinessProcessCacheKey($pipelineId);
if (isset($this->cachedBusinessProcesses[$cacheKey])) {
return $this->cachedBusinessProcesses[$cacheKey];
}
$businessProcess = $this->getBusinessProcess($pipelineId);
if (! $businessProcess instanceof BusinessProcess) {
$this->importStages();
$this->cachedClosedDealStages = null;
$businessProcess = $this->getBusinessProcess($pipelineId);
}
if (! $businessProcess instanceof BusinessProcess) {
$this->logger->info(
'[HubSpot] Deal is not attached to a pipeline',
[
'pipeline' => $pipelineId]
);
}
$this->cachedBusinessProcesses[$cacheKey] = $businessProcess;
return $businessProcess;
}
private function getBusinessProcess(string $pipelineId): ?BusinessProcess
{
return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);
}
private function getBusinessProcessCacheKey(string $pipelineId): string
{
return $this->config->getId() . '_' . $pipelineId;
}
private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage
{
if (empty($stageId)) {
return null;
}
$cacheKey = $this->config->getId() . ':' . $businessProcess->getId() . ':' . $stageId;
if (isset($this->cachedStages[$cacheKey])) {
return $this->cachedStages[$cacheKey];
}
$stage = $this->crmEntityRepository->getPipelineStageByConditions(
$businessProcess,
[
'crm_provider_id' => $stageId,
'type' => Stage::TYPE_OPPORTUNITY,
]
);
if ($stage === null) {
$this->importStages(null, $stageId);
$this->cachedClosedDealStages = null;
}
if ($stage === null) {
$this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);
}
$this->cachedStages[$cacheKey] = $stage;
return $stage;
}
private function resolveAmount(array $properties): ?string
{
$amount = null;
if (! empty($properties['amount'])) {
$amount = str_replace(',', '', $properties['amount']);
}
if ($this->config->hasDefaultCurrencyFieldSet()) {
$valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();
$amount = $properties[$valueFieldName] ?? $amount;
}
return $amount;
}
private function parseCleanDatetime(string $datetime): ?Carbon
{
// Treat pre-1980 values as invalid
$minValidDate = Carbon::parse('1980-01-01 00:00:00');
try {
$date = Carbon::parse($datetime);
if ($minValidDate->gt($date)) {
return null;
}
return $date;
} catch (Exception) {
return null; // On parse error, treat as null
}
}
private function resolveDealProbability(?string $stageProbability): int
{
if ($stageProbability === null) {
return 0;
}
$probability = (float) $stageProbability;
return $probability > 1 ? 0 : (int) ($probability * 100);
}
private function resolveForecastCategory(?string $forecastCategory): string
{
if (! $forecastCategory) {
return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;
}
$forecastCategory = str_replace('_', ' ', $forecastCategory);
return ucwords(strtolower($forecastCategory));
}
private function importExternalFieldData(array $properties, int $opportunityId): void
{
$this->importOpportunityCrmFieldData(
$properties,
$this->getCachedOpportunitySyncableFields(),
$opportunityId
);
}
private function getCachedOpportunitySyncableFields(): array
{
$cacheKey = (string) $this->config->getId();
if (! isset($this->cachedOpportunitySyncableFields[$cacheKey])) {
$this->cachedOpportunitySyncableFields[$cacheKey] = $this->getOpportunitySyncableFields();
}
return $this->cachedOpportunitySyncableFields[$cacheKey];
}
private function importOpportunityContacts(Opportunity $opportunity, array $associations): void
{
// Handle empty or missing contact associations
if (empty($associations)) {
// Remove all existing contact associations if none provided
$this->removeAllOpportunityContacts($opportunity);
return;
}
// Use differential sync approach for better performance and accuracy
$this->syncOpportunityContactsDifferential($opportunity, $associations);
}
/**
* Sync opportunity contacts using differential approach
* This compares current vs new associations and only makes necessary changes
*/
private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void
{
$currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);
$contactAssociationIds = array_keys($contactAssociations);
$contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);
$contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);
if (empty($contactsToAdd) && empty($contactsToRemove)) {
return;
}
$this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);
$this->removeContactAssociations($opportunity, $contactsToRemove);
$this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);
}
private function getCurrentContactCrmIds(Opportunity $opportunity): array
{
return $opportunity->contacts()
->pluck('contacts.crm_provider_id')
->toArray();
}
private function logContactAssociationChanges(
Opportunity $opportunity,
array $currentContactCrmIds,
array $contactAssociations,
array $contactsToAdd,
array $contactsToRemove
): void {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [
'opportunity_id' => $opportunity->getId(),
'current_contacts' => $currentContactCrmIds,
'new_contacts' => $contactAssociations,
'contacts_to_add' => $contactsToAdd,
'contacts_to_remove' => $contactsToRemove,
]);
}
private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void
{
if (empty($contactsToRemove)) {
return;
}
$contactsToDetach = $opportunity->contacts()
->whereIn('contacts.crm_provider_id', $contactsToRemove)
->pluck('contacts.id')
->toArray();
if (! empty($contactsToDetach)) {
$opportunity->contacts()->detach($contactsToDetach);
$this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_contact_crm_ids' => $contactsToRemove,
'removed_contact_count' => count($contactsToDetach),
]);
}
}
private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void
{
if (empty($contactsToAdd)) {
return;
}
$contactsAdded = [];
foreach ($contactsToAdd as $crmId) {
$id = $contactAssociations[$crmId];
if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {
$contactsAdded[] = $crmId;
}
}
$this->logAddedContacts($opportunity, $contactsAdded);
}
private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool
{
try {
return $this->performContactAttachment($opportunity, $id, $crmId);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [
'opportunity_id' => $opportunity->getId(),
'contact_crm_id' => $crmId,
'error' => $e->getMessage(),
]);
return false;
}
}
private function performContactAttachment(Opportunity $opportunity, int $contactId, string $crmId): bool
{
try {
$opportunity->contacts()->attach($contactId, [
'crm_provider_id' => $crmId,
]);
return true;
} catch (\Illuminate\Database\QueryException $e) {
if (str_contains($e->getMessage(), 'Duplicate entry')) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [
'contact_id' => $contactId,
'contact_crm_id' => $crmId,
'opportunity_id' => $opportunity->getId(),
]);
return false;
}
throw $e;
}
}
private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void
{
if (! empty($contactsAdded)) {
$this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [
'opportunity_id' => $opportunity->getId(),
'added_contact_crm_ids' => $contactsAdded,
'added_contacts_count' => count($contactsAdded),
]);
}
}
}
Execute
Explain Plan
Browse Query History...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
63523
|
2234
|
39
|
2026-05-20T12:29:55.526055+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779280195526_m1.jpg...
|
Firefox
|
Photos - Google Photos — Personal
|
1
|
photos.google.com/photo/AF1QipNtcyqXO5Ebi5Lf_5JzgZ photos.google.com/photo/AF1QipNtcyqXO5Ebi5Lf_5JzgZ7XsRUwLWwOFYBctCl2...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Inbox (1) - [EMAIL] - Gmail
Inbox (1) - [EMAIL] - Inbox (1) - [EMAIL] - Gmail
Inbox (1) - [EMAIL] - Gmail
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
(190) TED LASSO BEST SCENES - PART 2. The Most Heartwarming Ted Lasso Show Moments. - YouTube
(190) TED LASSO BEST SCENES - PART 2. The Most Heartwarming Ted Lasso Show Moments. - YouTube
LinkedIn Login, Sign in | LinkedIn
LinkedIn Login, Sign in | LinkedIn
Ambulatory sheet - Paperless-ngx
Ambulatory sheet - Paperless-ngx
New Tab
New Tab
💬1 - Apple MacBook Pro 14" Space Black M5 Max | Laptop.bg - Технологията с теб
💬1 - Apple MacBook Pro 14" Space Black M5 Max | Laptop.bg - Технологията с теб
Apple Mac Studio M4 Max | Desktop.bg - Мощни PC Гейминг конфигурации, Персонални компютри на изплащане за игри и работа
Apple Mac Studio M4 Max | Desktop.bg - Мощни PC Гейминг конфигурации, Персонални компютри на изплащане за игри и работа
Welcome to authentik! - authentik
Welcome to authentik! - authentik
Photos - Google Photos
Photos - Google Photos
Close tab
Завеждане на щета онлайн | Euroins
Завеждане на щета онлайн | Euroins
Google Maps
Google Maps
New Tab
New Tab
MAZANOKE | Online Image Optimizer That Runs Privately in Your Browser
MAZANOKE | Online Image Optimizer That Runs Privately in Your Browser
New Tab
New Tab
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
AI Chat settings
Close
Main menu
Gemini Pro
Gemini
Pro
New chat
Open menu for conversation actions.
Conversation with Gemini
Conversation with Gemini
Show the uploaded image in a Lightbox
Show the uploaded image in a Lightbox
Show the uploaded image in a Lightbox
Show the uploaded image in a Lightbox
Show the uploaded image in a Lightbox
Show the uploaded image in a Lightbox
Show the uploaded image in a Lightbox
Show the uploaded image in a Lightbox
Show the uploaded image in a Lightbox
Show the uploaded image in a Lightbox
Scroll to next files
You said How can I open case and use Kasko
You said
How can I open case and use Kasko
Copy prompt
Gemini said
Gemini said
To open a Casco claim and use your insurance, you must follow the specific procedures outlined in the provided Euroins General Terms. Failing to adhere to the deadlines or procedures can result in a denial of your claim.
Here is the step-by-step process:
1. Immediate Actions at the Scene
1. Immediate Actions at the Scene
Call the Authorities:
Immediately notify the competent state authorities (e.g., call 112 for the Traffic Police or Fire Department) to register the event. You will need an official document from them, such as a Police Protocol (Констативен протокол за ПТП), unless it is a minor scratch where a bilateral protocol is legally permitted.
Limit Damages:
Take all reasonable and necessary measures to limit further damage to the vehicle and prevent additional risks.
2. Notify the Insurer (Strict Deadlines)
2. Notify the Insurer (Strict Deadlines)
You must notify Euroins in writing within specific timeframes. Do not miss these deadlines:
Theft or Robbery:
Within
24 hours
of discovering the theft. You must also immediately inform the police.
All Other Damages (Accidents, Fire, Natural Disasters, Vandalism):
Within
3 working days
of the date you learned about the event.
3. Do Not Repair the Vehicle
3. Do Not Repair the Vehicle
Crucial Rule:
Do not change the condition of the damaged vehicle or begin any repairs until a Euroins representative has officially inspected it. If you repair the car before the inspection, your claim will be denied.
4. File the Claim and Arrange an Inspection
4. File the Claim and Arrange an Inspection
Visit an official Euroins liquidation center to submit a written claim form (заявление за изплащане на обезщетение).
You must provide the damaged vehicle for a physical inspection (оглед). If the vehicle is immobilized, notify Euroins so they can send an inspector to the vehicle's location.
5. Prepare the Required Documents
5. Prepare the Required Documents
When opening your case, you will need to present several original documents. Bring the following:
The Insurance Policy:
Your original Casco contract.
Vehicle Documents:
The vehicle's registration certificate (Талон - Part 1 and Part 2).
Driver's Documents:
The driver's license and the control talon of the person who was driving during the incident.
Official Reports:
The original document issued by the authorities (Police Protocol, breathalyzer/blood test results if applicable, or a note from the meteorological institute for hail/storms).
Technical Inspection:
Proof of a valid periodic technical inspection (ГТП).
Keys and Remotes (For Theft Only):
If your car is stolen, you must hand over all keys, alarm remote controls, and Part 1 of the registration certificate to the insurer.
Good response
Bad response
Share and export
Copy
Show more options
You said I have some parking scratches, Does it appy
You said
I have some parking scratches, Does it appy...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Inbox (1) - kovaliklukas@gmail.com - Gmail","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox (1) - kovaliklukas@gmail.com - Gmail","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"DXP4800PLUS-B5F8","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"DXP4800PLUS-B5F8","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"(190) TED LASSO BEST SCENES - PART 2. The Most Heartwarming Ted Lasso Show Moments. - YouTube","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"(190) TED LASSO BEST SCENES - PART 2. The Most Heartwarming Ted Lasso Show Moments. - YouTube","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"LinkedIn Login, Sign in | LinkedIn","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LinkedIn Login, Sign in | LinkedIn","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Ambulatory sheet - Paperless-ngx","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ambulatory sheet - Paperless-ngx","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"💬1 - Apple MacBook Pro 14\" Space Black M5 Max | Laptop.bg - Технологията с теб","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"💬1 - Apple MacBook Pro 14\" Space Black M5 Max | Laptop.bg - Технологията с теб","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Apple Mac Studio M4 Max | Desktop.bg - Мощни PC Гейминг конфигурации, Персонални компютри на изплащане за игри и работа","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Apple Mac Studio M4 Max | Desktop.bg - Мощни PC Гейминг конфигурации, Персонални компютри на изплащане за игри и работа","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Welcome to authentik! - authentik","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Welcome to authentik! - authentik","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Photos - Google Photos","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Photos - Google Photos","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Завеждане на щета онлайн | Euroins","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Завеждане на щета онлайн | Euroins","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Google Maps","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Google Maps","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"MAZANOKE | Online Image Optimizer That Runs Privately in Your Browser","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"MAZANOKE | Online Image Optimizer That Runs Privately in Your Browser","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bitwarden","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Main menu","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Gemini Pro","depth":14,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Pro","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New chat","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show the uploaded image in a Lightbox","depth":21,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show the uploaded image in a Lightbox","depth":21,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show the uploaded image in a Lightbox","depth":21,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show the uploaded image in a Lightbox","depth":21,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show the uploaded image in a Lightbox","depth":21,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show the uploaded image in a Lightbox","depth":21,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show the uploaded image in a Lightbox","depth":21,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show the uploaded image in a Lightbox","depth":21,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show the uploaded image in a Lightbox","depth":21,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show the uploaded image in a Lightbox","depth":21,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Scroll to next files","depth":19,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said How can I open case and use Kasko","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"How can I open case and use Kasko","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":20,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"To open a Casco claim and use your insurance, you must follow the specific procedures outlined in the provided Euroins General Terms. Failing to adhere to the deadlines or procedures can result in a denial of your claim.","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is the step-by-step process:","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Immediate Actions at the Scene","depth":26,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Immediate Actions at the Scene","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Call the Authorities:","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Immediately notify the competent state authorities (e.g., call 112 for the Traffic Police or Fire Department) to register the event. You will need an official document from them, such as a Police Protocol (Констативен протокол за ПТП), unless it is a minor scratch where a bilateral protocol is legally permitted.","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Limit Damages:","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Take all reasonable and necessary measures to limit further damage to the vehicle and prevent additional risks.","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Notify the Insurer (Strict Deadlines)","depth":26,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Notify the Insurer (Strict Deadlines)","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You must notify Euroins in writing within specific timeframes. Do not miss these deadlines:","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Theft or Robbery:","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Within","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"24 hours","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"of discovering the theft. You must also immediately inform the police.","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"All Other Damages (Accidents, Fire, Natural Disasters, Vandalism):","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Within","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3 working days","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"of the date you learned about the event.","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Do Not Repair the Vehicle","depth":26,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Do Not Repair the Vehicle","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Crucial Rule:","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Do not change the condition of the damaged vehicle or begin any repairs until a Euroins representative has officially inspected it. If you repair the car before the inspection, your claim will be denied.","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. File the Claim and Arrange an Inspection","depth":26,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. File the Claim and Arrange an Inspection","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Visit an official Euroins liquidation center to submit a written claim form (заявление за изплащане на обезщетение).","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You must provide the damaged vehicle for a physical inspection (оглед). If the vehicle is immobilized, notify Euroins so they can send an inspector to the vehicle's location.","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"5. Prepare the Required Documents","depth":26,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. Prepare the Required Documents","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"When opening your case, you will need to present several original documents. Bring the following:","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Insurance Policy:","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Your original Casco contract.","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Vehicle Documents:","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The vehicle's registration certificate (Талон - Part 1 and Part 2).","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Driver's Documents:","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The driver's license and the control talon of the person who was driving during the incident.","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Official Reports:","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The original document issued by the authorities (Police Protocol, breathalyzer/blood test results if applicable, or a note from the meteorological institute for hail/storms).","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Technical Inspection:","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Proof of a valid periodic technical inspection (ГТП).","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Keys and Remotes (For Theft Only):","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If your car is stolen, you must hand over all keys, alarm remote controls, and Part 1 of the registration certificate to the insurer.","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":25,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":25,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share and export","depth":24,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":25,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":24,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said I have some parking scratches, Does it appy","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I have some parking scratches, Does it appy","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-9094954399223285724
|
7011434703810924215
|
click
|
accessibility
|
NULL
|
Inbox (1) - [EMAIL] - Gmail
Inbox (1) - [EMAIL] - Inbox (1) - [EMAIL] - Gmail
Inbox (1) - [EMAIL] - Gmail
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
(190) TED LASSO BEST SCENES - PART 2. The Most Heartwarming Ted Lasso Show Moments. - YouTube
(190) TED LASSO BEST SCENES - PART 2. The Most Heartwarming Ted Lasso Show Moments. - YouTube
LinkedIn Login, Sign in | LinkedIn
LinkedIn Login, Sign in | LinkedIn
Ambulatory sheet - Paperless-ngx
Ambulatory sheet - Paperless-ngx
New Tab
New Tab
💬1 - Apple MacBook Pro 14" Space Black M5 Max | Laptop.bg - Технологията с теб
💬1 - Apple MacBook Pro 14" Space Black M5 Max | Laptop.bg - Технологията с теб
Apple Mac Studio M4 Max | Desktop.bg - Мощни PC Гейминг конфигурации, Персонални компютри на изплащане за игри и работа
Apple Mac Studio M4 Max | Desktop.bg - Мощни PC Гейминг конфигурации, Персонални компютри на изплащане за игри и работа
Welcome to authentik! - authentik
Welcome to authentik! - authentik
Photos - Google Photos
Photos - Google Photos
Close tab
Завеждане на щета онлайн | Euroins
Завеждане на щета онлайн | Euroins
Google Maps
Google Maps
New Tab
New Tab
MAZANOKE | Online Image Optimizer That Runs Privately in Your Browser
MAZANOKE | Online Image Optimizer That Runs Privately in Your Browser
New Tab
New Tab
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
AI Chat settings
Close
Main menu
Gemini Pro
Gemini
Pro
New chat
Open menu for conversation actions.
Conversation with Gemini
Conversation with Gemini
Show the uploaded image in a Lightbox
Show the uploaded image in a Lightbox
Show the uploaded image in a Lightbox
Show the uploaded image in a Lightbox
Show the uploaded image in a Lightbox
Show the uploaded image in a Lightbox
Show the uploaded image in a Lightbox
Show the uploaded image in a Lightbox
Show the uploaded image in a Lightbox
Show the uploaded image in a Lightbox
Scroll to next files
You said How can I open case and use Kasko
You said
How can I open case and use Kasko
Copy prompt
Gemini said
Gemini said
To open a Casco claim and use your insurance, you must follow the specific procedures outlined in the provided Euroins General Terms. Failing to adhere to the deadlines or procedures can result in a denial of your claim.
Here is the step-by-step process:
1. Immediate Actions at the Scene
1. Immediate Actions at the Scene
Call the Authorities:
Immediately notify the competent state authorities (e.g., call 112 for the Traffic Police or Fire Department) to register the event. You will need an official document from them, such as a Police Protocol (Констативен протокол за ПТП), unless it is a minor scratch where a bilateral protocol is legally permitted.
Limit Damages:
Take all reasonable and necessary measures to limit further damage to the vehicle and prevent additional risks.
2. Notify the Insurer (Strict Deadlines)
2. Notify the Insurer (Strict Deadlines)
You must notify Euroins in writing within specific timeframes. Do not miss these deadlines:
Theft or Robbery:
Within
24 hours
of discovering the theft. You must also immediately inform the police.
All Other Damages (Accidents, Fire, Natural Disasters, Vandalism):
Within
3 working days
of the date you learned about the event.
3. Do Not Repair the Vehicle
3. Do Not Repair the Vehicle
Crucial Rule:
Do not change the condition of the damaged vehicle or begin any repairs until a Euroins representative has officially inspected it. If you repair the car before the inspection, your claim will be denied.
4. File the Claim and Arrange an Inspection
4. File the Claim and Arrange an Inspection
Visit an official Euroins liquidation center to submit a written claim form (заявление за изплащане на обезщетение).
You must provide the damaged vehicle for a physical inspection (оглед). If the vehicle is immobilized, notify Euroins so they can send an inspector to the vehicle's location.
5. Prepare the Required Documents
5. Prepare the Required Documents
When opening your case, you will need to present several original documents. Bring the following:
The Insurance Policy:
Your original Casco contract.
Vehicle Documents:
The vehicle's registration certificate (Талон - Part 1 and Part 2).
Driver's Documents:
The driver's license and the control talon of the person who was driving during the incident.
Official Reports:
The original document issued by the authorities (Police Protocol, breathalyzer/blood test results if applicable, or a note from the meteorological institute for hail/storms).
Technical Inspection:
Proof of a valid periodic technical inspection (ГТП).
Keys and Remotes (For Theft Only):
If your car is stolen, you must hand over all keys, alarm remote controls, and Part 1 of the registration certificate to the insurer.
Good response
Bad response
Share and export
Copy
Show more options
You said I have some parking scratches, Does it appy
You said
I have some parking scratches, Does it appy...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
50589
|
1786
|
39
|
2026-05-18T07:41:35.679796+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779090095679_m2.jpg...
|
Firefox
|
Jy 20846 mcp enable the ai to know details about t Jy 20846 mcp enable the ai to know details about the user by nikolaybiaivanov · Pull Request #12075 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12075/changes#diff-1d8 github.com/jiminny/app/pull/12075/changes#diff-1d8bedfdf7947383437d6dca96fd490b0fdc13ebf5930b4bc7de71e62f30797c...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
[JY-20891] Sidekick SMS issue - Jira
[JY-20891] Sidekick SMS issue - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Usage | Windsurf
Usage | Windsurf
[SRD-6853] Moxso - Potential deal stages bug - Jira
[SRD-6853] Moxso - Potential deal stages bug - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
Feed — jiminny — Sentry
Feed — jiminny — Sentry
[JY-20906] Review of Pipedrive SDK - Jira
[JY-20906] Review of Pipedrive SDK - Jira
Pipedrive API Reference and Documentation
Pipedrive API Reference and Documentation
pipedrive/client-php: Pipedrive API client for PHP
pipedrive/client-php: Pipedrive API client for PHP
[jiminny/infrastructure] JY-20623 Add SQS queue for panorama reports (PR #728) - [EMAIL] - Jiminny Mail
[jiminny/infrastructure] JY-20623 Add SQS queue for panorama reports (PR #728) - [EMAIL] - Jiminny Mail
Pull requests · jiminny/app
Pull requests · jiminny/app
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20906] Review of Pipedrive SDK - Jira
[JY-20906] Review of Pipedrive SDK - Jira
Jy 20846 mcp enable the ai to know details about the user by nikolaybiaivanov · Pull Request #12075 · jiminny/app
Jy 20846 mcp enable the ai to know details about the user by nikolaybiaivanov · Pull Request #12075 · jiminny/app
Close tab
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
AI Chat settings
Close
WORK, Google Account: [EMAIL]
Main menu
New chat
Gemini
New Chat
Open menu for conversation actions.
Conversation with Gemini
Conversation with Gemini
Copy prompt
Edit
You said I’m on page “<tabTitle>Jy 20846 mcp enable the ai to know details about t</tabTitle>” with “<selection>@@ -0,0 +1,157 @@1+ $schema->string()35+ ->description(36+'UUID of the authenticated user. Use this as the authoritative identifier when filtering calls or deals by '37+ . 'the current user.'38+ )39+ ->required(),40+'name' => $schema->string()41+ ->description('Full display name of the user, e.g. \'John Doe\'.')42+ ->required(),43+'first_name' => $schema->string()44+ ->description(45+'First name of the user, derived from the full name field. Use for personalised greetings or addressing '46+ . 'the user directly.'47+ )48+ ->required(),49+'email' => $schema->string()50+ ->description('Primary email address of the user.')51+ ->required(),52+'timezone' => $schema->string()53+ ->description(54+'The user\'s timezone as an IANA timezone identifier, e.g. \'America/New_York\'. Use this when displaying '55+ . 'or reasoning about call times in the user\'s local time.'56+ )57+ ->required(),58+'spoken_languages' => $schema->array()59+ ->items($schema->string())60+ ->description(61+'Languages spoken during this user\'s calls, as an array of locale codes, e.g. [\'en_GB\', \'bg_BG\']. '62+ . 'The first entry is the primary/default language — used when language detection is unavailable. '63+ . 'Additional entries are other languages the user handles.'64+ )65+ ->required(),66+'sync_dialer' => $schema->boolean()67+ ->description(68+'True if the user has automatic syncing of softphone and outbound calls to their CRM enabled. When true, '69+ . 'calls made through the Jiminny dialler are automatically logged as CRM activities.'70+ )71+ ->required(),72+'sync_email' => $schema->boolean()73+ ->description(74+'True if the user has email syncing enabled. When true, customer email conversations are captured as '75+ . 'activities.'76+ )77+ ->required(),78+'sync_calendar' => $schema->boolean()79+ ->description(80+'True if the user has calendar syncing enabled. When true, scheduled meetings will be automatically '81+ . 'recorded.'82+ )83+ ->required(),84+'team' => $schema->object([85+'id' => $schema->string()86+ ->description(87+'Team UUID. Use this when filtering calls or deals by the user\'s team in search_calls or '88+ . 'search_deals.'89+ )90+ ->required(),91+'name' => $schema->string()92+ ->description('Team name, e.g. \'Account Executives\', \'Customer Success\'.')93+ ->required(),94+'playbook' => $schema->string()95+ ->description(96+'Name of the active playbook assigned to this team, e.g. \'Sales\', \'Customer Success\'. A playbook '97+ . 'defines the call evaluation criteria used for AI scoring. Null if no playbook is assigned.'98+ )99+ ->nullable()100+ ->required(),101+ ])102+ ->description('The team this user belongs to.')103+ ->required(),104+'job' => $schema->string()105+ ->description(106+'The user\'s job title, e.g. \'Software Engineer\', \'Account Executive\', \'Sales Manager\'. Null if no '107+ . 'job title has been assigned.'108+ )109+ ->nullable()110+ ->required(),111+'roles' => $schema->array()112+ ->items($schema->string())113+ ->description(114+'Roles assigned to the user, e.g. [\'admin\', \'recorder_and_voice\']. Determines what the user can do in '115+ . 'the platform. Common values: \'admin\' (full organisation admin), \'manager\' (team manager with '116+ . 'coaching access), \'recorder_and_voice\' (can record meetings and make calls), \'recorder\' (recording only).'117+ )118+ ->required(),119+ ];120+ }121+122+public function handle(Request $request): ResponseFactory123+ {124+$user = $request->user();125+assert($user instanceof User, 'auth:api + McpTierMiddleware must guarantee an authenticated User before the tool runs');126+127+$team = $user->team;128+$roles = $user->getRoles();129+$jobTitle = $user->job;130+131+$languageDialects = $user->languageDialects()->with('language')->get();132+$spokenLanguages = $languageDialects->isNotEmpty()133+ ? $languageDialects->map(fn ($dialect) => $dialect->getLanguageLocale())->toArray()134+ : [$user->getLanguage() ?? $team->getDefaultLanguage() ?? 'en'];135+136+$payload = [137+'id' => $user->getUuid(),138+'name' => $user->getName(),139+'first_name' => $user->getFirstName(),140+'email' => $user->getEmailAddress(),141+'timezone' => $user->getTimezone()->getName(),142+'spoken_languages' => $spokenLanguages,143+'sync_dialer' => $user->shouldSyncDialer(),144+'sync_email' => $user->isSyncEmailEnabled(),145+'sync_calendar' => $user->isSyncConferenceEnabled(),146+'team' => [147+'id' => $team->getUuid(),148+'name' => $team->getName(),149+'playbook' => $team->getDefaultPlaybook()?->getName(),150+ ],151+'job' => $jobTitle?->getName(),152+'roles' => $roles,153+ ];154+155+return Response::structured($payload);156+ }157+}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20846 mcp enable the ai to know details about t</tabTitle>” with “<selection>@@ -0,0 +1,157 @@1+ $schema->string()35+ ->description(36+'UUID of the authenticated user. Use this as the authoritative identifier when filtering calls or deals by '37+ . 'the current user.'38+ )39+ ->required(),40+'name' => $schema->string()41+ ->description('Full display name of the user, e.g. \'John Doe\'.')42+ ->required(),43+'first_name' => $schema->string()44+ ->description(45+'First name of the user, derived from the full name field. Use for personalised greetings or addressing '46+ . 'the user directly.'47+ )48+ ->required(),49+'email' => $schema->string()50+ ->description('Primary email address of the user.')51+ ->required(),52+'timezone' => $schema->string()53+ ->description(54+'The user\'s timezone as an IANA timezone identifier, e.g. \'America/New_York\'. Use this when displaying '55+ . 'or reasoning about call times in the user\'s local time.'56+ )57+ ->required(),58+'spoken_languages' => $schema->array()59+ ->items($schema->string())60+ ->description(61+'Languages spoken during this user\'s calls, as an array of locale codes, e.g. [\'en_GB\', \'bg_BG\']. '62+ . 'The first entry is the primary/default language — used when language detection is unavailable. '63+ . 'Additional entries are other languages the user handles.'64+ )65+ ->required(),66+'sync_dialer' => $schema->boolean()67+ ->description(68+'True if the user has automatic syncing of softphone and outbound calls to their CRM enabled. When true, '69+ . 'calls made through the Jiminny dialler are automatically logged as CRM activities.'70+ )71+ ->required(),72+'sync_email' => $schema->boolean()73+ ->description(74+'True if the user has email syncing enabled. When true, customer email conversations are captured as '75+ . 'activities.'76+ )77+ ->required(),78+'sync_calendar' => $schema->boolean()79+ ->description(80+'True if the user has calendar syncing enabled. When true, scheduled meetings will be automatically '81+ . 'recorded.'82+ )83+ ->required(),84+'team' => $schema->object([85+'id' => $schema->string()86+ ->description(87+'Team UUID. Use this when filtering calls or deals by the user\'s team in search_calls or '88+ . 'search_deals.'89+ )90+ ->required(),91+'name' => $schema->string()92+ ->description('Team name, e.g. \'Account Executives\', \'Customer Success\'.')93+ ->required(),94+'playbook' => $schema->string()95+ ->description(96+'Name of the active playbook assigned to this team, e.g. \'Sales\', \'Customer Success\'. A playbook '97+ . 'defines the call evaluation criteria used for AI scoring. Null if no playbook is assigned.'98+ )99+ ->nullable()100+ ->required(),101+ ])102+ ->description('The team this user belongs to.')103+ ->required(),104+'job' => $schema->string()105+ ->description(106+'The user\'s job title, e.g. \'Software Engineer\', \'Account Executive\', \'Sales Manager\'. Null if no '107+ . 'job title has been assigned.'108+ )109+ ->nullable()110+ ->required(),111+'roles' => $schema->array()112+ ->items($schema->string())113+ ->description(114+'Roles assigned to the user, e.g. [\'admin\', \'recorder_and_voice\']. Determines what the user can do in '115+ . 'the platform. Common values: \'admin\' (full organisation admin), \'manager\' (team manager with '116+ . 'coaching access), \'recorder_and_voice\' (can record meetings and make calls), \'recorder\' (recording only).'117+ )118+ ->required(),119+ ];120+ }121+122+public function handle(Request $request): ResponseFactory123+ {124+$user = $request->user();125+assert($user instanceof User, 'auth:api + McpTierMiddleware must guarantee an authenticated User before the tool runs');126+127+$team = $user->team;128+$roles = $user->getRoles();129+$jobTitle = $user->job;130+131+$languageDialects = $user->languageDialects()->with('language')->get();132+$spokenLanguages = $languageDialects->isNotEmpty()133+ ? $languageDialects->map(fn ($dialect) => $dialect->getLanguageLocale())->toArray()134+ : [$user->getLanguage() ?? $team->getDefaultLanguage() ?? 'en'];135+136+$payload = [137+'id' => $user->getUuid(),138+'name' => $user->getName(),139+'first_name' => $user->getFirstName(),140+'email' => $user->getEmailAddress(),141+'timezone' => $user->getTimezone()->getName(),142+'spoken_languages' => $spokenLanguages,143+'sync_dialer' => $user->shouldSyncDialer(),144+'sync_email' => $user->isSyncEmailEnabled(),145+'sync_calendar' => $user->isSyncConferenceEnabled(),146+'team' => [147+'id' => $team->getUuid(),148+'name' => $team->getName(),149+'playbook' => $team->getDefaultPlaybook()?->getName(),150+ ],151+'job' => $jobTitle?->getName(),152+'roles' => $roles,153+ ];154+155+return Response::structured($payload);156+ }157+}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Listen
Gemini said
Gemini said
Code Summary: MCP "Get User Details" Tool Implementation
Code Summary: MCP "Get User Details" Tool Implementation
This code snippet defines the schema and execution logic for a Model Context Protocol (MCP) tool that exposes the authenticated user's profile details to an AI assistant.
Schema Definition
Schema Definition
The tool outputs a structured object containing the following required fields to guide the AI's contextual understanding and filtering capabilities:
User Identity:
*
id
: UUID used as the authoritative identifier for filtering calls or deals.
name
: Full display name (e.g., 'John Doe').
first_name
: Extracted first name, explicitly intended for personalized greetings.
email
: Primary email address.
job
: Job title (e.g., 'Account Executive'); nullable.
Preferences & Context:
timezone
: IANA identifier (e.g., 'America/New_York') for reasoning about local call times.
spoken_languages
: An array of locale codes. The first entry serves as the primary/default language.
roles
: Platform permissions determining capabilities (e.g.,
admin
,
manager
,
recorder_and_voice
).
Sync Settings:
Booleans indicating whether the user has automated CRM syncing enabled for:
sync_dialer
(Jiminny softphone calls)
sync_email
(Customer email conversations)
sync_calendar
(Scheduled meetings)
Team Context:
An object containing the team's
id
,
name
, and
playbook...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.15658244,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20891] Sidekick SMS issue - Jira","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20891] Sidekick SMS issue - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.064494684,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Usage | Windsurf","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Usage | Windsurf","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.029920213,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6853] Moxso - Potential deal stages bug - Jira","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6853] Moxso - Potential deal stages bug - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.09158909,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.042719416,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20906] Review of Pipedrive SDK - Jira","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20906] Review of Pipedrive SDK - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.07413564,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipedrive API Reference and Documentation","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipedrive API Reference and Documentation","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.07679521,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"pipedrive/client-php: Pipedrive API client for PHP","depth":4,"bounds":{"left":0.0,"top":0.3790902,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"pipedrive/client-php: Pipedrive API client for PHP","depth":5,"bounds":{"left":0.013297873,"top":0.39026338,"width":0.08543883,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[jiminny/infrastructure] JY-20623 Add SQS queue for panorama reports (PR #728) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.41181165,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[jiminny/infrastructure] JY-20623 Add SQS queue for panorama reports (PR #728) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"bounds":{"left":0.013297873,"top":0.42298484,"width":0.21791889,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pull requests · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.4445331,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.4557063,"width":0.04537899,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":4,"bounds":{"left":0.0,"top":0.4772546,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.4884278,"width":0.15259309,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20906] Review of Pipedrive SDK - Jira","depth":4,"bounds":{"left":0.0,"top":0.509976,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20906] Review of Pipedrive SDK - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.5211492,"width":0.07413564,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20846 mcp enable the ai to know details about the user by nikolaybiaivanov · Pull Request #12075 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.54269755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Jy 20846 mcp enable the ai to know details about the user by nikolaybiaivanov · Pull Request #12075 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.55387074,"width":0.19847074,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.54988027,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.57701516,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"WORK, Google Account: lukas.kovalik@jiminny.com","depth":12,"bounds":{"left":0.3678524,"top":0.103751,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Main menu","depth":12,"bounds":{"left":0.08494016,"top":0.103751,"width":0.013297873,"height":0.031923383},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10454908,"width":0.028590426,"height":0.030327214},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10973663,"width":0.021941489,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":12,"bounds":{"left":0.33992687,"top":0.103751,"width":0.013297873,"height":0.031923383},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":12,"bounds":{"left":0.35322472,"top":0.103751,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14764565,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.15003991,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":21,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20846 mcp enable the ai to know details about t</tabTitle>” with “<selection>@@ -0,0 +1,157 @@1+ $schema->string()35+ ->description(36+'UUID of the authenticated user. Use this as the authoritative identifier when filtering calls or deals by '37+ . 'the current user.'38+ )39+ ->required(),40+'name' => $schema->string()41+ ->description('Full display name of the user, e.g. \\'John Doe\\'.')42+ ->required(),43+'first_name' => $schema->string()44+ ->description(45+'First name of the user, derived from the full name field. Use for personalised greetings or addressing '46+ . 'the user directly.'47+ )48+ ->required(),49+'email' => $schema->string()50+ ->description('Primary email address of the user.')51+ ->required(),52+'timezone' => $schema->string()53+ ->description(54+'The user\\'s timezone as an IANA timezone identifier, e.g. \\'America/New_York\\'. Use this when displaying '55+ . 'or reasoning about call times in the user\\'s local time.'56+ )57+ ->required(),58+'spoken_languages' => $schema->array()59+ ->items($schema->string())60+ ->description(61+'Languages spoken during this user\\'s calls, as an array of locale codes, e.g. [\\'en_GB\\', \\'bg_BG\\']. '62+ . 'The first entry is the primary/default language — used when language detection is unavailable. '63+ . 'Additional entries are other languages the user handles.'64+ )65+ ->required(),66+'sync_dialer' => $schema->boolean()67+ ->description(68+'True if the user has automatic syncing of softphone and outbound calls to their CRM enabled. When true, '69+ . 'calls made through the Jiminny dialler are automatically logged as CRM activities.'70+ )71+ ->required(),72+'sync_email' => $schema->boolean()73+ ->description(74+'True if the user has email syncing enabled. When true, customer email conversations are captured as '75+ . 'activities.'76+ )77+ ->required(),78+'sync_calendar' => $schema->boolean()79+ ->description(80+'True if the user has calendar syncing enabled. When true, scheduled meetings will be automatically '81+ . 'recorded.'82+ )83+ ->required(),84+'team' => $schema->object([85+'id' => $schema->string()86+ ->description(87+'Team UUID. Use this when filtering calls or deals by the user\\'s team in search_calls or '88+ . 'search_deals.'89+ )90+ ->required(),91+'name' => $schema->string()92+ ->description('Team name, e.g. \\'Account Executives\\', \\'Customer Success\\'.')93+ ->required(),94+'playbook' => $schema->string()95+ ->description(96+'Name of the active playbook assigned to this team, e.g. \\'Sales\\', \\'Customer Success\\'. A playbook '97+ . 'defines the call evaluation criteria used for AI scoring. Null if no playbook is assigned.'98+ )99+ ->nullable()100+ ->required(),101+ ])102+ ->description('The team this user belongs to.')103+ ->required(),104+'job' => $schema->string()105+ ->description(106+'The user\\'s job title, e.g. \\'Software Engineer\\', \\'Account Executive\\', \\'Sales Manager\\'. Null if no '107+ . 'job title has been assigned.'108+ )109+ ->nullable()110+ ->required(),111+'roles' => $schema->array()112+ ->items($schema->string())113+ ->description(114+'Roles assigned to the user, e.g. [\\'admin\\', \\'recorder_and_voice\\']. Determines what the user can do in '115+ . 'the platform. Common values: \\'admin\\' (full organisation admin), \\'manager\\' (team manager with '116+ . 'coaching access), \\'recorder_and_voice\\' (can record meetings and make calls), \\'recorder\\' (recording only).'117+ )118+ ->required(),119+ ];120+ }121+122+public function handle(Request $request): ResponseFactory123+ {124+$user = $request->user();125+assert($user instanceof User, 'auth:api + McpTierMiddleware must guarantee an authenticated User before the tool runs');126+127+$team = $user->team;128+$roles = $user->getRoles();129+$jobTitle = $user->job;130+131+$languageDialects = $user->languageDialects()->with('language')->get();132+$spokenLanguages = $languageDialects->isNotEmpty()133+ ? $languageDialects->map(fn ($dialect) => $dialect->getLanguageLocale())->toArray()134+ : [$user->getLanguage() ?? $team->getDefaultLanguage() ?? 'en'];135+136+$payload = [137+'id' => $user->getUuid(),138+'name' => $user->getName(),139+'first_name' => $user->getFirstName(),140+'email' => $user->getEmailAddress(),141+'timezone' => $user->getTimezone()->getName(),142+'spoken_languages' => $spokenLanguages,143+'sync_dialer' => $user->shouldSyncDialer(),144+'sync_email' => $user->isSyncEmailEnabled(),145+'sync_calendar' => $user->isSyncConferenceEnabled(),146+'team' => [147+'id' => $team->getUuid(),148+'name' => $team->getName(),149+'playbook' => $team->getDefaultPlaybook()?->getName(),150+ ],151+'job' => $jobTitle?->getName(),152+'roles' => $roles,153+ ];154+155+return Response::structured($payload);156+ }157+}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20846 mcp enable the ai to know details about t</tabTitle>” with “<selection>@@ -0,0 +1,157 @@1+ $schema->string()35+ ->description(36+'UUID of the authenticated user. Use this as the authoritative identifier when filtering calls or deals by '37+ . 'the current user.'38+ )39+ ->required(),40+'name' => $schema->string()41+ ->description('Full display name of the user, e.g. \\'John Doe\\'.')42+ ->required(),43+'first_name' => $schema->string()44+ ->description(45+'First name of the user, derived from the full name field. Use for personalised greetings or addressing '46+ . 'the user directly.'47+ )48+ ->required(),49+'email' => $schema->string()50+ ->description('Primary email address of the user.')51+ ->required(),52+'timezone' => $schema->string()53+ ->description(54+'The user\\'s timezone as an IANA timezone identifier, e.g. \\'America/New_York\\'. Use this when displaying '55+ . 'or reasoning about call times in the user\\'s local time.'56+ )57+ ->required(),58+'spoken_languages' => $schema->array()59+ ->items($schema->string())60+ ->description(61+'Languages spoken during this user\\'s calls, as an array of locale codes, e.g. [\\'en_GB\\', \\'bg_BG\\']. '62+ . 'The first entry is the primary/default language — used when language detection is unavailable. '63+ . 'Additional entries are other languages the user handles.'64+ )65+ ->required(),66+'sync_dialer' => $schema->boolean()67+ ->description(68+'True if the user has automatic syncing of softphone and outbound calls to their CRM enabled. When true, '69+ . 'calls made through the Jiminny dialler are automatically logged as CRM activities.'70+ )71+ ->required(),72+'sync_email' => $schema->boolean()73+ ->description(74+'True if the user has email syncing enabled. When true, customer email conversations are captured as '75+ . 'activities.'76+ )77+ ->required(),78+'sync_calendar' => $schema->boolean()79+ ->description(80+'True if the user has calendar syncing enabled. When true, scheduled meetings will be automatically '81+ . 'recorded.'82+ )83+ ->required(),84+'team' => $schema->object([85+'id' => $schema->string()86+ ->description(87+'Team UUID. Use this when filtering calls or deals by the user\\'s team in search_calls or '88+ . 'search_deals.'89+ )90+ ->required(),91+'name' => $schema->string()92+ ->description('Team name, e.g. \\'Account Executives\\', \\'Customer Success\\'.')93+ ->required(),94+'playbook' => $schema->string()95+ ->description(96+'Name of the active playbook assigned to this team, e.g. \\'Sales\\', \\'Customer Success\\'. A playbook '97+ . 'defines the call evaluation criteria used for AI scoring. Null if no playbook is assigned.'98+ )99+ ->nullable()100+ ->required(),101+ ])102+ ->description('The team this user belongs to.')103+ ->required(),104+'job' => $schema->string()105+ ->description(106+'The user\\'s job title, e.g. \\'Software Engineer\\', \\'Account Executive\\', \\'Sales Manager\\'. Null if no '107+ . 'job title has been assigned.'108+ )109+ ->nullable()110+ ->required(),111+'roles' => $schema->array()112+ ->items($schema->string())113+ ->description(114+'Roles assigned to the user, e.g. [\\'admin\\', \\'recorder_and_voice\\']. Determines what the user can do in '115+ . 'the platform. Common values: \\'admin\\' (full organisation admin), \\'manager\\' (team manager with '116+ . 'coaching access), \\'recorder_and_voice\\' (can record meetings and make calls), \\'recorder\\' (recording only).'117+ )118+ ->required(),119+ ];120+ }121+122+public function handle(Request $request): ResponseFactory123+ {124+$user = $request->user();125+assert($user instanceof User, 'auth:api + McpTierMiddleware must guarantee an authenticated User before the tool runs');126+127+$team = $user->team;128+$roles = $user->getRoles();129+$jobTitle = $user->job;130+131+$languageDialects = $user->languageDialects()->with('language')->get();132+$spokenLanguages = $languageDialects->isNotEmpty()133+ ? $languageDialects->map(fn ($dialect) => $dialect->getLanguageLocale())->toArray()134+ : [$user->getLanguage() ?? $team->getDefaultLanguage() ?? 'en'];135+136+$payload = [137+'id' => $user->getUuid(),138+'name' => $user->getName(),139+'first_name' => $user->getFirstName(),140+'email' => $user->getEmailAddress(),141+'timezone' => $user->getTimezone()->getName(),142+'spoken_languages' => $spokenLanguages,143+'sync_dialer' => $user->shouldSyncDialer(),144+'sync_email' => $user->isSyncEmailEnabled(),145+'sync_calendar' => $user->isSyncConferenceEnabled(),146+'team' => [147+'id' => $team->getUuid(),148+'name' => $team->getName(),149+'playbook' => $team->getDefaultPlaybook()?->getName(),150+ ],151+'job' => $jobTitle?->getName(),152+'roles' => $roles,153+ ];154+155+return Response::structured($payload);156+ }157+}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Listen","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Code Summary: MCP \"Get User Details\" Tool Implementation","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Code Summary: MCP \"Get User Details\" Tool Implementation","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This code snippet defines the schema and execution logic for a Model Context Protocol (MCP) tool that exposes the authenticated user's profile details to an AI assistant.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Schema Definition","depth":23,"bounds":{"left":0.111369684,"top":0.0,"width":0.24734043,"height":0.01915403},"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Schema Definition","depth":24,"bounds":{"left":0.111369684,"top":0.0,"width":0.047539894,"height":0.016360734},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The tool outputs a structured object containing the following required fields to guide the AI's contextual understanding and filtering capabilities:","depth":24,"bounds":{"left":0.111369684,"top":0.0,"width":0.22423537,"height":0.038707104},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"User Identity:","depth":26,"bounds":{"left":0.12400266,"top":0.03152434,"width":0.03523936,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"*","depth":26,"bounds":{"left":0.15924202,"top":0.03152434,"width":0.0051529254,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"id","depth":27,"bounds":{"left":0.16638963,"top":0.032721467,"width":0.0056515955,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": UUID used as the authoritative identifier for filtering calls or deals.","depth":26,"bounds":{"left":0.1740359,"top":0.03152434,"width":0.16156915,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"name","depth":29,"bounds":{"left":0.13696809,"top":0.06384677,"width":0.011136968,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": Full display name (e.g., 'John Doe').","depth":28,"bounds":{"left":0.15009974,"top":0.06264964,"width":0.087765954,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"first_name","depth":29,"bounds":{"left":0.13696809,"top":0.09497207,"width":0.027925532,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": Extracted first name, explicitly intended for personalized greetings.","depth":28,"bounds":{"left":0.1668883,"top":0.09377494,"width":0.1640625,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"email","depth":29,"bounds":{"left":0.13696809,"top":0.12609737,"width":0.013962766,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": Primary email address.","depth":28,"bounds":{"left":0.15292554,"top":0.12490024,"width":0.057679523,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"job","depth":29,"bounds":{"left":0.13696809,"top":0.15722266,"width":0.00831117,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": Job title (e.g., 'Account Executive'); nullable.","depth":28,"bounds":{"left":0.14727394,"top":0.15602554,"width":0.10887633,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Preferences & Context:","depth":26,"bounds":{"left":0.12400266,"top":0.18715084,"width":0.059840426,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"timezone","depth":29,"bounds":{"left":0.13696809,"top":0.21947326,"width":0.022273935,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": IANA identifier (e.g., 'America/New_York') for reasoning about local call times.","depth":28,"bounds":{"left":0.1612367,"top":0.21827614,"width":0.18866356,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"spoken_languages","depth":29,"bounds":{"left":0.13696809,"top":0.25059855,"width":0.044714097,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": An array of locale codes. The first entry serves as the primary/default language.","depth":28,"bounds":{"left":0.1349734,"top":0.24940144,"width":0.21908244,"height":0.038707104},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"roles","depth":29,"bounds":{"left":0.13696809,"top":0.30407023,"width":0.013962766,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": Platform permissions determining capabilities (e.g.,","depth":28,"bounds":{"left":0.15292554,"top":0.3028731,"width":0.12765957,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"admin","depth":29,"bounds":{"left":0.28257978,"top":0.30407023,"width":0.013962766,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":28,"bounds":{"left":0.29853722,"top":0.3028731,"width":0.0026595744,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"manager","depth":29,"bounds":{"left":0.30319148,"top":0.30407023,"width":0.019614361,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":28,"bounds":{"left":0.32480052,"top":0.3028731,"width":0.0026595744,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"recorder_and_voice","depth":29,"bounds":{"left":0.13696809,"top":0.3264166,"width":0.050199468,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":28,"bounds":{"left":0.18916224,"top":0.32521948,"width":0.0033244682,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Sync Settings:","depth":26,"bounds":{"left":0.12400266,"top":0.35634476,"width":0.03706782,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Booleans indicating whether the user has automated CRM syncing enabled for:","depth":26,"bounds":{"left":0.16107048,"top":0.35634476,"width":0.19182181,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"sync_dialer","depth":29,"bounds":{"left":0.13696809,"top":0.3886672,"width":0.030751329,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Jiminny softphone calls)","depth":28,"bounds":{"left":0.1697141,"top":0.38747007,"width":0.061835106,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"sync_email","depth":29,"bounds":{"left":0.13696809,"top":0.4197925,"width":0.027925532,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Customer email conversations)","depth":28,"bounds":{"left":0.1668883,"top":0.41859537,"width":0.077792555,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"sync_calendar","depth":29,"bounds":{"left":0.13696809,"top":0.4509178,"width":0.036236703,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Scheduled meetings)","depth":28,"bounds":{"left":0.17519946,"top":0.44972068,"width":0.054521278,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Team Context:","depth":26,"bounds":{"left":0.12400266,"top":0.48084596,"width":0.037400264,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"An object containing the team's","depth":26,"bounds":{"left":0.16140293,"top":0.48084596,"width":0.07895612,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"id","depth":27,"bounds":{"left":0.24235372,"top":0.4820431,"width":0.0056515955,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.25,"top":0.48084596,"width":0.0026595744,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"name","depth":27,"bounds":{"left":0.25465426,"top":0.4820431,"width":0.011303191,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.2679521,"top":0.48084596,"width":0.013131649,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"playbook","depth":27,"bounds":{"left":0.28307846,"top":0.4820431,"width":0.02244016,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-9094692793165918642
|
8319784429274662286
|
click
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
[JY-20891] Sidekick SMS issue - Jira
[JY-20891] Sidekick SMS issue - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Usage | Windsurf
Usage | Windsurf
[SRD-6853] Moxso - Potential deal stages bug - Jira
[SRD-6853] Moxso - Potential deal stages bug - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
Feed — jiminny — Sentry
Feed — jiminny — Sentry
[JY-20906] Review of Pipedrive SDK - Jira
[JY-20906] Review of Pipedrive SDK - Jira
Pipedrive API Reference and Documentation
Pipedrive API Reference and Documentation
pipedrive/client-php: Pipedrive API client for PHP
pipedrive/client-php: Pipedrive API client for PHP
[jiminny/infrastructure] JY-20623 Add SQS queue for panorama reports (PR #728) - [EMAIL] - Jiminny Mail
[jiminny/infrastructure] JY-20623 Add SQS queue for panorama reports (PR #728) - [EMAIL] - Jiminny Mail
Pull requests · jiminny/app
Pull requests · jiminny/app
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20906] Review of Pipedrive SDK - Jira
[JY-20906] Review of Pipedrive SDK - Jira
Jy 20846 mcp enable the ai to know details about the user by nikolaybiaivanov · Pull Request #12075 · jiminny/app
Jy 20846 mcp enable the ai to know details about the user by nikolaybiaivanov · Pull Request #12075 · jiminny/app
Close tab
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
AI Chat settings
Close
WORK, Google Account: [EMAIL]
Main menu
New chat
Gemini
New Chat
Open menu for conversation actions.
Conversation with Gemini
Conversation with Gemini
Copy prompt
Edit
You said I’m on page “<tabTitle>Jy 20846 mcp enable the ai to know details about t</tabTitle>” with “<selection>@@ -0,0 +1,157 @@1+ $schema->string()35+ ->description(36+'UUID of the authenticated user. Use this as the authoritative identifier when filtering calls or deals by '37+ . 'the current user.'38+ )39+ ->required(),40+'name' => $schema->string()41+ ->description('Full display name of the user, e.g. \'John Doe\'.')42+ ->required(),43+'first_name' => $schema->string()44+ ->description(45+'First name of the user, derived from the full name field. Use for personalised greetings or addressing '46+ . 'the user directly.'47+ )48+ ->required(),49+'email' => $schema->string()50+ ->description('Primary email address of the user.')51+ ->required(),52+'timezone' => $schema->string()53+ ->description(54+'The user\'s timezone as an IANA timezone identifier, e.g. \'America/New_York\'. Use this when displaying '55+ . 'or reasoning about call times in the user\'s local time.'56+ )57+ ->required(),58+'spoken_languages' => $schema->array()59+ ->items($schema->string())60+ ->description(61+'Languages spoken during this user\'s calls, as an array of locale codes, e.g. [\'en_GB\', \'bg_BG\']. '62+ . 'The first entry is the primary/default language — used when language detection is unavailable. '63+ . 'Additional entries are other languages the user handles.'64+ )65+ ->required(),66+'sync_dialer' => $schema->boolean()67+ ->description(68+'True if the user has automatic syncing of softphone and outbound calls to their CRM enabled. When true, '69+ . 'calls made through the Jiminny dialler are automatically logged as CRM activities.'70+ )71+ ->required(),72+'sync_email' => $schema->boolean()73+ ->description(74+'True if the user has email syncing enabled. When true, customer email conversations are captured as '75+ . 'activities.'76+ )77+ ->required(),78+'sync_calendar' => $schema->boolean()79+ ->description(80+'True if the user has calendar syncing enabled. When true, scheduled meetings will be automatically '81+ . 'recorded.'82+ )83+ ->required(),84+'team' => $schema->object([85+'id' => $schema->string()86+ ->description(87+'Team UUID. Use this when filtering calls or deals by the user\'s team in search_calls or '88+ . 'search_deals.'89+ )90+ ->required(),91+'name' => $schema->string()92+ ->description('Team name, e.g. \'Account Executives\', \'Customer Success\'.')93+ ->required(),94+'playbook' => $schema->string()95+ ->description(96+'Name of the active playbook assigned to this team, e.g. \'Sales\', \'Customer Success\'. A playbook '97+ . 'defines the call evaluation criteria used for AI scoring. Null if no playbook is assigned.'98+ )99+ ->nullable()100+ ->required(),101+ ])102+ ->description('The team this user belongs to.')103+ ->required(),104+'job' => $schema->string()105+ ->description(106+'The user\'s job title, e.g. \'Software Engineer\', \'Account Executive\', \'Sales Manager\'. Null if no '107+ . 'job title has been assigned.'108+ )109+ ->nullable()110+ ->required(),111+'roles' => $schema->array()112+ ->items($schema->string())113+ ->description(114+'Roles assigned to the user, e.g. [\'admin\', \'recorder_and_voice\']. Determines what the user can do in '115+ . 'the platform. Common values: \'admin\' (full organisation admin), \'manager\' (team manager with '116+ . 'coaching access), \'recorder_and_voice\' (can record meetings and make calls), \'recorder\' (recording only).'117+ )118+ ->required(),119+ ];120+ }121+122+public function handle(Request $request): ResponseFactory123+ {124+$user = $request->user();125+assert($user instanceof User, 'auth:api + McpTierMiddleware must guarantee an authenticated User before the tool runs');126+127+$team = $user->team;128+$roles = $user->getRoles();129+$jobTitle = $user->job;130+131+$languageDialects = $user->languageDialects()->with('language')->get();132+$spokenLanguages = $languageDialects->isNotEmpty()133+ ? $languageDialects->map(fn ($dialect) => $dialect->getLanguageLocale())->toArray()134+ : [$user->getLanguage() ?? $team->getDefaultLanguage() ?? 'en'];135+136+$payload = [137+'id' => $user->getUuid(),138+'name' => $user->getName(),139+'first_name' => $user->getFirstName(),140+'email' => $user->getEmailAddress(),141+'timezone' => $user->getTimezone()->getName(),142+'spoken_languages' => $spokenLanguages,143+'sync_dialer' => $user->shouldSyncDialer(),144+'sync_email' => $user->isSyncEmailEnabled(),145+'sync_calendar' => $user->isSyncConferenceEnabled(),146+'team' => [147+'id' => $team->getUuid(),148+'name' => $team->getName(),149+'playbook' => $team->getDefaultPlaybook()?->getName(),150+ ],151+'job' => $jobTitle?->getName(),152+'roles' => $roles,153+ ];154+155+return Response::structured($payload);156+ }157+}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20846 mcp enable the ai to know details about t</tabTitle>” with “<selection>@@ -0,0 +1,157 @@1+ $schema->string()35+ ->description(36+'UUID of the authenticated user. Use this as the authoritative identifier when filtering calls or deals by '37+ . 'the current user.'38+ )39+ ->required(),40+'name' => $schema->string()41+ ->description('Full display name of the user, e.g. \'John Doe\'.')42+ ->required(),43+'first_name' => $schema->string()44+ ->description(45+'First name of the user, derived from the full name field. Use for personalised greetings or addressing '46+ . 'the user directly.'47+ )48+ ->required(),49+'email' => $schema->string()50+ ->description('Primary email address of the user.')51+ ->required(),52+'timezone' => $schema->string()53+ ->description(54+'The user\'s timezone as an IANA timezone identifier, e.g. \'America/New_York\'. Use this when displaying '55+ . 'or reasoning about call times in the user\'s local time.'56+ )57+ ->required(),58+'spoken_languages' => $schema->array()59+ ->items($schema->string())60+ ->description(61+'Languages spoken during this user\'s calls, as an array of locale codes, e.g. [\'en_GB\', \'bg_BG\']. '62+ . 'The first entry is the primary/default language — used when language detection is unavailable. '63+ . 'Additional entries are other languages the user handles.'64+ )65+ ->required(),66+'sync_dialer' => $schema->boolean()67+ ->description(68+'True if the user has automatic syncing of softphone and outbound calls to their CRM enabled. When true, '69+ . 'calls made through the Jiminny dialler are automatically logged as CRM activities.'70+ )71+ ->required(),72+'sync_email' => $schema->boolean()73+ ->description(74+'True if the user has email syncing enabled. When true, customer email conversations are captured as '75+ . 'activities.'76+ )77+ ->required(),78+'sync_calendar' => $schema->boolean()79+ ->description(80+'True if the user has calendar syncing enabled. When true, scheduled meetings will be automatically '81+ . 'recorded.'82+ )83+ ->required(),84+'team' => $schema->object([85+'id' => $schema->string()86+ ->description(87+'Team UUID. Use this when filtering calls or deals by the user\'s team in search_calls or '88+ . 'search_deals.'89+ )90+ ->required(),91+'name' => $schema->string()92+ ->description('Team name, e.g. \'Account Executives\', \'Customer Success\'.')93+ ->required(),94+'playbook' => $schema->string()95+ ->description(96+'Name of the active playbook assigned to this team, e.g. \'Sales\', \'Customer Success\'. A playbook '97+ . 'defines the call evaluation criteria used for AI scoring. Null if no playbook is assigned.'98+ )99+ ->nullable()100+ ->required(),101+ ])102+ ->description('The team this user belongs to.')103+ ->required(),104+'job' => $schema->string()105+ ->description(106+'The user\'s job title, e.g. \'Software Engineer\', \'Account Executive\', \'Sales Manager\'. Null if no '107+ . 'job title has been assigned.'108+ )109+ ->nullable()110+ ->required(),111+'roles' => $schema->array()112+ ->items($schema->string())113+ ->description(114+'Roles assigned to the user, e.g. [\'admin\', \'recorder_and_voice\']. Determines what the user can do in '115+ . 'the platform. Common values: \'admin\' (full organisation admin), \'manager\' (team manager with '116+ . 'coaching access), \'recorder_and_voice\' (can record meetings and make calls), \'recorder\' (recording only).'117+ )118+ ->required(),119+ ];120+ }121+122+public function handle(Request $request): ResponseFactory123+ {124+$user = $request->user();125+assert($user instanceof User, 'auth:api + McpTierMiddleware must guarantee an authenticated User before the tool runs');126+127+$team = $user->team;128+$roles = $user->getRoles();129+$jobTitle = $user->job;130+131+$languageDialects = $user->languageDialects()->with('language')->get();132+$spokenLanguages = $languageDialects->isNotEmpty()133+ ? $languageDialects->map(fn ($dialect) => $dialect->getLanguageLocale())->toArray()134+ : [$user->getLanguage() ?? $team->getDefaultLanguage() ?? 'en'];135+136+$payload = [137+'id' => $user->getUuid(),138+'name' => $user->getName(),139+'first_name' => $user->getFirstName(),140+'email' => $user->getEmailAddress(),141+'timezone' => $user->getTimezone()->getName(),142+'spoken_languages' => $spokenLanguages,143+'sync_dialer' => $user->shouldSyncDialer(),144+'sync_email' => $user->isSyncEmailEnabled(),145+'sync_calendar' => $user->isSyncConferenceEnabled(),146+'team' => [147+'id' => $team->getUuid(),148+'name' => $team->getName(),149+'playbook' => $team->getDefaultPlaybook()?->getName(),150+ ],151+'job' => $jobTitle?->getName(),152+'roles' => $roles,153+ ];154+155+return Response::structured($payload);156+ }157+}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Listen
Gemini said
Gemini said
Code Summary: MCP "Get User Details" Tool Implementation
Code Summary: MCP "Get User Details" Tool Implementation
This code snippet defines the schema and execution logic for a Model Context Protocol (MCP) tool that exposes the authenticated user's profile details to an AI assistant.
Schema Definition
Schema Definition
The tool outputs a structured object containing the following required fields to guide the AI's contextual understanding and filtering capabilities:
User Identity:
*
id
: UUID used as the authoritative identifier for filtering calls or deals.
name
: Full display name (e.g., 'John Doe').
first_name
: Extracted first name, explicitly intended for personalized greetings.
email
: Primary email address.
job
: Job title (e.g., 'Account Executive'); nullable.
Preferences & Context:
timezone
: IANA identifier (e.g., 'America/New_York') for reasoning about local call times.
spoken_languages
: An array of locale codes. The first entry serves as the primary/default language.
roles
: Platform permissions determining capabilities (e.g.,
admin
,
manager
,
recorder_and_voice
).
Sync Settings:
Booleans indicating whether the user has automated CRM syncing enabled for:
sync_dialer
(Jiminny softphone calls)
sync_email
(Customer email conversations)
sync_calendar
(Scheduled meetings)
Team Context:
An object containing the team's
id
,
name
, and
playbook...
|
50587
|
NULL
|
NULL
|
NULL
|
|
50601
|
1786
|
47
|
2026-05-18T07:41:56.904540+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779090116904_m2.jpg...
|
Firefox
|
Jy 20846 mcp enable the ai to know details about t Jy 20846 mcp enable the ai to know details about the user by nikolaybiaivanov · Pull Request #12075 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12075/changes#diff-6b5 github.com/jiminny/app/pull/12075/changes#diff-6b52e3166ac7f04fdb89197e55757ffe1395595293cc2e65e630a533b034037d...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
[JY-20891] Sidekick SMS issue - Jira
[JY-20891] Sidekick SMS issue - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Usage | Windsurf
Usage | Windsurf
[SRD-6853] Moxso - Potential deal stages bug - Jira
[SRD-6853] Moxso - Potential deal stages bug - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
Feed — jiminny — Sentry
Feed — jiminny — Sentry
[JY-20906] Review of Pipedrive SDK - Jira
[JY-20906] Review of Pipedrive SDK - Jira
Pipedrive API Reference and Documentation
Pipedrive API Reference and Documentation
pipedrive/client-php: Pipedrive API client for PHP
pipedrive/client-php: Pipedrive API client for PHP
[jiminny/infrastructure] JY-20623 Add SQS queue for panorama reports (PR #728) - [EMAIL] - Jiminny Mail
[jiminny/infrastructure] JY-20623 Add SQS queue for panorama reports (PR #728) - [EMAIL] - Jiminny Mail
Pull requests · jiminny/app
Pull requests · jiminny/app
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20906] Review of Pipedrive SDK - Jira
[JY-20906] Review of Pipedrive SDK - Jira
Jy 20846 mcp enable the ai to know details about the user by nikolaybiaivanov · Pull Request #12075 · jiminny/app
Jy 20846 mcp enable the ai to know details about the user by nikolaybiaivanov · Pull Request #12075 · jiminny/app
Close tab
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
AI Chat settings
Close
WORK, Google Account: [EMAIL]
Main menu
New chat
Gemini
New Chat
Open menu for conversation actions.
Conversation with Gemini
Conversation with Gemini
Copy prompt
Edit
You said I’m on page “<tabTitle>Jy 20846 mcp enable the ai to know details about t</tabTitle>” with “<selection>@@ -0,0 +1,157 @@1+ $schema->string()35+ ->description(36+'UUID of the authenticated user. Use this as the authoritative identifier when filtering calls or deals by '37+ . 'the current user.'38+ )39+ ->required(),40+'name' => $schema->string()41+ ->description('Full display name of the user, e.g. \'John Doe\'.')42+ ->required(),43+'first_name' => $schema->string()44+ ->description(45+'First name of the user, derived from the full name field. Use for personalised greetings or addressing '46+ . 'the user directly.'47+ )48+ ->required(),49+'email' => $schema->string()50+ ->description('Primary email address of the user.')51+ ->required(),52+'timezone' => $schema->string()53+ ->description(54+'The user\'s timezone as an IANA timezone identifier, e.g. \'America/New_York\'. Use this when displaying '55+ . 'or reasoning about call times in the user\'s local time.'56+ )57+ ->required(),58+'spoken_languages' => $schema->array()59+ ->items($schema->string())60+ ->description(61+'Languages spoken during this user\'s calls, as an array of locale codes, e.g. [\'en_GB\', \'bg_BG\']. '62+ . 'The first entry is the primary/default language — used when language detection is unavailable. '63+ . 'Additional entries are other languages the user handles.'64+ )65+ ->required(),66+'sync_dialer' => $schema->boolean()67+ ->description(68+'True if the user has automatic syncing of softphone and outbound calls to their CRM enabled. When true, '69+ . 'calls made through the Jiminny dialler are automatically logged as CRM activities.'70+ )71+ ->required(),72+'sync_email' => $schema->boolean()73+ ->description(74+'True if the user has email syncing enabled. When true, customer email conversations are captured as '75+ . 'activities.'76+ )77+ ->required(),78+'sync_calendar' => $schema->boolean()79+ ->description(80+'True if the user has calendar syncing enabled. When true, scheduled meetings will be automatically '81+ . 'recorded.'82+ )83+ ->required(),84+'team' => $schema->object([85+'id' => $schema->string()86+ ->description(87+'Team UUID. Use this when filtering calls or deals by the user\'s team in search_calls or '88+ . 'search_deals.'89+ )90+ ->required(),91+'name' => $schema->string()92+ ->description('Team name, e.g. \'Account Executives\', \'Customer Success\'.')93+ ->required(),94+'playbook' => $schema->string()95+ ->description(96+'Name of the active playbook assigned to this team, e.g. \'Sales\', \'Customer Success\'. A playbook '97+ . 'defines the call evaluation criteria used for AI scoring. Null if no playbook is assigned.'98+ )99+ ->nullable()100+ ->required(),101+ ])102+ ->description('The team this user belongs to.')103+ ->required(),104+'job' => $schema->string()105+ ->description(106+'The user\'s job title, e.g. \'Software Engineer\', \'Account Executive\', \'Sales Manager\'. Null if no '107+ . 'job title has been assigned.'108+ )109+ ->nullable()110+ ->required(),111+'roles' => $schema->array()112+ ->items($schema->string())113+ ->description(114+'Roles assigned to the user, e.g. [\'admin\', \'recorder_and_voice\']. Determines what the user can do in '115+ . 'the platform. Common values: \'admin\' (full organisation admin), \'manager\' (team manager with '116+ . 'coaching access), \'recorder_and_voice\' (can record meetings and make calls), \'recorder\' (recording only).'117+ )118+ ->required(),119+ ];120+ }121+122+public function handle(Request $request): ResponseFactory123+ {124+$user = $request->user();125+assert($user instanceof User, 'auth:api + McpTierMiddleware must guarantee an authenticated User before the tool runs');126+127+$team = $user->team;128+$roles = $user->getRoles();129+$jobTitle = $user->job;130+131+$languageDialects = $user->languageDialects()->with('language')->get();132+$spokenLanguages = $languageDialects->isNotEmpty()133+ ? $languageDialects->map(fn ($dialect) => $dialect->getLanguageLocale())->toArray()134+ : [$user->getLanguage() ?? $team->getDefaultLanguage() ?? 'en'];135+136+$payload = [137+'id' => $user->getUuid(),138+'name' => $user->getName(),139+'first_name' => $user->getFirstName(),140+'email' => $user->getEmailAddress(),141+'timezone' => $user->getTimezone()->getName(),142+'spoken_languages' => $spokenLanguages,143+'sync_dialer' => $user->shouldSyncDialer(),144+'sync_email' => $user->isSyncEmailEnabled(),145+'sync_calendar' => $user->isSyncConferenceEnabled(),146+'team' => [147+'id' => $team->getUuid(),148+'name' => $team->getName(),149+'playbook' => $team->getDefaultPlaybook()?->getName(),150+ ],151+'job' => $jobTitle?->getName(),152+'roles' => $roles,153+ ];154+155+return Response::structured($payload);156+ }157+}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20846 mcp enable the ai to know details about t</tabTitle>” with “<selection>@@ -0,0 +1,157 @@1+ $schema->string()35+ ->description(36+'UUID of the authenticated user. Use this as the authoritative identifier when filtering calls or deals by '37+ . 'the current user.'38+ )39+ ->required(),40+'name' => $schema->string()41+ ->description('Full display name of the user, e.g. \'John Doe\'.')42+ ->required(),43+'first_name' => $schema->string()44+ ->description(45+'First name of the user, derived from the full name field. Use for personalised greetings or addressing '46+ . 'the user directly.'47+ )48+ ->required(),49+'email' => $schema->string()50+ ->description('Primary email address of the user.')51+ ->required(),52+'timezone' => $schema->string()53+ ->description(54+'The user\'s timezone as an IANA timezone identifier, e.g. \'America/New_York\'. Use this when displaying '55+ . 'or reasoning about call times in the user\'s local time.'56+ )57+ ->required(),58+'spoken_languages' => $schema->array()59+ ->items($schema->string())60+ ->description(61+'Languages spoken during this user\'s calls, as an array of locale codes, e.g. [\'en_GB\', \'bg_BG\']. '62+ . 'The first entry is the primary/default language — used when language detection is unavailable. '63+ . 'Additional entries are other languages the user handles.'64+ )65+ ->required(),66+'sync_dialer' => $schema->boolean()67+ ->description(68+'True if the user has automatic syncing of softphone and outbound calls to their CRM enabled. When true, '69+ . 'calls made through the Jiminny dialler are automatically logged as CRM activities.'70+ )71+ ->required(),72+'sync_email' => $schema->boolean()73+ ->description(74+'True if the user has email syncing enabled. When true, customer email conversations are captured as '75+ . 'activities.'76+ )77+ ->required(),78+'sync_calendar' => $schema->boolean()79+ ->description(80+'True if the user has calendar syncing enabled. When true, scheduled meetings will be automatically '81+ . 'recorded.'82+ )83+ ->required(),84+'team' => $schema->object([85+'id' => $schema->string()86+ ->description(87+'Team UUID. Use this when filtering calls or deals by the user\'s team in search_calls or '88+ . 'search_deals.'89+ )90+ ->required(),91+'name' => $schema->string()92+ ->description('Team name, e.g. \'Account Executives\', \'Customer Success\'.')93+ ->required(),94+'playbook' => $schema->string()95+ ->description(96+'Name of the active playbook assigned to this team, e.g. \'Sales\', \'Customer Success\'. A playbook '97+ . 'defines the call evaluation criteria used for AI scoring. Null if no playbook is assigned.'98+ )99+ ->nullable()100+ ->required(),101+ ])102+ ->description('The team this user belongs to.')103+ ->required(),104+'job' => $schema->string()105+ ->description(106+'The user\'s job title, e.g. \'Software Engineer\', \'Account Executive\', \'Sales Manager\'. Null if no '107+ . 'job title has been assigned.'108+ )109+ ->nullable()110+ ->required(),111+'roles' => $schema->array()112+ ->items($schema->string())113+ ->description(114+'Roles assigned to the user, e.g. [\'admin\', \'recorder_and_voice\']. Determines what the user can do in '115+ . 'the platform. Common values: \'admin\' (full organisation admin), \'manager\' (team manager with '116+ . 'coaching access), \'recorder_and_voice\' (can record meetings and make calls), \'recorder\' (recording only).'117+ )118+ ->required(),119+ ];120+ }121+122+public function handle(Request $request): ResponseFactory123+ {124+$user = $request->user();125+assert($user instanceof User, 'auth:api + McpTierMiddleware must guarantee an authenticated User before the tool runs');126+127+$team = $user->team;128+$roles = $user->getRoles();129+$jobTitle = $user->job;130+131+$languageDialects = $user->languageDialects()->with('language')->get();132+$spokenLanguages = $languageDialects->isNotEmpty()133+ ? $languageDialects->map(fn ($dialect) => $dialect->getLanguageLocale())->toArray()134+ : [$user->getLanguage() ?? $team->getDefaultLanguage() ?? 'en'];135+136+$payload = [137+'id' => $user->getUuid(),138+'name' => $user->getName(),139+'first_name' => $user->getFirstName(),140+'email' => $user->getEmailAddress(),141+'timezone' => $user->getTimezone()->getName(),142+'spoken_languages' => $spokenLanguages,143+'sync_dialer' => $user->shouldSyncDialer(),144+'sync_email' => $user->isSyncEmailEnabled(),145+'sync_calendar' => $user->isSyncConferenceEnabled(),146+'team' => [147+'id' => $team->getUuid(),148+'name' => $team->getName(),149+'playbook' => $team->getDefaultPlaybook()?->getName(),150+ ],151+'job' => $jobTitle?->getName(),152+'roles' => $roles,153+ ];154+155+return Response::structured($payload);156+ }157+}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Listen
Gemini said
Gemini said
Code Summary: MCP "Get User Details" Tool Implementation
Code Summary: MCP "Get User Details" Tool Implementation
This code snippet defines the schema and execution logic for a Model Context Protocol (MCP) tool that exposes the authenticated user's profile details to an AI assistant.
Schema Definition
Schema Definition
The tool outputs a structured object containing the following required fields to guide the AI's contextual understanding and filtering capabilities:
User Identity:
*
id
: UUID used as the authoritative identifier for filtering calls or deals.
name
: Full display name (e.g., 'John Doe').
first_name
: Extracted first name, explicitly intended for personalized greetings.
email
: Primary email address.
job
: Job title (e.g., 'Account Executive'); nullable.
Preferences & Context:
timezone
: IANA identifier (e.g., 'America/New_York') for reasoning about local call times.
spoken_languages
: An array of locale codes. The first entry serves as the primary/default language.
roles
: Platform permissions determining capabilities (e.g.,
admin
,
manager
,
recorder_and_voice
).
Sync Settings:
Booleans indicating whether the user has automated CRM syncing enabled for:
sync_dialer
(Jiminny softphone calls)
sync_email
(Customer email conversations)
sync_calendar
(Scheduled meetings)
Team Context:
An object containing the team's
id
,
name
, and
playbook...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.15658244,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20891] Sidekick SMS issue - Jira","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20891] Sidekick SMS issue - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.064494684,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.18068483,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Usage | Windsurf","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Usage | Windsurf","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.029920213,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6853] Moxso - Potential deal stages bug - Jira","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6853] Moxso - Potential deal stages bug - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.09158909,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.042719416,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20906] Review of Pipedrive SDK - Jira","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20906] Review of Pipedrive SDK - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.07413564,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipedrive API Reference and Documentation","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipedrive API Reference and Documentation","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.07679521,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"pipedrive/client-php: Pipedrive API client for PHP","depth":4,"bounds":{"left":0.0,"top":0.3790902,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"pipedrive/client-php: Pipedrive API client for PHP","depth":5,"bounds":{"left":0.013297873,"top":0.39026338,"width":0.08543883,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[jiminny/infrastructure] JY-20623 Add SQS queue for panorama reports (PR #728) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.41181165,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[jiminny/infrastructure] JY-20623 Add SQS queue for panorama reports (PR #728) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"bounds":{"left":0.013297873,"top":0.42298484,"width":0.21791889,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pull requests · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.4445331,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.4557063,"width":0.04537899,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":4,"bounds":{"left":0.0,"top":0.4772546,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.4884278,"width":0.15259309,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20906] Review of Pipedrive SDK - Jira","depth":4,"bounds":{"left":0.0,"top":0.509976,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20906] Review of Pipedrive SDK - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.5211492,"width":0.07413564,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20846 mcp enable the ai to know details about the user by nikolaybiaivanov · Pull Request #12075 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.54269755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Jy 20846 mcp enable the ai to know details about the user by nikolaybiaivanov · Pull Request #12075 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.55387074,"width":0.19847074,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.54988027,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.57701516,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.35854387,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.37051198,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"WORK, Google Account: lukas.kovalik@jiminny.com","depth":12,"bounds":{"left":0.3678524,"top":0.103751,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Main menu","depth":12,"bounds":{"left":0.08494016,"top":0.103751,"width":0.013297873,"height":0.031923383},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.09823803,"top":0.10454908,"width":0.028590426,"height":0.030327214},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.1008976,"top":0.10973663,"width":0.021941489,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Chat","depth":12,"bounds":{"left":0.33992687,"top":0.103751,"width":0.013297873,"height":0.031923383},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":12,"bounds":{"left":0.35322472,"top":0.103751,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14764565,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.15003991,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":21,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20846 mcp enable the ai to know details about t</tabTitle>” with “<selection>@@ -0,0 +1,157 @@1+ $schema->string()35+ ->description(36+'UUID of the authenticated user. Use this as the authoritative identifier when filtering calls or deals by '37+ . 'the current user.'38+ )39+ ->required(),40+'name' => $schema->string()41+ ->description('Full display name of the user, e.g. \\'John Doe\\'.')42+ ->required(),43+'first_name' => $schema->string()44+ ->description(45+'First name of the user, derived from the full name field. Use for personalised greetings or addressing '46+ . 'the user directly.'47+ )48+ ->required(),49+'email' => $schema->string()50+ ->description('Primary email address of the user.')51+ ->required(),52+'timezone' => $schema->string()53+ ->description(54+'The user\\'s timezone as an IANA timezone identifier, e.g. \\'America/New_York\\'. Use this when displaying '55+ . 'or reasoning about call times in the user\\'s local time.'56+ )57+ ->required(),58+'spoken_languages' => $schema->array()59+ ->items($schema->string())60+ ->description(61+'Languages spoken during this user\\'s calls, as an array of locale codes, e.g. [\\'en_GB\\', \\'bg_BG\\']. '62+ . 'The first entry is the primary/default language — used when language detection is unavailable. '63+ . 'Additional entries are other languages the user handles.'64+ )65+ ->required(),66+'sync_dialer' => $schema->boolean()67+ ->description(68+'True if the user has automatic syncing of softphone and outbound calls to their CRM enabled. When true, '69+ . 'calls made through the Jiminny dialler are automatically logged as CRM activities.'70+ )71+ ->required(),72+'sync_email' => $schema->boolean()73+ ->description(74+'True if the user has email syncing enabled. When true, customer email conversations are captured as '75+ . 'activities.'76+ )77+ ->required(),78+'sync_calendar' => $schema->boolean()79+ ->description(80+'True if the user has calendar syncing enabled. When true, scheduled meetings will be automatically '81+ . 'recorded.'82+ )83+ ->required(),84+'team' => $schema->object([85+'id' => $schema->string()86+ ->description(87+'Team UUID. Use this when filtering calls or deals by the user\\'s team in search_calls or '88+ . 'search_deals.'89+ )90+ ->required(),91+'name' => $schema->string()92+ ->description('Team name, e.g. \\'Account Executives\\', \\'Customer Success\\'.')93+ ->required(),94+'playbook' => $schema->string()95+ ->description(96+'Name of the active playbook assigned to this team, e.g. \\'Sales\\', \\'Customer Success\\'. A playbook '97+ . 'defines the call evaluation criteria used for AI scoring. Null if no playbook is assigned.'98+ )99+ ->nullable()100+ ->required(),101+ ])102+ ->description('The team this user belongs to.')103+ ->required(),104+'job' => $schema->string()105+ ->description(106+'The user\\'s job title, e.g. \\'Software Engineer\\', \\'Account Executive\\', \\'Sales Manager\\'. Null if no '107+ . 'job title has been assigned.'108+ )109+ ->nullable()110+ ->required(),111+'roles' => $schema->array()112+ ->items($schema->string())113+ ->description(114+'Roles assigned to the user, e.g. [\\'admin\\', \\'recorder_and_voice\\']. Determines what the user can do in '115+ . 'the platform. Common values: \\'admin\\' (full organisation admin), \\'manager\\' (team manager with '116+ . 'coaching access), \\'recorder_and_voice\\' (can record meetings and make calls), \\'recorder\\' (recording only).'117+ )118+ ->required(),119+ ];120+ }121+122+public function handle(Request $request): ResponseFactory123+ {124+$user = $request->user();125+assert($user instanceof User, 'auth:api + McpTierMiddleware must guarantee an authenticated User before the tool runs');126+127+$team = $user->team;128+$roles = $user->getRoles();129+$jobTitle = $user->job;130+131+$languageDialects = $user->languageDialects()->with('language')->get();132+$spokenLanguages = $languageDialects->isNotEmpty()133+ ? $languageDialects->map(fn ($dialect) => $dialect->getLanguageLocale())->toArray()134+ : [$user->getLanguage() ?? $team->getDefaultLanguage() ?? 'en'];135+136+$payload = [137+'id' => $user->getUuid(),138+'name' => $user->getName(),139+'first_name' => $user->getFirstName(),140+'email' => $user->getEmailAddress(),141+'timezone' => $user->getTimezone()->getName(),142+'spoken_languages' => $spokenLanguages,143+'sync_dialer' => $user->shouldSyncDialer(),144+'sync_email' => $user->isSyncEmailEnabled(),145+'sync_calendar' => $user->isSyncConferenceEnabled(),146+'team' => [147+'id' => $team->getUuid(),148+'name' => $team->getName(),149+'playbook' => $team->getDefaultPlaybook()?->getName(),150+ ],151+'job' => $jobTitle?->getName(),152+'roles' => $roles,153+ ];154+155+return Response::structured($payload);156+ }157+}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20846 mcp enable the ai to know details about t</tabTitle>” with “<selection>@@ -0,0 +1,157 @@1+ $schema->string()35+ ->description(36+'UUID of the authenticated user. Use this as the authoritative identifier when filtering calls or deals by '37+ . 'the current user.'38+ )39+ ->required(),40+'name' => $schema->string()41+ ->description('Full display name of the user, e.g. \\'John Doe\\'.')42+ ->required(),43+'first_name' => $schema->string()44+ ->description(45+'First name of the user, derived from the full name field. Use for personalised greetings or addressing '46+ . 'the user directly.'47+ )48+ ->required(),49+'email' => $schema->string()50+ ->description('Primary email address of the user.')51+ ->required(),52+'timezone' => $schema->string()53+ ->description(54+'The user\\'s timezone as an IANA timezone identifier, e.g. \\'America/New_York\\'. Use this when displaying '55+ . 'or reasoning about call times in the user\\'s local time.'56+ )57+ ->required(),58+'spoken_languages' => $schema->array()59+ ->items($schema->string())60+ ->description(61+'Languages spoken during this user\\'s calls, as an array of locale codes, e.g. [\\'en_GB\\', \\'bg_BG\\']. '62+ . 'The first entry is the primary/default language — used when language detection is unavailable. '63+ . 'Additional entries are other languages the user handles.'64+ )65+ ->required(),66+'sync_dialer' => $schema->boolean()67+ ->description(68+'True if the user has automatic syncing of softphone and outbound calls to their CRM enabled. When true, '69+ . 'calls made through the Jiminny dialler are automatically logged as CRM activities.'70+ )71+ ->required(),72+'sync_email' => $schema->boolean()73+ ->description(74+'True if the user has email syncing enabled. When true, customer email conversations are captured as '75+ . 'activities.'76+ )77+ ->required(),78+'sync_calendar' => $schema->boolean()79+ ->description(80+'True if the user has calendar syncing enabled. When true, scheduled meetings will be automatically '81+ . 'recorded.'82+ )83+ ->required(),84+'team' => $schema->object([85+'id' => $schema->string()86+ ->description(87+'Team UUID. Use this when filtering calls or deals by the user\\'s team in search_calls or '88+ . 'search_deals.'89+ )90+ ->required(),91+'name' => $schema->string()92+ ->description('Team name, e.g. \\'Account Executives\\', \\'Customer Success\\'.')93+ ->required(),94+'playbook' => $schema->string()95+ ->description(96+'Name of the active playbook assigned to this team, e.g. \\'Sales\\', \\'Customer Success\\'. A playbook '97+ . 'defines the call evaluation criteria used for AI scoring. Null if no playbook is assigned.'98+ )99+ ->nullable()100+ ->required(),101+ ])102+ ->description('The team this user belongs to.')103+ ->required(),104+'job' => $schema->string()105+ ->description(106+'The user\\'s job title, e.g. \\'Software Engineer\\', \\'Account Executive\\', \\'Sales Manager\\'. Null if no '107+ . 'job title has been assigned.'108+ )109+ ->nullable()110+ ->required(),111+'roles' => $schema->array()112+ ->items($schema->string())113+ ->description(114+'Roles assigned to the user, e.g. [\\'admin\\', \\'recorder_and_voice\\']. Determines what the user can do in '115+ . 'the platform. Common values: \\'admin\\' (full organisation admin), \\'manager\\' (team manager with '116+ . 'coaching access), \\'recorder_and_voice\\' (can record meetings and make calls), \\'recorder\\' (recording only).'117+ )118+ ->required(),119+ ];120+ }121+122+public function handle(Request $request): ResponseFactory123+ {124+$user = $request->user();125+assert($user instanceof User, 'auth:api + McpTierMiddleware must guarantee an authenticated User before the tool runs');126+127+$team = $user->team;128+$roles = $user->getRoles();129+$jobTitle = $user->job;130+131+$languageDialects = $user->languageDialects()->with('language')->get();132+$spokenLanguages = $languageDialects->isNotEmpty()133+ ? $languageDialects->map(fn ($dialect) => $dialect->getLanguageLocale())->toArray()134+ : [$user->getLanguage() ?? $team->getDefaultLanguage() ?? 'en'];135+136+$payload = [137+'id' => $user->getUuid(),138+'name' => $user->getName(),139+'first_name' => $user->getFirstName(),140+'email' => $user->getEmailAddress(),141+'timezone' => $user->getTimezone()->getName(),142+'spoken_languages' => $spokenLanguages,143+'sync_dialer' => $user->shouldSyncDialer(),144+'sync_email' => $user->isSyncEmailEnabled(),145+'sync_calendar' => $user->isSyncConferenceEnabled(),146+'team' => [147+'id' => $team->getUuid(),148+'name' => $team->getName(),149+'playbook' => $team->getDefaultPlaybook()?->getName(),150+ ],151+'job' => $jobTitle?->getName(),152+'roles' => $roles,153+ ];154+155+return Response::structured($payload);156+ }157+}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Listen","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Code Summary: MCP \"Get User Details\" Tool Implementation","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Code Summary: MCP \"Get User Details\" Tool Implementation","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This code snippet defines the schema and execution logic for a Model Context Protocol (MCP) tool that exposes the authenticated user's profile details to an AI assistant.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Schema Definition","depth":23,"bounds":{"left":0.111369684,"top":0.0,"width":0.24734043,"height":0.01915403},"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Schema Definition","depth":24,"bounds":{"left":0.111369684,"top":0.0,"width":0.047539894,"height":0.016360734},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The tool outputs a structured object containing the following required fields to guide the AI's contextual understanding and filtering capabilities:","depth":24,"bounds":{"left":0.111369684,"top":0.0,"width":0.22423537,"height":0.038707104},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"User Identity:","depth":26,"bounds":{"left":0.12400266,"top":0.03152434,"width":0.03523936,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"*","depth":26,"bounds":{"left":0.15924202,"top":0.03152434,"width":0.0051529254,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"id","depth":27,"bounds":{"left":0.16638963,"top":0.032721467,"width":0.0056515955,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": UUID used as the authoritative identifier for filtering calls or deals.","depth":26,"bounds":{"left":0.1740359,"top":0.03152434,"width":0.16156915,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"name","depth":29,"bounds":{"left":0.13696809,"top":0.06384677,"width":0.011136968,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": Full display name (e.g., 'John Doe').","depth":28,"bounds":{"left":0.15009974,"top":0.06264964,"width":0.087765954,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"first_name","depth":29,"bounds":{"left":0.13696809,"top":0.09497207,"width":0.027925532,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": Extracted first name, explicitly intended for personalized greetings.","depth":28,"bounds":{"left":0.1668883,"top":0.09377494,"width":0.1640625,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"email","depth":29,"bounds":{"left":0.13696809,"top":0.12609737,"width":0.013962766,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": Primary email address.","depth":28,"bounds":{"left":0.15292554,"top":0.12490024,"width":0.057679523,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"job","depth":29,"bounds":{"left":0.13696809,"top":0.15722266,"width":0.00831117,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": Job title (e.g., 'Account Executive'); nullable.","depth":28,"bounds":{"left":0.14727394,"top":0.15602554,"width":0.10887633,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Preferences & Context:","depth":26,"bounds":{"left":0.12400266,"top":0.18715084,"width":0.059840426,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"timezone","depth":29,"bounds":{"left":0.13696809,"top":0.21947326,"width":0.022273935,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": IANA identifier (e.g., 'America/New_York') for reasoning about local call times.","depth":28,"bounds":{"left":0.1612367,"top":0.21827614,"width":0.18866356,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"spoken_languages","depth":29,"bounds":{"left":0.13696809,"top":0.25059855,"width":0.044714097,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": An array of locale codes. The first entry serves as the primary/default language.","depth":28,"bounds":{"left":0.1349734,"top":0.24940144,"width":0.21908244,"height":0.038707104},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"roles","depth":29,"bounds":{"left":0.13696809,"top":0.30407023,"width":0.013962766,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": Platform permissions determining capabilities (e.g.,","depth":28,"bounds":{"left":0.15292554,"top":0.3028731,"width":0.12765957,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"admin","depth":29,"bounds":{"left":0.28257978,"top":0.30407023,"width":0.013962766,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":28,"bounds":{"left":0.29853722,"top":0.3028731,"width":0.0026595744,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"manager","depth":29,"bounds":{"left":0.30319148,"top":0.30407023,"width":0.019614361,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":28,"bounds":{"left":0.32480052,"top":0.3028731,"width":0.0026595744,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"recorder_and_voice","depth":29,"bounds":{"left":0.13696809,"top":0.3264166,"width":0.050199468,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":28,"bounds":{"left":0.18916224,"top":0.32521948,"width":0.0033244682,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Sync Settings:","depth":26,"bounds":{"left":0.12400266,"top":0.35634476,"width":0.03706782,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Booleans indicating whether the user has automated CRM syncing enabled for:","depth":26,"bounds":{"left":0.16107048,"top":0.35634476,"width":0.19182181,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"sync_dialer","depth":29,"bounds":{"left":0.13696809,"top":0.3886672,"width":0.030751329,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Jiminny softphone calls)","depth":28,"bounds":{"left":0.1697141,"top":0.38747007,"width":0.061835106,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"sync_email","depth":29,"bounds":{"left":0.13696809,"top":0.4197925,"width":0.027925532,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Customer email conversations)","depth":28,"bounds":{"left":0.1668883,"top":0.41859537,"width":0.077792555,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"sync_calendar","depth":29,"bounds":{"left":0.13696809,"top":0.4509178,"width":0.036236703,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Scheduled meetings)","depth":28,"bounds":{"left":0.17519946,"top":0.44972068,"width":0.054521278,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Team Context:","depth":26,"bounds":{"left":0.12400266,"top":0.48084596,"width":0.037400264,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"An object containing the team's","depth":26,"bounds":{"left":0.16140293,"top":0.48084596,"width":0.07895612,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"id","depth":27,"bounds":{"left":0.24235372,"top":0.4820431,"width":0.0056515955,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"bounds":{"left":0.25,"top":0.48084596,"width":0.0026595744,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"name","depth":27,"bounds":{"left":0.25465426,"top":0.4820431,"width":0.011303191,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"bounds":{"left":0.2679521,"top":0.48084596,"width":0.013131649,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"playbook","depth":27,"bounds":{"left":0.28307846,"top":0.4820431,"width":0.02244016,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-9094692793165918642
|
8319784429274662286
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
[JY-20891] Sidekick SMS issue - Jira
[JY-20891] Sidekick SMS issue - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Usage | Windsurf
Usage | Windsurf
[SRD-6853] Moxso - Potential deal stages bug - Jira
[SRD-6853] Moxso - Potential deal stages bug - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
Feed — jiminny — Sentry
Feed — jiminny — Sentry
[JY-20906] Review of Pipedrive SDK - Jira
[JY-20906] Review of Pipedrive SDK - Jira
Pipedrive API Reference and Documentation
Pipedrive API Reference and Documentation
pipedrive/client-php: Pipedrive API client for PHP
pipedrive/client-php: Pipedrive API client for PHP
[jiminny/infrastructure] JY-20623 Add SQS queue for panorama reports (PR #728) - [EMAIL] - Jiminny Mail
[jiminny/infrastructure] JY-20623 Add SQS queue for panorama reports (PR #728) - [EMAIL] - Jiminny Mail
Pull requests · jiminny/app
Pull requests · jiminny/app
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20906] Review of Pipedrive SDK - Jira
[JY-20906] Review of Pipedrive SDK - Jira
Jy 20846 mcp enable the ai to know details about the user by nikolaybiaivanov · Pull Request #12075 · jiminny/app
Jy 20846 mcp enable the ai to know details about the user by nikolaybiaivanov · Pull Request #12075 · jiminny/app
Close tab
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
AI Chat settings
Close
WORK, Google Account: [EMAIL]
Main menu
New chat
Gemini
New Chat
Open menu for conversation actions.
Conversation with Gemini
Conversation with Gemini
Copy prompt
Edit
You said I’m on page “<tabTitle>Jy 20846 mcp enable the ai to know details about t</tabTitle>” with “<selection>@@ -0,0 +1,157 @@1+ $schema->string()35+ ->description(36+'UUID of the authenticated user. Use this as the authoritative identifier when filtering calls or deals by '37+ . 'the current user.'38+ )39+ ->required(),40+'name' => $schema->string()41+ ->description('Full display name of the user, e.g. \'John Doe\'.')42+ ->required(),43+'first_name' => $schema->string()44+ ->description(45+'First name of the user, derived from the full name field. Use for personalised greetings or addressing '46+ . 'the user directly.'47+ )48+ ->required(),49+'email' => $schema->string()50+ ->description('Primary email address of the user.')51+ ->required(),52+'timezone' => $schema->string()53+ ->description(54+'The user\'s timezone as an IANA timezone identifier, e.g. \'America/New_York\'. Use this when displaying '55+ . 'or reasoning about call times in the user\'s local time.'56+ )57+ ->required(),58+'spoken_languages' => $schema->array()59+ ->items($schema->string())60+ ->description(61+'Languages spoken during this user\'s calls, as an array of locale codes, e.g. [\'en_GB\', \'bg_BG\']. '62+ . 'The first entry is the primary/default language — used when language detection is unavailable. '63+ . 'Additional entries are other languages the user handles.'64+ )65+ ->required(),66+'sync_dialer' => $schema->boolean()67+ ->description(68+'True if the user has automatic syncing of softphone and outbound calls to their CRM enabled. When true, '69+ . 'calls made through the Jiminny dialler are automatically logged as CRM activities.'70+ )71+ ->required(),72+'sync_email' => $schema->boolean()73+ ->description(74+'True if the user has email syncing enabled. When true, customer email conversations are captured as '75+ . 'activities.'76+ )77+ ->required(),78+'sync_calendar' => $schema->boolean()79+ ->description(80+'True if the user has calendar syncing enabled. When true, scheduled meetings will be automatically '81+ . 'recorded.'82+ )83+ ->required(),84+'team' => $schema->object([85+'id' => $schema->string()86+ ->description(87+'Team UUID. Use this when filtering calls or deals by the user\'s team in search_calls or '88+ . 'search_deals.'89+ )90+ ->required(),91+'name' => $schema->string()92+ ->description('Team name, e.g. \'Account Executives\', \'Customer Success\'.')93+ ->required(),94+'playbook' => $schema->string()95+ ->description(96+'Name of the active playbook assigned to this team, e.g. \'Sales\', \'Customer Success\'. A playbook '97+ . 'defines the call evaluation criteria used for AI scoring. Null if no playbook is assigned.'98+ )99+ ->nullable()100+ ->required(),101+ ])102+ ->description('The team this user belongs to.')103+ ->required(),104+'job' => $schema->string()105+ ->description(106+'The user\'s job title, e.g. \'Software Engineer\', \'Account Executive\', \'Sales Manager\'. Null if no '107+ . 'job title has been assigned.'108+ )109+ ->nullable()110+ ->required(),111+'roles' => $schema->array()112+ ->items($schema->string())113+ ->description(114+'Roles assigned to the user, e.g. [\'admin\', \'recorder_and_voice\']. Determines what the user can do in '115+ . 'the platform. Common values: \'admin\' (full organisation admin), \'manager\' (team manager with '116+ . 'coaching access), \'recorder_and_voice\' (can record meetings and make calls), \'recorder\' (recording only).'117+ )118+ ->required(),119+ ];120+ }121+122+public function handle(Request $request): ResponseFactory123+ {124+$user = $request->user();125+assert($user instanceof User, 'auth:api + McpTierMiddleware must guarantee an authenticated User before the tool runs');126+127+$team = $user->team;128+$roles = $user->getRoles();129+$jobTitle = $user->job;130+131+$languageDialects = $user->languageDialects()->with('language')->get();132+$spokenLanguages = $languageDialects->isNotEmpty()133+ ? $languageDialects->map(fn ($dialect) => $dialect->getLanguageLocale())->toArray()134+ : [$user->getLanguage() ?? $team->getDefaultLanguage() ?? 'en'];135+136+$payload = [137+'id' => $user->getUuid(),138+'name' => $user->getName(),139+'first_name' => $user->getFirstName(),140+'email' => $user->getEmailAddress(),141+'timezone' => $user->getTimezone()->getName(),142+'spoken_languages' => $spokenLanguages,143+'sync_dialer' => $user->shouldSyncDialer(),144+'sync_email' => $user->isSyncEmailEnabled(),145+'sync_calendar' => $user->isSyncConferenceEnabled(),146+'team' => [147+'id' => $team->getUuid(),148+'name' => $team->getName(),149+'playbook' => $team->getDefaultPlaybook()?->getName(),150+ ],151+'job' => $jobTitle?->getName(),152+'roles' => $roles,153+ ];154+155+return Response::structured($payload);156+ }157+}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20846 mcp enable the ai to know details about t</tabTitle>” with “<selection>@@ -0,0 +1,157 @@1+ $schema->string()35+ ->description(36+'UUID of the authenticated user. Use this as the authoritative identifier when filtering calls or deals by '37+ . 'the current user.'38+ )39+ ->required(),40+'name' => $schema->string()41+ ->description('Full display name of the user, e.g. \'John Doe\'.')42+ ->required(),43+'first_name' => $schema->string()44+ ->description(45+'First name of the user, derived from the full name field. Use for personalised greetings or addressing '46+ . 'the user directly.'47+ )48+ ->required(),49+'email' => $schema->string()50+ ->description('Primary email address of the user.')51+ ->required(),52+'timezone' => $schema->string()53+ ->description(54+'The user\'s timezone as an IANA timezone identifier, e.g. \'America/New_York\'. Use this when displaying '55+ . 'or reasoning about call times in the user\'s local time.'56+ )57+ ->required(),58+'spoken_languages' => $schema->array()59+ ->items($schema->string())60+ ->description(61+'Languages spoken during this user\'s calls, as an array of locale codes, e.g. [\'en_GB\', \'bg_BG\']. '62+ . 'The first entry is the primary/default language — used when language detection is unavailable. '63+ . 'Additional entries are other languages the user handles.'64+ )65+ ->required(),66+'sync_dialer' => $schema->boolean()67+ ->description(68+'True if the user has automatic syncing of softphone and outbound calls to their CRM enabled. When true, '69+ . 'calls made through the Jiminny dialler are automatically logged as CRM activities.'70+ )71+ ->required(),72+'sync_email' => $schema->boolean()73+ ->description(74+'True if the user has email syncing enabled. When true, customer email conversations are captured as '75+ . 'activities.'76+ )77+ ->required(),78+'sync_calendar' => $schema->boolean()79+ ->description(80+'True if the user has calendar syncing enabled. When true, scheduled meetings will be automatically '81+ . 'recorded.'82+ )83+ ->required(),84+'team' => $schema->object([85+'id' => $schema->string()86+ ->description(87+'Team UUID. Use this when filtering calls or deals by the user\'s team in search_calls or '88+ . 'search_deals.'89+ )90+ ->required(),91+'name' => $schema->string()92+ ->description('Team name, e.g. \'Account Executives\', \'Customer Success\'.')93+ ->required(),94+'playbook' => $schema->string()95+ ->description(96+'Name of the active playbook assigned to this team, e.g. \'Sales\', \'Customer Success\'. A playbook '97+ . 'defines the call evaluation criteria used for AI scoring. Null if no playbook is assigned.'98+ )99+ ->nullable()100+ ->required(),101+ ])102+ ->description('The team this user belongs to.')103+ ->required(),104+'job' => $schema->string()105+ ->description(106+'The user\'s job title, e.g. \'Software Engineer\', \'Account Executive\', \'Sales Manager\'. Null if no '107+ . 'job title has been assigned.'108+ )109+ ->nullable()110+ ->required(),111+'roles' => $schema->array()112+ ->items($schema->string())113+ ->description(114+'Roles assigned to the user, e.g. [\'admin\', \'recorder_and_voice\']. Determines what the user can do in '115+ . 'the platform. Common values: \'admin\' (full organisation admin), \'manager\' (team manager with '116+ . 'coaching access), \'recorder_and_voice\' (can record meetings and make calls), \'recorder\' (recording only).'117+ )118+ ->required(),119+ ];120+ }121+122+public function handle(Request $request): ResponseFactory123+ {124+$user = $request->user();125+assert($user instanceof User, 'auth:api + McpTierMiddleware must guarantee an authenticated User before the tool runs');126+127+$team = $user->team;128+$roles = $user->getRoles();129+$jobTitle = $user->job;130+131+$languageDialects = $user->languageDialects()->with('language')->get();132+$spokenLanguages = $languageDialects->isNotEmpty()133+ ? $languageDialects->map(fn ($dialect) => $dialect->getLanguageLocale())->toArray()134+ : [$user->getLanguage() ?? $team->getDefaultLanguage() ?? 'en'];135+136+$payload = [137+'id' => $user->getUuid(),138+'name' => $user->getName(),139+'first_name' => $user->getFirstName(),140+'email' => $user->getEmailAddress(),141+'timezone' => $user->getTimezone()->getName(),142+'spoken_languages' => $spokenLanguages,143+'sync_dialer' => $user->shouldSyncDialer(),144+'sync_email' => $user->isSyncEmailEnabled(),145+'sync_calendar' => $user->isSyncConferenceEnabled(),146+'team' => [147+'id' => $team->getUuid(),148+'name' => $team->getName(),149+'playbook' => $team->getDefaultPlaybook()?->getName(),150+ ],151+'job' => $jobTitle?->getName(),152+'roles' => $roles,153+ ];154+155+return Response::structured($payload);156+ }157+}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Listen
Gemini said
Gemini said
Code Summary: MCP "Get User Details" Tool Implementation
Code Summary: MCP "Get User Details" Tool Implementation
This code snippet defines the schema and execution logic for a Model Context Protocol (MCP) tool that exposes the authenticated user's profile details to an AI assistant.
Schema Definition
Schema Definition
The tool outputs a structured object containing the following required fields to guide the AI's contextual understanding and filtering capabilities:
User Identity:
*
id
: UUID used as the authoritative identifier for filtering calls or deals.
name
: Full display name (e.g., 'John Doe').
first_name
: Extracted first name, explicitly intended for personalized greetings.
email
: Primary email address.
job
: Job title (e.g., 'Account Executive'); nullable.
Preferences & Context:
timezone
: IANA identifier (e.g., 'America/New_York') for reasoning about local call times.
spoken_languages
: An array of locale codes. The first entry serves as the primary/default language.
roles
: Platform permissions determining capabilities (e.g.,
admin
,
manager
,
recorder_and_voice
).
Sync Settings:
Booleans indicating whether the user has automated CRM syncing enabled for:
sync_dialer
(Jiminny softphone calls)
sync_email
(Customer email conversations)
sync_calendar
(Scheduled meetings)
Team Context:
An object containing the team's
id
,
name
, and
playbook...
|
50600
|
NULL
|
NULL
|
NULL
|
|
22108
|
965
|
12
|
2026-05-12T06:49:27.559134+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-12/1778 /Users/lukas/.screenpipe/data/data/2026-05-12/1778568567559_m2.jpg...
|
Firefox
|
Platform Sprint 3 Q2 - Platform Team - Scrum Board Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira — Work...
|
1
|
jiminny.atlassian.net/jira/software/c/projects/JY/ jiminny.atlassian.net/jira/software/c/projects/JY/boards/37...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
New Tab
New Tab
Jy 20820 es reindex stream model h New Tab
New Tab
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
Pull requests · jiminny/app
Pull requests · jiminny/app
[JY-20773] User Pilot not receiving events on report generated - Jira
[JY-20773] User Pilot not receiving events on report generated - Jira
JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app
JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app
[JY-20776] Automated report - sentry - Jira
[JY-20776] Automated report - sentry - Jira
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.29986703,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.3131649,"top":0.06304868,"width":0.014960106,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app","depth":4,"bounds":{"left":0.29986703,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app","depth":5,"bounds":{"left":0.3131649,"top":0.09577015,"width":0.16888298,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira","depth":4,"bounds":{"left":0.29986703,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira","depth":5,"bounds":{"left":0.3131649,"top":0.12849163,"width":0.16140293,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app","depth":4,"bounds":{"left":0.29986703,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app","depth":5,"bounds":{"left":0.3131649,"top":0.16121309,"width":0.18816489,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.29986703,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.3131649,"top":0.19393456,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pull requests · jiminny/app","depth":4,"bounds":{"left":0.29986703,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests · jiminny/app","depth":5,"bounds":{"left":0.3131649,"top":0.22665602,"width":0.04537899,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20773] User Pilot not receiving events on report generated - Jira","depth":4,"bounds":{"left":0.29986703,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20773] User Pilot not receiving events on report generated - Jira","depth":5,"bounds":{"left":0.3131649,"top":0.25937748,"width":0.1200133,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app","depth":4,"bounds":{"left":0.29986703,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app","depth":5,"bounds":{"left":0.3131649,"top":0.29209897,"width":0.19331782,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20776] Automated report - sentry - Jira","depth":4,"bounds":{"left":0.29986703,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20776] Automated report - sentry - Jira","depth":5,"bounds":{"left":0.3131649,"top":0.32482043,"width":0.07646277,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"TypeError: League\\Flysystem\\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app","depth":4,"bounds":{"left":0.29986703,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"TypeError: League\\Flysystem\\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app","depth":5,"bounds":{"left":0.3131649,"top":0.3575419,"width":0.40475398,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"TypeError: League\\Flysystem\\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app","depth":4,"bounds":{"left":0.29986703,"top":0.3790902,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"TypeError: League\\Flysystem\\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app","depth":5,"bounds":{"left":0.3131649,"top":0.39026338,"width":0.40475398,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.29986703,"top":0.41181165,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.3131649,"top":0.42298484,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.3671875,"top":0.41899443,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.30269283,"top":0.4461293,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.30269283,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.31366357,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-9093742680405457588
|
-3011410393329092466
|
click
|
accessibility
|
NULL
|
New Tab
New Tab
Jy 20820 es reindex stream model h New Tab
New Tab
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
Pull requests · jiminny/app
Pull requests · jiminny/app
[JY-20773] User Pilot not receiving events on report generated - Jira
[JY-20773] User Pilot not receiving events on report generated - Jira
JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app
JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app
[JY-20776] Automated report - sentry - Jira
[JY-20776] Automated report - sentry - Jira
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45993
|
1646
|
32
|
2026-05-15T06:42:47.332052+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-15/1778 /Users/lukas/.screenpipe/data/data/2026-05-15/1778827367332_m1.jpg...
|
Firefox
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira — Work...
|
1
|
jiminny.atlassian.net/jira/software/c/projects/JY/ jiminny.atlassian.net/jira/software/c/projects/JY/boards/37?selectedIssue=JY-20903...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
[SRD-6853] Moxso - Potential deal stages bug - Jira
[SRD-6853] Moxso - Potential deal stages bug - Jira
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to:
Top Bar
Top Bar
Sidebar
Sidebar
Main Content
Main Content
Space navigation
Space navigation
Collapse sidebar [
Collapse sidebar [
Switch sites or apps
Switch sites or apps
Go to your Jira homepage
Search, press enter to navigate to advanced search with your text query
Create
Create
Rovo Ask Rovo
Ask Rovo
Notifications
Notifications
Help
Help
Settings
Settings
[EMAIL]
[EMAIL]
For you
For you
Recent
Recent
Starred
Starred
Apps
Apps
More actions for Apps
More actions for Apps
Spaces
Spaces
Create space
Create space
More actions for spaces
More actions for spaces
Recent
Jiminny (New)
Jiminny (New)
Jiminny (New)
Create board
Create board
More actions for Jiminny (New)
More actions for Jiminny (New)
Platform Team
Platform Team
Board actions
Board actions
Capture Team
Capture Team
Board actions
Board actions
Enterprise Stability Issues 🤕
Enterprise Stability Issues 🤕
Board actions
Board actions
Processing Team
Processing Team
Board actions
Board actions
SE Kanban
SE Kanban
Board actions
Board actions
Service-Desk
Service-Desk
More actions for Service-Desk
More actions for Service-Desk
More spaces
More spaces
Filters
Filters
More actions for Filters
More actions for Filters
Dashboards
Dashboards
Create dashboard
Create dashboard
More actions for Dashboards
More actions for Dashboards
Operations
Operations
More actions for Operations
More actions for Operations
Confluence , (opens new window)
Confluence
, (opens new window)
Teams , (opens new window)
Teams
, (opens new window)
open menu
open menu
Customise sidebar
Customise sidebar
Resize side navigation panel
Spaces
Spaces
/
Jiminny (New)
Jiminny (New)
Platform Team
Platform Team
Add people
Add people
Board actions
Board actions
Share
Automation
Give feedback
Give feedback
Enter full screen
Enter full screen
Summary
Summary
Timeline
Timeline
Backlog
Backlog
Active sprints
Active sprints
Calendar
Calendar
Reports
Reports
Testing Board
Testing Board
List
List
Forms
Forms
Components
Components
Development
Development
Code
Code
Security
Security
Releases
Releases
6 more tabs
More
6
Add to navigation
As you type to search or apply filters, the board updates with work items to match.
Search on current page
Filter by assignee
Filter assignees by Lukas Kovalik
Filter assignees by Aneliya Angelova
Filter assignees by Nikolay Ivanov
Filter assignees by Nikolay Nikolov
Filter assignees by Steliyan Georgiev
Filter assignees by Unassigned
Epic
Epic
Type
Type
Quick filters
Quick filters
Complete sprint
Complete sprint
Sprint details
Sprint details
Group by Queries
Group
: Queries
Sprint insights
Sprint insights
View settings
View settings
More actions
More actions
Ready To Be Merged (2 work items)
Ready To Be Merged
(2 work items)
Ready For DEV
READY FOR DEV
In DEV
IN DEV
Code Review
CODE REVIEW
Create work item in Code Review
Create
Blocked
BLOCKED
Create work item in Blocked
Create
QA
QA...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6853] Moxso - Potential deal stages bug - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6853] Moxso - Potential deal stages bug - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.0,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.009722223,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.032986112,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.05625,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to:","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Top Bar","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Top Bar","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sidebar","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sidebar","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Main Content","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Main Content","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Space navigation","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Space navigation","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Collapse sidebar [","depth":9,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Collapse sidebar [","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Switch sites or apps","depth":10,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Switch sites or apps","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Go to your Jira homepage","depth":9,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXComboBox","text":"Search, press enter to navigate to advanced search with your text query","depth":11,"on_screen":true,"help_text":"","placeholder":"Search","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Create","depth":10,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Rovo Ask Rovo","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Rovo","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Notifications","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notifications","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Help","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Help","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Settings","depth":12,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Settings","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"lukas.kovalik@jiminny.com","depth":12,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"lukas.kovalik@jiminny.com","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"For you","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"For you","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Recent","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Recent","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Starred","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Starred","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Apps","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Apps","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Apps","depth":13,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Apps","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Spaces","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"Spaces","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create space","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create space","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for spaces","depth":13,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for spaces","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Recent","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Jiminny (New)","depth":17,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny (New)","depth":20,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Jiminny (New)","depth":18,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXMenuButton","text":"Create board","depth":18,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Create board","depth":20,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Jiminny (New)","depth":18,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Jiminny (New)","depth":20,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Platform Team","depth":19,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Team","depth":22,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Capture Team","depth":19,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Capture Team","depth":22,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Enterprise Stability Issues 🤕","depth":19,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Enterprise Stability Issues 🤕","depth":22,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Processing Team","depth":19,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Processing Team","depth":22,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"SE Kanban","depth":19,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SE Kanban","depth":22,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Service-Desk","depth":17,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Service-Desk","depth":20,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Service-Desk","depth":18,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Service-Desk","depth":20,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"More spaces","depth":17,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More spaces","depth":20,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Filters","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Filters","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Filters","depth":13,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Filters","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Dashboards","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Dashboards","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create dashboard","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create dashboard","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Dashboards","depth":13,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Dashboards","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Operations","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Operations","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Operations","depth":13,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Operations","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Confluence , (opens new window)","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Confluence","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", (opens new window)","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Teams , (opens new window)","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Teams","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", (opens new window)","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"open menu","depth":14,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"open menu","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Customise sidebar","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Customise sidebar","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Resize side navigation panel","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Spaces","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Spaces","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Jiminny (New)","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny (New)","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Platform Team","depth":10,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Platform Team","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add people","depth":10,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add people","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":10,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Share","depth":10,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Automation","depth":10,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give feedback","depth":10,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Give feedback","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Enter full screen","depth":10,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Enter full screen","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Summary","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Summary","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Timeline","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Timeline","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Backlog","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Backlog","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Active sprints","depth":13,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Active sprints","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Calendar","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Calendar","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Reports","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Reports","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Testing Board","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Testing Board","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"List","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"List","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Forms","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Forms","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Components","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Components","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Development","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Development","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Releases","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Releases","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"6 more tabs","depth":11,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Add to navigation","depth":11,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"As you type to search or apply filters, the board updates with work items to match.","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"Search on current page","depth":11,"on_screen":true,"placeholder":"Search board","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Filter by assignee","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Filter assignees by Lukas Kovalik","depth":11,"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Filter assignees by Aneliya Angelova","depth":11,"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Filter assignees by Nikolay Ivanov","depth":11,"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Filter assignees by Nikolay Nikolov","depth":11,"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Filter assignees by Steliyan Georgiev","depth":11,"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Filter assignees by Unassigned","depth":11,"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Epic","depth":13,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Epic","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Type","depth":13,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Type","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Quick filters","depth":13,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Quick filters","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Complete sprint","depth":10,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Complete sprint","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Sprint details","depth":10,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Sprint details","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Group by Queries","depth":10,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Group","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": Queries","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Sprint insights","depth":10,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Sprint insights","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"View settings","depth":10,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"View settings","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions","depth":10,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Ready To Be Merged (2 work items)","depth":16,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"Ready To Be Merged","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(2 work items)","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Ready For DEV","depth":17,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"READY FOR DEV","depth":19,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"In DEV","depth":17,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"IN DEV","depth":19,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Code Review","depth":17,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CODE REVIEW","depth":19,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create work item in Code Review","depth":17,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create","depth":20,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Blocked","depth":17,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BLOCKED","depth":19,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create work item in Blocked","depth":17,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create","depth":20,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"QA","depth":17,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QA","depth":19,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-9093580863393456415
|
4779849643901571134
|
click
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
[SRD-6853] Moxso - Potential deal stages bug - Jira
[SRD-6853] Moxso - Potential deal stages bug - Jira
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to:
Top Bar
Top Bar
Sidebar
Sidebar
Main Content
Main Content
Space navigation
Space navigation
Collapse sidebar [
Collapse sidebar [
Switch sites or apps
Switch sites or apps
Go to your Jira homepage
Search, press enter to navigate to advanced search with your text query
Create
Create
Rovo Ask Rovo
Ask Rovo
Notifications
Notifications
Help
Help
Settings
Settings
[EMAIL]
[EMAIL]
For you
For you
Recent
Recent
Starred
Starred
Apps
Apps
More actions for Apps
More actions for Apps
Spaces
Spaces
Create space
Create space
More actions for spaces
More actions for spaces
Recent
Jiminny (New)
Jiminny (New)
Jiminny (New)
Create board
Create board
More actions for Jiminny (New)
More actions for Jiminny (New)
Platform Team
Platform Team
Board actions
Board actions
Capture Team
Capture Team
Board actions
Board actions
Enterprise Stability Issues 🤕
Enterprise Stability Issues 🤕
Board actions
Board actions
Processing Team
Processing Team
Board actions
Board actions
SE Kanban
SE Kanban
Board actions
Board actions
Service-Desk
Service-Desk
More actions for Service-Desk
More actions for Service-Desk
More spaces
More spaces
Filters
Filters
More actions for Filters
More actions for Filters
Dashboards
Dashboards
Create dashboard
Create dashboard
More actions for Dashboards
More actions for Dashboards
Operations
Operations
More actions for Operations
More actions for Operations
Confluence , (opens new window)
Confluence
, (opens new window)
Teams , (opens new window)
Teams
, (opens new window)
open menu
open menu
Customise sidebar
Customise sidebar
Resize side navigation panel
Spaces
Spaces
/
Jiminny (New)
Jiminny (New)
Platform Team
Platform Team
Add people
Add people
Board actions
Board actions
Share
Automation
Give feedback
Give feedback
Enter full screen
Enter full screen
Summary
Summary
Timeline
Timeline
Backlog
Backlog
Active sprints
Active sprints
Calendar
Calendar
Reports
Reports
Testing Board
Testing Board
List
List
Forms
Forms
Components
Components
Development
Development
Code
Code
Security
Security
Releases
Releases
6 more tabs
More
6
Add to navigation
As you type to search or apply filters, the board updates with work items to match.
Search on current page
Filter by assignee
Filter assignees by Lukas Kovalik
Filter assignees by Aneliya Angelova
Filter assignees by Nikolay Ivanov
Filter assignees by Nikolay Nikolov
Filter assignees by Steliyan Georgiev
Filter assignees by Unassigned
Epic
Epic
Type
Type
Quick filters
Quick filters
Complete sprint
Complete sprint
Sprint details
Sprint details
Group by Queries
Group
: Queries
Sprint insights
Sprint insights
View settings
View settings
More actions
More actions
Ready To Be Merged (2 work items)
Ready To Be Merged
(2 work items)
Ready For DEV
READY FOR DEV
In DEV
IN DEV
Code Review
CODE REVIEW
Create work item in Code Review
Create
Blocked
BLOCKED
Create work item in Blocked
Create
QA
QA...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45999
|
NULL
|
0
|
2026-05-15T06:43:03.612076+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-15/1778 /Users/lukas/.screenpipe/data/data/2026-05-15/1778827383612_m1.jpg...
|
Firefox
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira — Work...
|
1
|
jiminny.atlassian.net/jira/software/c/projects/JY/ jiminny.atlassian.net/jira/software/c/projects/JY/boards/37?selectedIssue=JY-20903...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
[SRD-6853] Moxso - Potential deal stages bug - Jira
[SRD-6853] Moxso - Potential deal stages bug - Jira
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to:
Top Bar
Top Bar
Sidebar
Sidebar
Main Content
Main Content
Space navigation
Space navigation
Collapse sidebar [
Collapse sidebar [
Switch sites or apps
Switch sites or apps
Go to your Jira homepage
Search, press enter to navigate to advanced search with your text query
Create
Create
Rovo Ask Rovo
Ask Rovo
Notifications
Notifications
Help
Help
Settings
Settings
[EMAIL]
[EMAIL]
For you
For you
Recent
Recent
Starred
Starred
Apps
Apps
More actions for Apps
More actions for Apps
Spaces
Spaces
Create space
Create space
More actions for spaces
More actions for spaces
Recent
Jiminny (New)
Jiminny (New)
Jiminny (New)
Create board
Create board
More actions for Jiminny (New)
More actions for Jiminny (New)
Platform Team
Platform Team
Board actions
Board actions
Capture Team
Capture Team
Board actions
Board actions
Enterprise Stability Issues 🤕
Enterprise Stability Issues 🤕
Board actions
Board actions
Processing Team
Processing Team
Board actions
Board actions
SE Kanban
SE Kanban
Board actions
Board actions
Service-Desk
Service-Desk
More actions for Service-Desk
More actions for Service-Desk
More spaces
More spaces
Filters
Filters
More actions for Filters
More actions for Filters
Dashboards
Dashboards
Create dashboard
Create dashboard
More actions for Dashboards
More actions for Dashboards
Operations
Operations
More actions for Operations
More actions for Operations
Confluence , (opens new window)
Confluence
, (opens new window)
Teams , (opens new window)
Teams
, (opens new window)
open menu
open menu
Customise sidebar
Customise sidebar
Resize side navigation panel
Spaces
Spaces
/
Jiminny (New)
Jiminny (New)
Platform Team
Platform Team
Add people
Add people
Board actions
Board actions
Share
Automation
Give feedback
Give feedback
Enter full screen
Enter full screen
Summary
Summary
Timeline
Timeline
Backlog
Backlog
Active sprints
Active sprints
Calendar
Calendar
Reports
Reports
Testing Board
Testing Board
List
List
Forms
Forms
Components
Components
Development
Development
Code
Code
Security
Security
Releases
Releases
6 more tabs
More
6
Add to navigation
As you type to search or apply filters, the board updates with work items to match.
Search on current page
Filter by assignee
Filter assignees by Lukas Kovalik
Filter assignees by Aneliya Angelova
Filter assignees by Nikolay Ivanov
Filter assignees by Nikolay Nikolov
Filter assignees by Steliyan Georgiev
Filter assignees by Unassigned
Epic
Epic
Type
Type
Quick filters
Quick filters
Complete sprint
Complete sprint
Sprint details
Sprint details
Group by Queries
Group
: Queries
Sprint insights
Sprint insights
View settings
View settings
More actions
More actions
Ready To Be Merged (2 work items)
Ready To Be Merged
(2 work items)
Ready For DEV
READY FOR DEV
In DEV
IN DEV
Code Review
CODE REVIEW
Create work item in Code Review
Create
Blocked
BLOCKED
Create work item in Blocked
Create
QA
QA...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6853] Moxso - Potential deal stages bug - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6853] Moxso - Potential deal stages bug - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.0,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.009722223,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.032986112,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.05625,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to:","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Top Bar","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Top Bar","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sidebar","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sidebar","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Main Content","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Main Content","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Space navigation","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Space navigation","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Collapse sidebar [","depth":9,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Collapse sidebar [","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Switch sites or apps","depth":10,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Switch sites or apps","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Go to your Jira homepage","depth":9,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXComboBox","text":"Search, press enter to navigate to advanced search with your text query","depth":11,"on_screen":true,"help_text":"","placeholder":"Search","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Create","depth":10,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Rovo Ask Rovo","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Rovo","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Notifications","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notifications","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Help","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Help","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Settings","depth":12,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Settings","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"lukas.kovalik@jiminny.com","depth":12,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"lukas.kovalik@jiminny.com","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"For you","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"For you","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Recent","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Recent","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Starred","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Starred","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Apps","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Apps","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Apps","depth":13,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Apps","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Spaces","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"Spaces","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create space","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create space","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for spaces","depth":13,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for spaces","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Recent","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Jiminny (New)","depth":17,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny (New)","depth":20,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Jiminny (New)","depth":18,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXMenuButton","text":"Create board","depth":18,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Create board","depth":20,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Jiminny (New)","depth":18,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Jiminny (New)","depth":20,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Platform Team","depth":19,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Team","depth":22,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Capture Team","depth":19,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Capture Team","depth":22,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Enterprise Stability Issues 🤕","depth":19,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Enterprise Stability Issues 🤕","depth":22,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Processing Team","depth":19,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Processing Team","depth":22,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"SE Kanban","depth":19,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SE Kanban","depth":22,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Service-Desk","depth":17,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Service-Desk","depth":20,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Service-Desk","depth":18,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Service-Desk","depth":20,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"More spaces","depth":17,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More spaces","depth":20,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Filters","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Filters","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Filters","depth":13,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Filters","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Dashboards","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Dashboards","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create dashboard","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create dashboard","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Dashboards","depth":13,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Dashboards","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Operations","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Operations","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Operations","depth":13,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Operations","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Confluence , (opens new window)","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Confluence","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", (opens new window)","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Teams , (opens new window)","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Teams","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", (opens new window)","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"open menu","depth":14,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"open menu","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Customise sidebar","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Customise sidebar","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Resize side navigation panel","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Spaces","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Spaces","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Jiminny (New)","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny (New)","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Platform Team","depth":10,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Platform Team","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add people","depth":10,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add people","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":10,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Share","depth":10,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Automation","depth":10,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give feedback","depth":10,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Give feedback","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Enter full screen","depth":10,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Enter full screen","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Summary","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Summary","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Timeline","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Timeline","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Backlog","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Backlog","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Active sprints","depth":13,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Active sprints","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Calendar","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Calendar","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Reports","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Reports","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Testing Board","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Testing Board","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"List","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"List","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Forms","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Forms","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Components","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Components","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Development","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Development","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Releases","depth":13,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Releases","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"6 more tabs","depth":11,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Add to navigation","depth":11,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"As you type to search or apply filters, the board updates with work items to match.","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"Search on current page","depth":11,"on_screen":true,"placeholder":"Search board","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Filter by assignee","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Filter assignees by Lukas Kovalik","depth":11,"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Filter assignees by Aneliya Angelova","depth":11,"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Filter assignees by Nikolay Ivanov","depth":11,"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Filter assignees by Nikolay Nikolov","depth":11,"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Filter assignees by Steliyan Georgiev","depth":11,"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Filter assignees by Unassigned","depth":11,"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Epic","depth":13,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Epic","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Type","depth":13,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Type","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Quick filters","depth":13,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Quick filters","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Complete sprint","depth":10,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Complete sprint","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Sprint details","depth":10,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Sprint details","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Group by Queries","depth":10,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Group","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": Queries","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Sprint insights","depth":10,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Sprint insights","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"View settings","depth":10,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"View settings","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions","depth":10,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Ready To Be Merged (2 work items)","depth":16,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"Ready To Be Merged","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(2 work items)","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Ready For DEV","depth":17,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"READY FOR DEV","depth":19,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"In DEV","depth":17,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"IN DEV","depth":19,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Code Review","depth":17,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CODE REVIEW","depth":19,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create work item in Code Review","depth":17,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create","depth":20,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Blocked","depth":17,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BLOCKED","depth":19,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create work item in Blocked","depth":17,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create","depth":20,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"QA","depth":17,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"QA","depth":19,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-9093580863393456415
|
4779849643901571134
|
click
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
[SRD-6853] Moxso - Potential deal stages bug - Jira
[SRD-6853] Moxso - Potential deal stages bug - Jira
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to:
Top Bar
Top Bar
Sidebar
Sidebar
Main Content
Main Content
Space navigation
Space navigation
Collapse sidebar [
Collapse sidebar [
Switch sites or apps
Switch sites or apps
Go to your Jira homepage
Search, press enter to navigate to advanced search with your text query
Create
Create
Rovo Ask Rovo
Ask Rovo
Notifications
Notifications
Help
Help
Settings
Settings
[EMAIL]
[EMAIL]
For you
For you
Recent
Recent
Starred
Starred
Apps
Apps
More actions for Apps
More actions for Apps
Spaces
Spaces
Create space
Create space
More actions for spaces
More actions for spaces
Recent
Jiminny (New)
Jiminny (New)
Jiminny (New)
Create board
Create board
More actions for Jiminny (New)
More actions for Jiminny (New)
Platform Team
Platform Team
Board actions
Board actions
Capture Team
Capture Team
Board actions
Board actions
Enterprise Stability Issues 🤕
Enterprise Stability Issues 🤕
Board actions
Board actions
Processing Team
Processing Team
Board actions
Board actions
SE Kanban
SE Kanban
Board actions
Board actions
Service-Desk
Service-Desk
More actions for Service-Desk
More actions for Service-Desk
More spaces
More spaces
Filters
Filters
More actions for Filters
More actions for Filters
Dashboards
Dashboards
Create dashboard
Create dashboard
More actions for Dashboards
More actions for Dashboards
Operations
Operations
More actions for Operations
More actions for Operations
Confluence , (opens new window)
Confluence
, (opens new window)
Teams , (opens new window)
Teams
, (opens new window)
open menu
open menu
Customise sidebar
Customise sidebar
Resize side navigation panel
Spaces
Spaces
/
Jiminny (New)
Jiminny (New)
Platform Team
Platform Team
Add people
Add people
Board actions
Board actions
Share
Automation
Give feedback
Give feedback
Enter full screen
Enter full screen
Summary
Summary
Timeline
Timeline
Backlog
Backlog
Active sprints
Active sprints
Calendar
Calendar
Reports
Reports
Testing Board
Testing Board
List
List
Forms
Forms
Components
Components
Development
Development
Code
Code
Security
Security
Releases
Releases
6 more tabs
More
6
Add to navigation
As you type to search or apply filters, the board updates with work items to match.
Search on current page
Filter by assignee
Filter assignees by Lukas Kovalik
Filter assignees by Aneliya Angelova
Filter assignees by Nikolay Ivanov
Filter assignees by Nikolay Nikolov
Filter assignees by Steliyan Georgiev
Filter assignees by Unassigned
Epic
Epic
Type
Type
Quick filters
Quick filters
Complete sprint
Complete sprint
Sprint details
Sprint details
Group by Queries
Group
: Queries
Sprint insights
Sprint insights
View settings
View settings
More actions
More actions
Ready To Be Merged (2 work items)
Ready To Be Merged
(2 work items)
Ready For DEV
READY FOR DEV
In DEV
IN DEV
Code Review
CODE REVIEW
Create work item in Code Review
Create
Blocked
BLOCKED
Create work item in Blocked
Create
QA
QA...
|
45997
|
NULL
|
NULL
|
NULL
|
|
51745
|
1818
|
10
|
2026-05-18T09:02:12.633022+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779094932633_m2.jpg...
|
iTerm2
|
NULL
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
DMActivityMoreSlackVIewJiminny... v# general#jimin DMActivityMoreSlackVIewJiminny... v# general#jiminny-bg# plattorm-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...ó Direct messagesa. Stefka Stoyano...FP. Nikolay YankovR?. Stoyan TomovVasil Vasiley®. Galya Dimitrovaf. Aneliya AngelovaCà Todor Stamatov m&. Mario Georgiev. Nikolay Ivanovdo James Graham 78. Stoyan Tanev E. Steliyan Georgiev. Petko KashinskiLukas Kovalik y...::: Aons€ Jira CloudMistonWindowhelt&. Stefka StoyanovaMessagesr FilesUntitledUntitledStefka Staunnaiако имXpedrive, 4eвиждам Key Date: Major v1 features arescheduled for sunsetting by July 2026.Today~Sterka stovanova 11:56 AMhtps:/iminny.aulassian.net/browse/skD-0о4о - това е олокирано, а сторито вспринта e готово?Jira CloudSidekick Sms issueSue SRok SMS ibsia CroudBlockedses Lukas Kovalik (you)As of today at 11:57 AMOpen in Jira*SummariseLukas Kovallk 11:58 AMзлрасти. те са лве стовитаедно е готово, деплоинато а второто е вhacklogMessage Stefka Stoyanova+ Aa €CiltereCB Dashboards@: OperationsI& Confluence:: Teams9= Customise sidebarB 40 0 1 Preparation for Rei... in 2h 58 meKey=SRD-6848&subProduct=jira-servicedeskSpaces / 1] Service-Desk / #f SRD-6848ImpactNonelRoot causeArrachments 5Q Search100% C/ 8• Mon 18 May 12:02:12S ASK ROVO 1® sô @in ao 202000.4 610pmgin ago 202004 455 png: 2026.0.900ngLinked work items** JY-20915 Add environment-specific email domains for text relay to prevent duplicate processingis caused by# JY-20891 Sidekick SMS issueActivityCommentsHistory Work logApprovalsAdd internal note / Reply to customerPro tip: press (M) to commentLukas Kovalik 3 days ago & Internal noteon another look it turned out that text relays are created on both environments US and EU. So it always fails.© • Edit • Delete+BACKLOGDEPLOYED V@=• Summarise 2 comments =Lukas Kovalik 5 days ago & Internal noteThe issue for Scott is related to his email address. It looks up the sender email exactly as it appears in the email's From header. Since Scott sentfrom rewardgateway.com but his Jiminny account uses edenred.com, the lookup returned null, triggering the REASON_CODE_SENDER_UNKNOWN exception.• Scott's Jiminny account email: [EMAIL]• Email he sent from: [EMAIL] the PR related to the ticket I will add support for secondary email in this flow.• Edit • Delete+ CreateBlockedv|DetailsAssigneeReporter@ Lukas KovalikStoyan TomovRequest Type# Report a bugKnowledge base# View related articlesPriority levelP2 Medium)Dev TeamOrganizationCanny LinksPlatform teamReward Gateway-EdenredOpen Canny Links> More fields Labels. Time trackina. Tvpe of InfoSec incident. Components(InfoSec). Client. Affected user...> Automation 4 Rule executions> featureOS $ Open featureOSIntercomShowing 1 out of 1 linked conversations& Scott de Zoeten.>> Sentry sll Linked IssuesCreated last weekUpdated 2 hours agoConfigure...
|
NULL
|
-9093435997640667340
|
NULL
|
visual_change
|
ocr
|
NULL
|
DMActivityMoreSlackVIewJiminny... v# general#jimin DMActivityMoreSlackVIewJiminny... v# general#jiminny-bg# plattorm-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...ó Direct messagesa. Stefka Stoyano...FP. Nikolay YankovR?. Stoyan TomovVasil Vasiley®. Galya Dimitrovaf. Aneliya AngelovaCà Todor Stamatov m&. Mario Georgiev. Nikolay Ivanovdo James Graham 78. Stoyan Tanev E. Steliyan Georgiev. Petko KashinskiLukas Kovalik y...::: Aons€ Jira CloudMistonWindowhelt&. Stefka StoyanovaMessagesr FilesUntitledUntitledStefka Staunnaiако имXpedrive, 4eвиждам Key Date: Major v1 features arescheduled for sunsetting by July 2026.Today~Sterka stovanova 11:56 AMhtps:/iminny.aulassian.net/browse/skD-0о4о - това е олокирано, а сторито вспринта e готово?Jira CloudSidekick Sms issueSue SRok SMS ibsia CroudBlockedses Lukas Kovalik (you)As of today at 11:57 AMOpen in Jira*SummariseLukas Kovallk 11:58 AMзлрасти. те са лве стовитаедно е готово, деплоинато а второто е вhacklogMessage Stefka Stoyanova+ Aa €CiltereCB Dashboards@: OperationsI& Confluence:: Teams9= Customise sidebarB 40 0 1 Preparation for Rei... in 2h 58 meKey=SRD-6848&subProduct=jira-servicedeskSpaces / 1] Service-Desk / #f SRD-6848ImpactNonelRoot causeArrachments 5Q Search100% C/ 8• Mon 18 May 12:02:12S ASK ROVO 1® sô @in ao 202000.4 610pmgin ago 202004 455 png: 2026.0.900ngLinked work items** JY-20915 Add environment-specific email domains for text relay to prevent duplicate processingis caused by# JY-20891 Sidekick SMS issueActivityCommentsHistory Work logApprovalsAdd internal note / Reply to customerPro tip: press (M) to commentLukas Kovalik 3 days ago & Internal noteon another look it turned out that text relays are created on both environments US and EU. So it always fails.© • Edit • Delete+BACKLOGDEPLOYED V@=• Summarise 2 comments =Lukas Kovalik 5 days ago & Internal noteThe issue for Scott is related to his email address. It looks up the sender email exactly as it appears in the email's From header. Since Scott sentfrom rewardgateway.com but his Jiminny account uses edenred.com, the lookup returned null, triggering the REASON_CODE_SENDER_UNKNOWN exception.• Scott's Jiminny account email: [EMAIL]• Email he sent from: [EMAIL] the PR related to the ticket I will add support for secondary email in this flow.• Edit • Delete+ CreateBlockedv|DetailsAssigneeReporter@ Lukas KovalikStoyan TomovRequest Type# Report a bugKnowledge base# View related articlesPriority levelP2 Medium)Dev TeamOrganizationCanny LinksPlatform teamReward Gateway-EdenredOpen Canny Links> More fields Labels. Time trackina. Tvpe of InfoSec incident. Components(InfoSec). Client. Affected user...> Automation 4 Rule executions> featureOS $ Open featureOSIntercomShowing 1 out of 1 linked conversations& Scott de Zoeten.>> Sentry sll Linked IssuesCreated last weekUpdated 2 hours agoConfigure...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
63020
|
2228
|
28
|
2026-05-20T12:11:22.753274+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-20/1779 /Users/lukas/.screenpipe/data/data/2026-05-20/1779279082753_m2.jpg...
|
Firefox
|
MAZANOKE | Online Image Optimizer That Runs Privat MAZANOKE | Online Image Optimizer That Runs Privately in Your Browser — Personal...
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
MAZANOKE | Online Image Optimizer That Runs Privat MAZANOKE | Online Image Optimizer That Runs Privately in Your Browser
images.lakylak.xyz
Inbox (2) - [EMAIL] - Gmail
Inbox (2) - [EMAIL] - Gmail
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
(190) TED LASSO BEST SCENES - PART 2. The Most Heartwarming Ted Lasso Show Moments. - YouTube
(190) TED LASSO BEST SCENES - PART 2. The Most Heartwarming Ted Lasso Show Moments. - YouTube
LinkedIn Login, Sign in | LinkedIn
LinkedIn Login, Sign in | LinkedIn
Ambulatory sheet - Paperless-ngx
Ambulatory sheet - Paperless-ngx
New Tab
New Tab
💬1 - Apple MacBook Pro 14" Space Black M5 Max | Laptop.bg - Технологията с теб
💬1 - Apple MacBook Pro 14" Space Black M5 Max | Laptop.bg - Технологията с теб
Apple Mac Studio M4 Max | Desktop.bg - Мощни PC Гейминг конфигурации, Персонални компютри на изплащане за игри и работа
Apple Mac Studio M4 Max | Desktop.bg - Мощни PC Гейминг конфигурации, Персонални компютри на изплащане за игри и работа
Welcome to authentik! - authentik
Welcome to authentik! - authentik
Inbox (2) - [EMAIL] - Gmail
Inbox (2) - [EMAIL] - Gmail
Photo - Google Photos
Photo - Google Photos
Завеждане на щета онлайн | Euroins
Завеждане на щета онлайн | Euroins
Google Maps
Google Maps
MAZANOKE | Online Image Optimizer That Runs Privately in Your Browser
MAZANOKE | Online Image Optimizer That Runs Privately in Your Browser
Close tab
New Tab
New Tab
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
AI Chat settings
Close...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"MAZANOKE | Online Image Optimizer That Runs Privately in Your Browser","depth":4,"bounds":{"left":0.07280585,"top":0.48443735,"width":0.07646277,"height":0.021548284},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"images.lakylak.xyz","depth":4,"bounds":{"left":0.07280585,"top":0.5059856,"width":0.032579787,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Inbox (2) - kovaliklukas@gmail.com - Gmail","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox (2) - kovaliklukas@gmail.com - Gmail","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.07513298,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"DXP4800PLUS-B5F8","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"DXP4800PLUS-B5F8","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.036901597,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"(190) TED LASSO BEST SCENES - PART 2. The Most Heartwarming Ted Lasso Show Moments. - YouTube","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"(190) TED LASSO BEST SCENES - PART 2. The Most Heartwarming Ted Lasso Show Moments. - YouTube","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.18284574,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"LinkedIn Login, Sign in | LinkedIn","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LinkedIn Login, Sign in | LinkedIn","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.05668218,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Ambulatory sheet - Paperless-ngx","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ambulatory sheet - Paperless-ngx","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.059507977,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.014960106,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"💬1 - Apple MacBook Pro 14\" Space Black M5 Max | Laptop.bg - Технологията с теб","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"💬1 - Apple MacBook Pro 14\" Space Black M5 Max | Laptop.bg - Технологията с теб","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.14727394,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Apple Mac Studio M4 Max | Desktop.bg - Мощни PC Гейминг конфигурации, Персонални компютри на изплащане за игри и работа","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Apple Mac Studio M4 Max | Desktop.bg - Мощни PC Гейминг конфигурации, Персонални компютри на изплащане за игри и работа","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.2365359,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Welcome to authentik! - authentik","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Welcome to authentik! - authentik","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.05900931,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Inbox (2) - kovaliklukas@gmail.com - Gmail","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox (2) - kovaliklukas@gmail.com - Gmail","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.07513298,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Photo - Google Photos","depth":4,"bounds":{"left":0.0,"top":0.3790902,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Photo - Google Photos","depth":5,"bounds":{"left":0.013297873,"top":0.39026338,"width":0.03956117,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Завеждане на щета онлайн | Euroins","depth":4,"bounds":{"left":0.0,"top":0.41181165,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Завеждане на щета онлайн | Euroins","depth":5,"bounds":{"left":0.013297873,"top":0.42298484,"width":0.0653258,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Google Maps","depth":4,"bounds":{"left":0.0,"top":0.4445331,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Google Maps","depth":5,"bounds":{"left":0.013297873,"top":0.4557063,"width":0.022772606,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"MAZANOKE | Online Image Optimizer That Runs Privately in Your Browser","depth":4,"bounds":{"left":0.0,"top":0.4772546,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"MAZANOKE | Online Image Optimizer That Runs Privately in Your Browser","depth":5,"bounds":{"left":0.013297873,"top":0.4884278,"width":0.12749335,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.05651596,"top":0.48443735,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.509976,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.013297873,"top":0.5211492,"width":0.014960106,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.5442937,"width":0.06333112,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bitwarden","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.21941489,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.23138298,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-9093242489951770621
|
8060330839828045483
|
click
|
accessibility
|
NULL
|
MAZANOKE | Online Image Optimizer That Runs Privat MAZANOKE | Online Image Optimizer That Runs Privately in Your Browser
images.lakylak.xyz
Inbox (2) - [EMAIL] - Gmail
Inbox (2) - [EMAIL] - Gmail
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
(190) TED LASSO BEST SCENES - PART 2. The Most Heartwarming Ted Lasso Show Moments. - YouTube
(190) TED LASSO BEST SCENES - PART 2. The Most Heartwarming Ted Lasso Show Moments. - YouTube
LinkedIn Login, Sign in | LinkedIn
LinkedIn Login, Sign in | LinkedIn
Ambulatory sheet - Paperless-ngx
Ambulatory sheet - Paperless-ngx
New Tab
New Tab
💬1 - Apple MacBook Pro 14" Space Black M5 Max | Laptop.bg - Технологията с теб
💬1 - Apple MacBook Pro 14" Space Black M5 Max | Laptop.bg - Технологията с теб
Apple Mac Studio M4 Max | Desktop.bg - Мощни PC Гейминг конфигурации, Персонални компютри на изплащане за игри и работа
Apple Mac Studio M4 Max | Desktop.bg - Мощни PC Гейминг конфигурации, Персонални компютри на изплащане за игри и работа
Welcome to authentik! - authentik
Welcome to authentik! - authentik
Inbox (2) - [EMAIL] - Gmail
Inbox (2) - [EMAIL] - Gmail
Photo - Google Photos
Photo - Google Photos
Завеждане на щета онлайн | Euroins
Завеждане на щета онлайн | Euroins
Google Maps
Google Maps
MAZANOKE | Online Image Optimizer That Runs Privately in Your Browser
MAZANOKE | Online Image Optimizer That Runs Privately in Your Browser
Close tab
New Tab
New Tab
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
AI Chat settings
Close...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
42931
|
1572
|
10
|
2026-05-14T12:12:29.423742+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778760749423_m2.jpg...
|
Firefox
|
JY-20903 Update activity stage when opportunity is JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12077
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Unnamed Group
Platform Sprint 4 Q2 - Platform Team Unnamed Group
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
[SRD-6849] Recorded call does not appear on the dashboard - Jira
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Usage | Windsurf
Usage | Windsurf
Unnamed Group
[SRD-6848] Sidekick SMS issue - Jira
[SRD-6848] Sidekick SMS issue - Jira
[JY-20891] Sidekick SMS issue - Jira
[JY-20891] Sidekick SMS issue - Jira
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
Unnamed Group
[SRD-6849] Recorded call does not appear on the dashboard - Jira
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Jiminny
Jiminny
Jiminny
Jiminny
Jiminny
Jiminny
Unnamed Group
[SRD-6853] Moxso - Potential deal stages bug - Jira
[SRD-6853] Moxso - Potential deal stages bug - Jira
[JY-20903] Recorded call does not appear on the dashboard - Jira
[JY-20903] Recorded call does not appear on the dashboard - Jira
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
Useful commands - Engineering - Confluence
Useful commands - Engineering - Confluence
Dev Tools - Elastic
Dev Tools - Elastic
Jiminny
Jiminny
CloudWatch | eu-west-1
CloudWatch | eu-west-1
CloudWatch | eu-west-1
CloudWatch | eu-west-1
CloudWatch | eu-west-1
CloudWatch | eu-west-1
Unnamed Group
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
Pipedrive Developers Corner | Pipedrive API sandbox
Pipedrive Developers Corner | Pipedrive API sandbox
daniti/pipedrive-laravel: A Pipedrive App boilerplate with Laravel
daniti/pipedrive-laravel: A Pipedrive App boilerplate with Laravel
pipedrive/client-php: Pipedrive API client for PHP
pipedrive/client-php: Pipedrive API client for PHP
IsraelOrtuno/pipedrive: Complete Pipedrive API client for PHP
IsraelOrtuno/pipedrive: Complete Pipedrive API client for PHP
Code search results
Code search results
Jiminny
Jiminny
[JY-20903] Recorded call does not appear on the dashboard - Jira
[JY-20903] Recorded call does not appear on the dashboard - Jira
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
AI Chat settings
Close
WORK, Google Account: [EMAIL]
Main menu
New Chat
Open menu for conversation actions.
Conversation with Gemini
Conversation with Gemini
Copy prompt
You said I’m on page “<tabTitle>JY-18091 | Update composer to support php 8.5 by n</tabTitle>” with “<selection>@@ -40,15 +40,15 @@ public function postUsers(Request $request): JsonResponse40 {40 {41$team = $this->getTeamFromRequest($request);41$team = $this->getTeamFromRequest($request);424243-$requestDTO = (new CoreUserRequest())->createFromRequestPayload($request);43+$requestDTO = new CoreUserRequest()->createFromRequestPayload($request);444445if ($requestDTO->getPrimaryPhoneNumber() !== null) {45if ($requestDTO->getPrimaryPhoneNumber() !== null) {46$user = $this->userRepository->findByPhone(46$user = $this->userRepository->findByPhone(47 phone: $requestDTO->getPrimaryPhoneNumber(),47 phone: $requestDTO->getPrimaryPhoneNumber(),48 );48 );494950if ($user !== null && $user->getEmailAddress() !== $requestDTO->getEmailPrimaryWork()) {50if ($user !== null && $user->getEmailAddress() !== $requestDTO->getEmailPrimaryWork()) {51-return (new Error(Constants::HTTP_BAD_REQUEST, 'User primary phone already exists'))51+return new Error(Constants::HTTP_BAD_REQUEST, 'User primary phone already exists')52 ->getResponse();52 ->getResponse();53 }53 }54 }54 }@@ -59,7 +59,7 @@ public function postUsers(Request $request): JsonResponse59 );59 );606061if ($user !== null && $user->getEmailAddress() !== $requestDTO->getEmailPrimaryWork()) {61if ($user !== null && $user->getEmailAddress() !== $requestDTO->getEmailPrimaryWork()) {62-return (new Error(Constants::HTTP_BAD_REQUEST, 'User secondary phone already exists'))62+return new Error(Constants::HTTP_BAD_REQUEST, 'User secondary phone already exists')63 ->getResponse();63 ->getResponse();64 }64 }65 }65 }@@ -74,11 +74,11 @@ public function postUsers(Request $request): JsonResponse74$newUser = $this->scimProvisioning->createOrUpdateNewUser($requestDTO, $team);74$newUser = $this->scimProvisioning->createOrUpdateNewUser($requestDTO, $team);757576if (! $newUser instanceof User) {76if (! $newUser instanceof User) {77-return (new Error(Constants::HTTP_BAD_REQUEST, 'User create operation failed'))77+return new Error(Constants::HTTP_BAD_REQUEST, 'User create operation failed')78 ->getResponse();78 ->getResponse();79 }79 }808081-return (new CoreUser())81+return new CoreUser()82 ->createFromUser($newUser)82 ->createFromUser($newUser)83 ->setExternalId($requestDTO->getUserName())83 ->setExternalId($requestDTO->getUserName())84 ->setHttpCode(Constants::HTTP_CREATED)84 ->setHttpCode(Constants::HTTP_CREATED)@@ -93,22 +93,22 @@ public function putUsers(Request $request, string $id): JsonResponse93$team = $this->getTeamFromRequest($request);93$team = $this->getTeamFromRequest($request);949495if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {95if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {96-return (new Error())->getResponse();96+return new Error()->getResponse();97 }97 }989899-$requestDTO = (new CoreUserRequest())->createFromRequestPayload($request);99+$requestDTO = new CoreUserRequest()->createFromRequestPayload($request);100100101try {101try {102$user = $this->scimProvisioning->updateUserAttributesIfChanged(User::uuid($id), $requestDTO, $team);102$user = $this->scimProvisioning->updateUserAttributesIfChanged(User::uuid($id), $requestDTO, $team);103103104-return (new CoreUser())104+return new CoreUser()105 ->createFromUser($user)105 ->createFromUser($user)106 ->setHttpCode(Constants::HTTP_OK)106 ->setHttpCode(Constants::HTTP_OK)107 ->getResponse();107 ->getResponse();108 } catch (Exception $exception) {108 } catch (Exception $exception) {109 Sentry::captureException($exception);109 Sentry::captureException($exception);110110111-return (new Error(Constants::HTTP_ERROR, 'Operation failed'))->getResponse();111+return new Error(Constants::HTTP_ERROR, 'Operation failed')->getResponse();112 }112 }113 }113 }114114@@ -117,7 +117,7 @@ public function deleteUser(Request $request, string $id): JsonResponse117$team = $this->getTeamFromRequest($request);117$team = $this->getTeamFromRequest($request);118118119if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {119if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {120-return (new Error())->getResponse();120+return new Error()->getResponse();121 }121 }122122123$user = $this->scimProvisioning->getUserById($id);123$user = $this->scimProvisioning->getUserById($id);@@ -131,18 +131,18 @@ public function deleteUser(Request $request, string $id): JsonResponse131 ),131 ),132 );132 );133133134-return (new JsonResponse())->setStatusCode(204);134+return new JsonResponse()->setStatusCode(204);135 }135 }136136137public function patchUser(Request $request, string $id): JsonResponse137public function patchUser(Request $request, string $id): JsonResponse138 {138 {139$team = $this->getTeamFromRequest($request);139$team = $this->getTeamFromRequest($request);140140141if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {141if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {142-return (new Error())->getResponse();142+return new Error()->getResponse();143 }143 }144144145-$requestDTO = (new PatchUser())145+$requestDTO = new PatchUser()146 ->setResourceLocalId($id)146 ->setResourceLocalId($id)147 ->createFromRequestPayload($request);147 ->createFromRequestPayload($request);148148@@ -153,10 +153,10 @@ public function patchUser(Request $request, string $id): JsonResponse153 } catch (Exception $e) {153 } catch (Exception $e) {154 Sentry::captureException($e);154 Sentry::captureException($e);155155156-return (new Error(Constants::HTTP_BAD_REQUEST, 'Operation failed'))->getResponse();156+return new Error(Constants::HTTP_BAD_REQUEST, 'Operation failed')->getResponse();157 }157 }158158159-return (new CoreUser())159+return new CoreUser()160 ->createFromUser($user)160 ->createFromUser($user)161 ->setHttpCode(Constants::HTTP_OK)161 ->setHttpCode(Constants::HTTP_OK)162 ->getResponse();162 ->getResponse();@@ -167,16 +167,16 @@ public function getUser(Request $request, string $id): JsonResponse167$team = $this->getTeamFromRequest($request);167$team = $this->getTeamFromRequest($request);168168169if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {169if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {170-return (new Error())->getResponse();170+return new Error()->getResponse();171 }171 }172172173$user = $this->scimProvisioning->getUserById($id);173$user = $this->scimProvisioning->getUserById($id);174174175if (! $team->isSame($user->getTeam())) {175if (! $team->isSame($user->getTeam())) {176-return (new Error(Constants::HTTP_ACCESS_DENIED, 'Context does not match.'))->getResponse();176+return new Error(Constants::HTTP_ACCESS_DENIED, 'Context does not match.')->getResponse();177 }177 }178178179-return (new CoreUser())179+return new CoreUser()180 ->setHttpCode(Constants::HTTP_OK)180 ->setHttpCode(Constants::HTTP_OK)181 ->createFromUser($user)181 ->createFromUser($user)182 ->getResponse();182 ->getResponse();@@ -186,20 +186,20 @@ public function getUsersByQuery(Request $request): JsonResponse186 {186 {187$team = $this->getTeamFromRequest($request);187$team = $this->getTeamFromRequest($request);188188189-$requestDto = (new GetUserByQuery())->createFromRequestPayload($request);189+$requestDto = new GetUserByQuery()->createFromRequestPayload($request);190190191try {191try {192$resultsQuery = $this->scimProvisioning192$resultsQuery = $this->scimProvisioning193 ->translateQueryStringsToUserModelQuery($requestDto, $team);193 ->translateQueryStringsToUserModelQuery($requestDto, $team);194 } catch (Exception $exception) {194 } catch (Exception $exception) {195-return (new ListResponse())->createEmpty()->getResponse();195+return new ListResponse()->createEmpty()->getResponse();196 }196 }197197198$resultsQuery198$resultsQuery199 ->limit($requestDto->getCount())199 ->limit($requestDto->getCount())200 ->offset($requestDto->getStartIndex() - 1);200 ->offset($requestDto->getStartIndex() - 1);201201202-return (new ListResponse())202+return new ListResponse()203 ->setHttpCode(Constants::HTTP_OK)203 ->setHttpCode(Constants::HTTP_OK)204 ->setStartIndex($requestDto->getStartIndex())204 ->setStartIndex($requestDto->getStartIndex())205 ->createFromCollection($resultsQuery->get())205 ->createFromCollection($resultsQuery->get())@@ -210,15 +210,15 @@ public function postGroups(Request $request): JsonResponse210 {210 {211$team = $this->getTeamFromRequest($request);211$team = $this->getTeamFromRequest($request);212212213-$requestDto = (new CoreGroupRequest())->createFromRequestPayload($request);213+$requestDto = new CoreGroupRequest()->createFromRequestPayload($request);214214215$group = $this->scimProvisioning->createNewGroup($requestDto, $team);215$group = $this->scimProvisioning->createNewGroup($requestDto, $team);216216217if (! $group instanceof Group) {217if (! $group instanceof Group) {218-return (new Error(Constants::HTTP_BAD_REQUEST, 'Group resource create failed'))->getResponse();218+return new Error(Constants::HTTP_BAD_REQUEST, 'Group resource create failed')->getResponse();219 }219 }220220221-$response = (new CoreGroup())->createFromGroup($group)->setHttpCode(Constants::HTTP_CREATED)->getResponse();221+$response = new CoreGroup()->createFromGroup($group)->setHttpCode(Constants::HTTP_CREATED)->getResponse();222222223return $response;223return $response;224 }224 }@@ -228,22 +228,22 @@ public function putGroups(Request $request, string $id): JsonResponse228$team = $this->getTeamFromRequest($request);228$team = $this->getTeamFromRequest($request);229229230if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {230if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {231-return (new Error())->getResponse();231+return new Error()->getResponse();232 }232 }233233234-$requestDTO = (new CoreGroupRequest())->createFromRequestPayload($request);234+$requestDTO = new CoreGroupRequest()->createFromRequestPayload($request);235235236try {236try {237$group = $this->scimProvisioning->updateGroupAttributesIfChanged(Group::uuid($id), $requestDTO, $team);237$group = $this->scimProvisioning->updateGroupAttributesIfChanged(Group::uuid($id), $requestDTO, $team);238238239-return (new CoreGroup())239+return new CoreGroup()240 ->createFromGroup($group)240 ->createFromGroup($group)241 ->setHttpCode(Constants::HTTP_OK)241 ->setHttpCode(Constants::HTTP_OK)242 ->getResponse();242 ->getResponse();243 } catch (Exception $exception) {243 } catch (Exception $exception) {244 Sentry::captureException($exception);244 Sentry::captureException($exception);245245246-return (new Error(Constants::HTTP_ERROR, 'Operation failed'))->getResponse();246+return new Error(Constants::HTTP_ERROR, 'Operation failed')->getResponse();247 }247 }248 }248 }249249@@ -252,12 +252,12 @@ public function getGroup(Request $request, string $id): JsonResponse252$team = $this->getTeamFromRequest($request);252$team = $this->getTeamFromRequest($request);253253254if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {254if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {255-return (new Error())->getResponse();255+return new Error()->getResponse();256 }256 }257257258$group = $this->scimProvisioning->getGroupById($id);258$group = $this->scimProvisioning->getGroupById($id);259259260-return (new CoreGroup())260+return new CoreGroup()261 ->excludeAttributes($request)261 ->excludeAttributes($request)262 ->setHttpCode(Constants::HTTP_OK)262 ->setHttpCode(Constants::HTTP_OK)263 ->createFromGroup($group)263 ->createFromGroup($group)@@ -268,19 +268,19 @@ public function getGroupByQuery(Request $request): JsonResponse268 {268 {269$team = $this->getTeamFromRequest($request);269$team = $this->getTeamFromRequest($request);270270271-$dto = (new GetGroupByQuery())->createFromRequestPayload($request);271+$dto = new GetGroupByQuery()->createFromRequestPayload($request);272272273try {273try {274$groupsQuery = $this->scimProvisioning->translateQueryStringsToGroupModelQuery($dto, $team);274$groupsQuery = $this->scimProvisioning->translateQueryStringsToGroupModelQuery($dto, $team);275 } catch (Exception $exception) {275 } catch (Exception $exception) {276-return (new ListResponse())->createEmpty()->getResponse();276+return new ListResponse()->createEmpty()->getResponse();277 }277 }278278279$groupsQuery279$groupsQuery280 ->limit($dto->getCount())280 ->limit($dto->getCount())281 ->offset($dto->getStartIndex() - 1);281 ->offset($dto->getStartIndex() - 1);282282283-return (new ListResponse())283+return new ListResponse()284 ->setHttpCode(Constants::HTTP_OK)284 ->setHttpCode(Constants::HTTP_OK)285 ->setStartIndex($dto->getStartIndex())285 ->setStartIndex($dto->getStartIndex())286 ->createFromCollection($groupsQuery->get(), $dto->getExcludeAttributes())286 ->createFromCollection($groupsQuery->get(), $dto->getExcludeAttributes())@@ -292,30 +292,30 @@ public function patchGroup(Request $request, $id): JsonResponse292$team = $this->getTeamFromRequest($request);292$team = $this->getTeamFromRequest($request);293293294if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {294if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {295-return (new Error())->getResponse();295+return new Error()->getResponse();296 }296 }297297298-$dto = (new PatchGroup())->createFromRequestPayload($request);298+$dto = new PatchGroup()->createFromRequestPayload($request);299299300$group = Group::uuid($id);300$group = Group::uuid($id);301301302$this->scimProvisioning->patchGroupOperation($dto, $team, $group);302$this->scimProvisioning->patchGroupOperation($dto, $team, $group);303303304-return (new JsonResponse())->setStatusCode(Constants::HTTP_NO_CONTENT);304+return new JsonResponse()->setStatusCode(Constants::HTTP_NO_CONTENT);305 }305 }306306307public function deleteGroup(Request $request, $id): JsonResponse307public function deleteGroup(Request $request, $id): JsonResponse308 {308 {309$team = $this->getTeamFromRequest($request);309$team = $this->getTeamFromRequest($request);310310311if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {311if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {312-return (new Error())->getResponse();312+return new Error()->getResponse();313 }313 }314314315if ($this->scimProvisioning->deleteGroup(Group::uuid($id))) {315if ($this->scimProvisioning->deleteGroup(Group::uuid($id))) {316-return (new JsonResponse())->setStatusCode(Constants::HTTP_NO_CONTENT);316+return new JsonResponse()->setStatusCode(Constants::HTTP_NO_CONTENT);317 } else {317 } else {318-return (new Error(Constants::HTTP_ERROR, 'Operation failed'))->getResponse();318+return new Error(Constants::HTTP_ERROR, 'Operation failed')->getResponse();319 }319 }320 }320 }321321</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>JY-18091 | Update composer to support php 8.5 by n</tabTitle>” with “<selection>@@ -40,15 +40,15 @@ public function postUsers(Request $request): JsonResponse40 {40 {41$team = $this->getTeamFromRequest($request);41$team = $this->getTeamFromRequest($request);424243-$requestDTO = (new CoreUserRequest())->createFromRequestPayload($request);43+$requestDTO = new CoreUserRequest()->createFromRequestPayload($request);444445if ($requestDTO->getPrimaryPhoneNumber() !== null) {45if ($requestDTO->getPrimaryPhoneNumber() !== null) {46$user = $this->userRepository->findByPhone(46$user = $this->userRepository->findByPhone(47 phone: $requestDTO->getPrimaryPhoneNumber(),47 phone: $requestDTO->getPrimaryPhoneNumber(),48 );48 );494950if ($user !== null && $user->getEmailAddress() !== $requestDTO->getEmailPrimaryWork()) {50if ($user !== null && $user->getEmailAddress() !== $requestDTO->getEmailPrimaryWork()) {51-return (new Error(Constants::HTTP_BAD_REQUEST, 'User primary phone already exists'))51+return new Error(Constants::HTTP_BAD_REQUEST, 'User primary phone already exists')52 ->getResponse();52 ->getResponse();53 }53 }54 }54 }@@ -59,7 +59,7 @@ public function postUsers(Request $request): JsonResponse59 );59 );606061if ($user !== null && $user->getEmailAddress() !== $requestDTO->getEmailPrimaryWork()) {61if ($user !== null && $user->getEmailAddress() !== $requestDTO->getEmailPrimaryWork()) {62-return (new Error(Constants::HTTP_BAD_REQUEST, 'User secondary phone already exists'))62+return new Error(Constants::HTTP_BAD_REQUEST, 'User secondary phone already exists')63 ->getResponse();63 ->getResponse();64 }64 }65 }65 }@@ -74,11 +74,11 @@ public function postUsers(Request $request): JsonResponse74$newUser = $this->scimProvisioning->createOrUpdateNewUser($requestDTO, $team);74$newUser = $this->scimProvisioning->createOrUpdateNewUser($requestDTO, $team);757576if (! $newUser instanceof User) {76if (! $newUser instanceof User) {77-return (new Error(Constants::HTTP_BAD_REQUEST, 'User create operation failed'))77+return new Error(Constants::HTTP_BAD_REQUEST, 'User create operation failed')78 ->getResponse();78 ->getResponse();79 }79 }808081-return (new CoreUser())81+return new CoreUser()82 ->createFromUser($newUser)82 ->createFromUser($newUser)83 ->setExternalId($requestDTO->getUserName())83 ->setExternalId($requestDTO->getUserName())84 ->setHttpCode(Constants::HTTP_CREATED)84 ->setHttpCode(Constants::HTTP_CREATED)@@ -93,22 +93,22 @@ public function putUsers(Request $request, string $id): JsonResponse93$team = $this->getTeamFromRequest($request);93$team = $this->getTeamFromRequest($request);949495if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {95if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {96-return (new Error())->getResponse();96+return new Error()->getResponse();97 }97 }989899-$requestDTO = (new CoreUserRequest())->createFromRequestPayload($request);99+$requestDTO = new CoreUserRequest()->createFromRequestPayload($request);100100101try {101try {102$user = $this->scimProvisioning->updateUserAttributesIfChanged(User::uuid($id), $requestDTO, $team);102$user = $this->scimProvisioning->updateUserAttributesIfChanged(User::uuid($id), $requestDTO, $team);103103104-return (new CoreUser())104+return new CoreUser()105 ->createFromUser($user)105 ->createFromUser($user)106 ->setHttpCode(Constants::HTTP_OK)106 ->setHttpCode(Constants::HTTP_OK)107 ->getResponse();107 ->getResponse();108 } catch (Exception $exception) {108 } catch (Exception $exception) {109 Sentry::captureException($exception);109 Sentry::captureException($exception);110110111-return (new Error(Constants::HTTP_ERROR, 'Operation failed'))->getResponse();111+return new Error(Constants::HTTP_ERROR, 'Operation failed')->getResponse();112 }112 }113 }113 }114114@@ -117,7 +117,7 @@ public function deleteUser(Request $request, string $id): JsonResponse117$team = $this->getTeamFromRequest($request);117$team = $this->getTeamFromRequest($request);118118119if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {119if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {120-return (new Error())->getResponse();120+return new Error()->getResponse();121 }121 }122122123$user = $this->scimProvisioning->getUserById($id);123$user = $this->scimProvisioning->getUserById($id);@@ -131,18 +131,18 @@ public function deleteUser(Request $request, string $id): JsonResponse131 ),131 ),132 );132 );133133134-return (new JsonResponse())->setStatusCode(204);134+return new JsonResponse()->setStatusCode(204);135 }135 }136136137public function patchUser(Request $request, string $id): JsonResponse137public function patchUser(Request $request, string $id): JsonResponse138 {138 {139$team = $this->getTeamFromRequest($request);139$team = $this->getTeamFromRequest($request);140140141if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {141if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {142-return (new Error())->getResponse();142+return new Error()->getResponse();143 }143 }144144145-$requestDTO = (new PatchUser())145+$requestDTO = new PatchUser()146 ->setResourceLocalId($id)146 ->setResourceLocalId($id)147 ->createFromRequestPayload($request);147 ->createFromRequestPayload($request);148148@@ -153,10 +153,10 @@ public function patchUser(Request $request, string $id): JsonResponse153 } catch (Exception $e) {153 } catch (Exception $e) {154 Sentry::captureException($e);154 Sentry::captureException($e);155155156-return (new Error(Constants::HTTP_BAD_REQUEST, 'Operation failed'))->getResponse();156+return new Error(Constants::HTTP_BAD_REQUEST, 'Operation failed')->getResponse();157 }157 }158158159-return (new CoreUser())159+return new CoreUser()160 ->createFromUser($user)160 ->createFromUser($user)161 ->setHttpCode(Constants::HTTP_OK)161 ->setHttpCode(Constants::HTTP_OK)162 ->getResponse();162 ->getResponse();@@ -167,16 +167,16 @@ public function getUser(Request $request, string $id): JsonResponse167$team = $this->getTeamFromRequest($request);167$team = $this->getTeamFromRequest($request);168168169if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {169if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {170-return (new Error())->getResponse();170+return new Error()->getResponse();171 }171 }172172173$user = $this->scimProvisioning->getUserById($id);173$user = $this->scimProvisioning->getUserById($id);174174175if (! $team->isSame($user->getTeam())) {175if (! $team->isSame($user->getTeam())) {176-return (new Error(Constants::HTTP_ACCESS_DENIED, 'Context does not match.'))->getResponse();176+return new Error(Constants::HTTP_ACCESS_DENIED, 'Context does not match.')->getResponse();177 }177 }178178179-return (new CoreUser())179+return new CoreUser()180 ->setHttpCode(Constants::HTTP_OK)180 ->setHttpCode(Constants::HTTP_OK)181 ->createFromUser($user)181 ->createFromUser($user)182 ->getResponse();182 ->getResponse();@@ -186,20 +186,20 @@ public function getUsersByQuery(Request $request): JsonResponse186 {186 {187$team = $this->getTeamFromRequest($request);187$team = $this->getTeamFromRequest($request);188188189-$requestDto = (new GetUserByQuery())->createFromRequestPayload($request);189+$requestDto = new GetUserByQuery()->createFromRequestPayload($request);190190191try {191try {192$resultsQuery = $this->scimProvisioning192$resultsQuery = $this->scimProvisioning193 ->translateQueryStringsToUserModelQuery($requestDto, $team);193 ->translateQueryStringsToUserModelQuery($requestDto, $team);194 } catch (Exception $exception) {194 } catch (Exception $exception) {195-return (new ListResponse())->createEmpty()->getResponse();195+return new ListResponse()->createEmpty()->getResponse();196 }196 }197197198$resultsQuery198$resultsQuery199 ->limit($requestDto->getCount())199 ->limit($requestDto->getCount())200 ->offset($requestDto->getStartIndex() - 1);200 ->offset($requestDto->getStartIndex() - 1);201201202-return (new ListResponse())202+return new ListResponse()203 ->setHttpCode(Constants::HTTP_OK)203 ->setHttpCode(Constants::HTTP_OK)204 ->setStartIndex($requestDto->getStartIndex())204 ->setStartIndex($requestDto->getStartIndex())205 ->createFromCollection($resultsQuery->get())205 ->createFromCollection($resultsQuery->get())@@ -210,15 +210,15 @@ public function postGroups(Request $request): JsonResponse210 {210 {211$team = $this->getTeamFromRequest($request);211$team = $this->getTeamFromRequest($request);212212213-$requestDto = (new CoreGroupRequest())->createFromRequestPayload($request);213+$requestDto = new CoreGroupRequest()->createFromRequestPayload($request);214214215$group = $this->scimProvisioning->createNewGroup($requestDto, $team);215$group = $this->scimProvisioning->createNewGroup($requestDto, $team);216216217if (! $group instanceof Group) {217if (! $group instanceof Group) {218-return (new Error(Constants::HTTP_BAD_REQUEST, 'Group resource create failed'))->getResponse();218+return new Error(Constants::HTTP_BAD_REQUEST, 'Group resource create failed')->getResponse();219 }219 }220220221-$response = (new CoreGroup())->createFromGroup($group)->setHttpCode(Constants::HTTP_CREATED)->getResponse();221+$response = new CoreGroup()->createFromGroup($group)->setHttpCode(Constants::HTTP_CREATED)->getResponse();222222223return $response;223return $response;224 }224 }@@ -228,22 +228,22 @@ public function putGroups(Request $request, string $id): JsonResponse228$team = $this->getTeamFromRequest($request);228$team = $this->getTeamFromRequest($request);229229230if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {230if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {231-return (new Error())->getResponse();231+return new Error()->getResponse();232 }232 }233233234-$requestDTO = (new CoreGroupRequest())->createFromRequestPayload($request);234+$requestDTO = new CoreGroupRequest()->createFromRequestPayload($request);235235236try {236try {237$group = $this->scimProvisioning->updateGroupAttributesIfChanged(Group::uuid($id), $requestDTO, $team);237$group = $this->scimProvisioning->updateGroupAttributesIfChanged(Group::uuid($id), $requestDTO, $team);238238239-return (new CoreGroup())239+return new CoreGroup()240 ->createFromGroup($group)240 ->createFromGroup($group)241 ->setHttpCode(Constants::HTTP_OK)241 ->setHttpCode(Constants::HTTP_OK)242 ->getResponse();242 ->getResponse();243 } catch (Exception $exception) {243 } catch (Exception $exception) {244 Sentry::captureException($exception);244 Sentry::captureException($exception);245245246-return (new Error(Constants::HTTP_ERROR, 'Operation failed'))->getResponse();246+return new Error(Constants::HTTP_ERROR, 'Operation failed')->getResponse();247 }247 }248 }248 }249249@@ -252,12 +252,12 @@ public function getGroup(Request $request, string $id): JsonResponse252$team = $this->getTeamFromRequest($request);252$team = $this->getTeamFromRequest($request);253253254if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {254if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {255-return (new Error())->getResponse();255+return new Error()->getResponse();256 }256 }257257258$group = $this->scimProvisioning->getGroupById($id);258$group = $this->scimProvisioning->getGroupById($id);259259260-return (new CoreGroup())260+return new CoreGroup()261 ->excludeAttributes($request)261 ->excludeAttributes($request)262 ->setHttpCode(Constants::HTTP_OK)262 ->setHttpCode(Constants::HTTP_OK)263 ->createFromGroup($group)263 ->createFromGroup($group)@@ -268,19 +268,19 @@ public function getGroupByQuery(Request $request): JsonResponse268 {268 {269$team = $this->getTeamFromRequest($request);269$team = $this->getTeamFromRequest($request);270270271-$dto = (new GetGroupByQuery())->createFromRequestPayload($request);271+$dto = new GetGroupByQuery()->createFromRequestPayload($request);272272273try {273try {274$groupsQuery = $this->scimProvisioning->translateQueryStringsToGroupModelQuery($dto, $team);274$groupsQuery = $this->scimProvisioning->translateQueryStringsToGroupModelQuery($dto, $team);275 } catch (Exception $exception) {275 } catch (Exception $exception) {276-return (new ListResponse())->createEmpty()->getResponse();276+return new ListResponse()->createEmpty()->getResponse();277 }277 }278278279$groupsQuery279$groupsQuery280 ->limit($dto->getCount())280 ->limit($dto->getCount())281 ->offset($dto->getStartIndex() - 1);281 ->offset($dto->getStartIndex() - 1);282282283-return (new ListResponse())283+return new ListResponse()284 ->setHttpCode(Constants::HTTP_OK)284 ->setHttpCode(Constants::HTTP_OK)285 ->setStartIndex($dto->getStartIndex())285 ->setStartIndex($dto->getStartIndex())286 ->createFromCollection($groupsQuery->get(), $dto->getExcludeAttributes())286 ->createFromCollection($groupsQuery->get(), $dto->getExcludeAttributes())@@ -292,30 +292,30 @@ public function patchGroup(Request $request, $id): JsonResponse292$team = $this->getTeamFromRequest($request);292$team = $this->getTeamFromRequest($request);293293294if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {294if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {295-return (new Error())->getResponse();295+return new Error()->getResponse();296 }296 }297297298-$dto = (new PatchGroup())->createFromRequestPayload($request);298+$dto = new PatchGroup()->createFromRequestPayload($request);299299300$group = Group::uuid($id);300$group = Group::uuid($id);301301302$this->scimProvisioning->patchGroupOperation($dto, $team, $group);302$this->scimProvisioning->patchGroupOperation($dto, $team, $group);303303304-return (new JsonResponse())->setStatusCode(Constants::HTTP_NO_CONTENT);304+return new JsonResponse()->setStatusCode(Constants::HTTP_NO_CONTENT);305 }305 }306306307public function deleteGroup(Request $request, $id): JsonResponse307public function deleteGroup(Request $request, $id): JsonResponse308 {308 {309$team = $this->getTeamFromRequest($request);309$team = $this->getTeamFromRequest($request);310310311if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {311if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {312-return (new Error())->getResponse();312+return new Error()->getResponse();313 }313 }314314315if ($this->scimProvisioning->deleteGroup(Group::uuid($id))) {315if ($this->scimProvisioning->deleteGroup(Group::uuid($id))) {316-return (new JsonResponse())->setStatusCode(Constants::HTTP_NO_CONTENT);316+return new JsonResponse()->setStatusCode(Constants::HTTP_NO_CONTENT);317 } else {317 } else {318-return (new Error(Constants::HTTP_ERROR, 'Operation failed'))->getResponse();318+return new Error(Constants::HTTP_ERROR, 'Operation failed')->getResponse();319 }319 }320 }320 }321321</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Listen
Gemini said
Gemini said
The selected code changes represent a mechanical refactoring of a PHP controller to align with modern syntax standards, likely as part of the upgrade to
PHP 8.5
.
Core Change: Member Access on Instantiation
Core Change: Member Access on Instantiation
The primary modification across the entire file is the removal of unnecessary parentheses when performing member access (method chaining) on a newly instantiated object....
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.0028257978,"top":0.057063047,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0028257978,"top":0.08060654,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.015957447,"top":0.09217877,"width":0.101230055,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":4,"bounds":{"left":0.0028257978,"top":0.11332801,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":5,"bounds":{"left":0.015957447,"top":0.12490024,"width":0.11735372,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Usage | Windsurf","depth":4,"bounds":{"left":0.0,"top":0.14604948,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Usage | Windsurf","depth":5,"bounds":{"left":0.013297873,"top":0.15762171,"width":0.029920213,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.0028257978,"top":0.1839585,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6848] Sidekick SMS issue - Jira","depth":4,"bounds":{"left":0.0028257978,"top":0.207502,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6848] Sidekick SMS issue - Jira","depth":5,"bounds":{"left":0.015957447,"top":0.21907422,"width":0.06632314,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20891] Sidekick SMS issue - Jira","depth":4,"bounds":{"left":0.0028257978,"top":0.24022347,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20891] Sidekick SMS issue - Jira","depth":5,"bounds":{"left":0.015957447,"top":0.25179568,"width":0.064494684,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app","depth":4,"bounds":{"left":0.0028257978,"top":0.27294493,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app","depth":5,"bounds":{"left":0.015957447,"top":0.28451717,"width":0.15658244,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.0028257978,"top":0.31085396,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":4,"bounds":{"left":0.0028257978,"top":0.33439744,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":5,"bounds":{"left":0.015957447,"top":0.34596968,"width":0.11735372,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0028257978,"top":0.36711892,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.015957447,"top":0.37869114,"width":0.013297873,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0028257978,"top":0.39984038,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.015957447,"top":0.4114126,"width":0.013297873,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0028257978,"top":0.43256184,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.015957447,"top":0.4441341,"width":0.013297873,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.0028257978,"top":0.47047088,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6853] Moxso - Potential deal stages bug - Jira","depth":4,"bounds":{"left":0.0028257978,"top":0.49401435,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6853] Moxso - Potential deal stages bug - Jira","depth":5,"bounds":{"left":0.015957447,"top":0.50558656,"width":0.091755316,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20903] Recorded call does not appear on the dashboard - Jira","depth":4,"bounds":{"left":0.0028257978,"top":0.52673584,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20903] Recorded call does not appear on the dashboard - Jira","depth":5,"bounds":{"left":0.015957447,"top":0.5383081,"width":0.11619016,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app","depth":4,"bounds":{"left":0.0028257978,"top":0.5594573,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app","depth":5,"bounds":{"left":0.015957447,"top":0.57102954,"width":0.18932846,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.5666401,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.59217876,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":5,"bounds":{"left":0.013297873,"top":0.603751,"width":0.12898937,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Useful commands - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.6249002,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Useful commands - Engineering - Confluence","depth":5,"bounds":{"left":0.013297873,"top":0.63647246,"width":0.079288565,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Dev Tools - Elastic","depth":4,"bounds":{"left":0.0,"top":0.6576217,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Dev Tools - Elastic","depth":5,"bounds":{"left":0.013297873,"top":0.6691939,"width":0.032247342,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.6903432,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.7019154,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | eu-west-1","depth":4,"bounds":{"left":0.0,"top":0.72306466,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | eu-west-1","depth":5,"bounds":{"left":0.013297873,"top":0.73463684,"width":0.041722074,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | eu-west-1","depth":4,"bounds":{"left":0.0,"top":0.7557861,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | eu-west-1","depth":5,"bounds":{"left":0.013297873,"top":0.76735836,"width":0.041722074,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | eu-west-1","depth":4,"bounds":{"left":0.0,"top":0.7885076,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | eu-west-1","depth":5,"bounds":{"left":0.013297873,"top":0.8000798,"width":0.041722074,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.0028257978,"top":0.8264166,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app","depth":4,"bounds":{"left":0.0028257978,"top":0.8499601,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app","depth":5,"bounds":{"left":0.015957447,"top":0.86153233,"width":0.21575798,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira","depth":4,"bounds":{"left":0.0028257978,"top":0.88268155,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira","depth":5,"bounds":{"left":0.015957447,"top":0.8942538,"width":0.122340426,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipedrive Developers Corner | Pipedrive API sandbox","depth":4,"bounds":{"left":0.0,"top":0.915403,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipedrive Developers Corner | Pipedrive API sandbox","depth":5,"bounds":{"left":0.013297873,"top":0.92697525,"width":0.091755316,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"daniti/pipedrive-laravel: A Pipedrive App boilerplate with Laravel","depth":4,"bounds":{"left":0.0,"top":0.9481245,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"daniti/pipedrive-laravel: A Pipedrive App boilerplate with Laravel","depth":5,"bounds":{"left":0.013297873,"top":0.9596967,"width":0.11153591,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"pipedrive/client-php: Pipedrive API client for PHP","depth":4,"bounds":{"left":0.0,"top":0.980846,"width":0.07962101,"height":0.019154012},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"pipedrive/client-php: Pipedrive API client for PHP","depth":5,"bounds":{"left":0.013297873,"top":0.99241817,"width":0.08543883,"height":0.00758183},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"IsraelOrtuno/pipedrive: Complete Pipedrive API client for PHP","depth":4,"bounds":{"left":0.0,"top":1.0,"width":0.07962101,"height":-0.013567448},"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"IsraelOrtuno/pipedrive: Complete Pipedrive API client for PHP","depth":5,"bounds":{"left":0.013297873,"top":1.0,"width":0.10688165,"height":-0.02513969},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Code search results","depth":4,"bounds":{"left":0.0,"top":1.0,"width":0.07962101,"height":-0.046288848},"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code search results","depth":5,"bounds":{"left":0.013297873,"top":1.0,"width":0.034574468,"height":-0.05786109},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":1.0,"width":0.07962101,"height":-0.07901037},"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":1.0,"width":0.013131649,"height":-0.09058261},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20903] Recorded call does not appear on the dashboard - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20903] Recorded call does not appear on the dashboard - Jira","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.92897046,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.1846742,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.1966423,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"WORK, Google Account: lukas.kovalik@jiminny.com","depth":12,"bounds":{"left":0.1939827,"top":0.103751,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Main menu","depth":12,"bounds":{"left":0.08361037,"top":0.103751,"width":0.013297873,"height":0.031923383},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Chat","depth":12,"bounds":{"left":0.16605718,"top":0.103751,"width":0.013297873,"height":0.031923383},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":12,"bounds":{"left":0.17935506,"top":0.103751,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.079288565,"top":0.14764565,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.079288565,"top":0.15003991,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":21,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>JY-18091 | Update composer to support php 8.5 by n</tabTitle>” with “<selection>@@ -40,15 +40,15 @@ public function postUsers(Request $request): JsonResponse40 {40 {41$team = $this->getTeamFromRequest($request);41$team = $this->getTeamFromRequest($request);424243-$requestDTO = (new CoreUserRequest())->createFromRequestPayload($request);43+$requestDTO = new CoreUserRequest()->createFromRequestPayload($request);444445if ($requestDTO->getPrimaryPhoneNumber() !== null) {45if ($requestDTO->getPrimaryPhoneNumber() !== null) {46$user = $this->userRepository->findByPhone(46$user = $this->userRepository->findByPhone(47 phone: $requestDTO->getPrimaryPhoneNumber(),47 phone: $requestDTO->getPrimaryPhoneNumber(),48 );48 );494950if ($user !== null && $user->getEmailAddress() !== $requestDTO->getEmailPrimaryWork()) {50if ($user !== null && $user->getEmailAddress() !== $requestDTO->getEmailPrimaryWork()) {51-return (new Error(Constants::HTTP_BAD_REQUEST, 'User primary phone already exists'))51+return new Error(Constants::HTTP_BAD_REQUEST, 'User primary phone already exists')52 ->getResponse();52 ->getResponse();53 }53 }54 }54 }@@ -59,7 +59,7 @@ public function postUsers(Request $request): JsonResponse59 );59 );606061if ($user !== null && $user->getEmailAddress() !== $requestDTO->getEmailPrimaryWork()) {61if ($user !== null && $user->getEmailAddress() !== $requestDTO->getEmailPrimaryWork()) {62-return (new Error(Constants::HTTP_BAD_REQUEST, 'User secondary phone already exists'))62+return new Error(Constants::HTTP_BAD_REQUEST, 'User secondary phone already exists')63 ->getResponse();63 ->getResponse();64 }64 }65 }65 }@@ -74,11 +74,11 @@ public function postUsers(Request $request): JsonResponse74$newUser = $this->scimProvisioning->createOrUpdateNewUser($requestDTO, $team);74$newUser = $this->scimProvisioning->createOrUpdateNewUser($requestDTO, $team);757576if (! $newUser instanceof User) {76if (! $newUser instanceof User) {77-return (new Error(Constants::HTTP_BAD_REQUEST, 'User create operation failed'))77+return new Error(Constants::HTTP_BAD_REQUEST, 'User create operation failed')78 ->getResponse();78 ->getResponse();79 }79 }808081-return (new CoreUser())81+return new CoreUser()82 ->createFromUser($newUser)82 ->createFromUser($newUser)83 ->setExternalId($requestDTO->getUserName())83 ->setExternalId($requestDTO->getUserName())84 ->setHttpCode(Constants::HTTP_CREATED)84 ->setHttpCode(Constants::HTTP_CREATED)@@ -93,22 +93,22 @@ public function putUsers(Request $request, string $id): JsonResponse93$team = $this->getTeamFromRequest($request);93$team = $this->getTeamFromRequest($request);949495if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {95if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {96-return (new Error())->getResponse();96+return new Error()->getResponse();97 }97 }989899-$requestDTO = (new CoreUserRequest())->createFromRequestPayload($request);99+$requestDTO = new CoreUserRequest()->createFromRequestPayload($request);100100101try {101try {102$user = $this->scimProvisioning->updateUserAttributesIfChanged(User::uuid($id), $requestDTO, $team);102$user = $this->scimProvisioning->updateUserAttributesIfChanged(User::uuid($id), $requestDTO, $team);103103104-return (new CoreUser())104+return new CoreUser()105 ->createFromUser($user)105 ->createFromUser($user)106 ->setHttpCode(Constants::HTTP_OK)106 ->setHttpCode(Constants::HTTP_OK)107 ->getResponse();107 ->getResponse();108 } catch (Exception $exception) {108 } catch (Exception $exception) {109 Sentry::captureException($exception);109 Sentry::captureException($exception);110110111-return (new Error(Constants::HTTP_ERROR, 'Operation failed'))->getResponse();111+return new Error(Constants::HTTP_ERROR, 'Operation failed')->getResponse();112 }112 }113 }113 }114114@@ -117,7 +117,7 @@ public function deleteUser(Request $request, string $id): JsonResponse117$team = $this->getTeamFromRequest($request);117$team = $this->getTeamFromRequest($request);118118119if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {119if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {120-return (new Error())->getResponse();120+return new Error()->getResponse();121 }121 }122122123$user = $this->scimProvisioning->getUserById($id);123$user = $this->scimProvisioning->getUserById($id);@@ -131,18 +131,18 @@ public function deleteUser(Request $request, string $id): JsonResponse131 ),131 ),132 );132 );133133134-return (new JsonResponse())->setStatusCode(204);134+return new JsonResponse()->setStatusCode(204);135 }135 }136136137public function patchUser(Request $request, string $id): JsonResponse137public function patchUser(Request $request, string $id): JsonResponse138 {138 {139$team = $this->getTeamFromRequest($request);139$team = $this->getTeamFromRequest($request);140140141if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {141if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {142-return (new Error())->getResponse();142+return new Error()->getResponse();143 }143 }144144145-$requestDTO = (new PatchUser())145+$requestDTO = new PatchUser()146 ->setResourceLocalId($id)146 ->setResourceLocalId($id)147 ->createFromRequestPayload($request);147 ->createFromRequestPayload($request);148148@@ -153,10 +153,10 @@ public function patchUser(Request $request, string $id): JsonResponse153 } catch (Exception $e) {153 } catch (Exception $e) {154 Sentry::captureException($e);154 Sentry::captureException($e);155155156-return (new Error(Constants::HTTP_BAD_REQUEST, 'Operation failed'))->getResponse();156+return new Error(Constants::HTTP_BAD_REQUEST, 'Operation failed')->getResponse();157 }157 }158158159-return (new CoreUser())159+return new CoreUser()160 ->createFromUser($user)160 ->createFromUser($user)161 ->setHttpCode(Constants::HTTP_OK)161 ->setHttpCode(Constants::HTTP_OK)162 ->getResponse();162 ->getResponse();@@ -167,16 +167,16 @@ public function getUser(Request $request, string $id): JsonResponse167$team = $this->getTeamFromRequest($request);167$team = $this->getTeamFromRequest($request);168168169if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {169if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {170-return (new Error())->getResponse();170+return new Error()->getResponse();171 }171 }172172173$user = $this->scimProvisioning->getUserById($id);173$user = $this->scimProvisioning->getUserById($id);174174175if (! $team->isSame($user->getTeam())) {175if (! $team->isSame($user->getTeam())) {176-return (new Error(Constants::HTTP_ACCESS_DENIED, 'Context does not match.'))->getResponse();176+return new Error(Constants::HTTP_ACCESS_DENIED, 'Context does not match.')->getResponse();177 }177 }178178179-return (new CoreUser())179+return new CoreUser()180 ->setHttpCode(Constants::HTTP_OK)180 ->setHttpCode(Constants::HTTP_OK)181 ->createFromUser($user)181 ->createFromUser($user)182 ->getResponse();182 ->getResponse();@@ -186,20 +186,20 @@ public function getUsersByQuery(Request $request): JsonResponse186 {186 {187$team = $this->getTeamFromRequest($request);187$team = $this->getTeamFromRequest($request);188188189-$requestDto = (new GetUserByQuery())->createFromRequestPayload($request);189+$requestDto = new GetUserByQuery()->createFromRequestPayload($request);190190191try {191try {192$resultsQuery = $this->scimProvisioning192$resultsQuery = $this->scimProvisioning193 ->translateQueryStringsToUserModelQuery($requestDto, $team);193 ->translateQueryStringsToUserModelQuery($requestDto, $team);194 } catch (Exception $exception) {194 } catch (Exception $exception) {195-return (new ListResponse())->createEmpty()->getResponse();195+return new ListResponse()->createEmpty()->getResponse();196 }196 }197197198$resultsQuery198$resultsQuery199 ->limit($requestDto->getCount())199 ->limit($requestDto->getCount())200 ->offset($requestDto->getStartIndex() - 1);200 ->offset($requestDto->getStartIndex() - 1);201201202-return (new ListResponse())202+return new ListResponse()203 ->setHttpCode(Constants::HTTP_OK)203 ->setHttpCode(Constants::HTTP_OK)204 ->setStartIndex($requestDto->getStartIndex())204 ->setStartIndex($requestDto->getStartIndex())205 ->createFromCollection($resultsQuery->get())205 ->createFromCollection($resultsQuery->get())@@ -210,15 +210,15 @@ public function postGroups(Request $request): JsonResponse210 {210 {211$team = $this->getTeamFromRequest($request);211$team = $this->getTeamFromRequest($request);212212213-$requestDto = (new CoreGroupRequest())->createFromRequestPayload($request);213+$requestDto = new CoreGroupRequest()->createFromRequestPayload($request);214214215$group = $this->scimProvisioning->createNewGroup($requestDto, $team);215$group = $this->scimProvisioning->createNewGroup($requestDto, $team);216216217if (! $group instanceof Group) {217if (! $group instanceof Group) {218-return (new Error(Constants::HTTP_BAD_REQUEST, 'Group resource create failed'))->getResponse();218+return new Error(Constants::HTTP_BAD_REQUEST, 'Group resource create failed')->getResponse();219 }219 }220220221-$response = (new CoreGroup())->createFromGroup($group)->setHttpCode(Constants::HTTP_CREATED)->getResponse();221+$response = new CoreGroup()->createFromGroup($group)->setHttpCode(Constants::HTTP_CREATED)->getResponse();222222223return $response;223return $response;224 }224 }@@ -228,22 +228,22 @@ public function putGroups(Request $request, string $id): JsonResponse228$team = $this->getTeamFromRequest($request);228$team = $this->getTeamFromRequest($request);229229230if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {230if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {231-return (new Error())->getResponse();231+return new Error()->getResponse();232 }232 }233233234-$requestDTO = (new CoreGroupRequest())->createFromRequestPayload($request);234+$requestDTO = new CoreGroupRequest()->createFromRequestPayload($request);235235236try {236try {237$group = $this->scimProvisioning->updateGroupAttributesIfChanged(Group::uuid($id), $requestDTO, $team);237$group = $this->scimProvisioning->updateGroupAttributesIfChanged(Group::uuid($id), $requestDTO, $team);238238239-return (new CoreGroup())239+return new CoreGroup()240 ->createFromGroup($group)240 ->createFromGroup($group)241 ->setHttpCode(Constants::HTTP_OK)241 ->setHttpCode(Constants::HTTP_OK)242 ->getResponse();242 ->getResponse();243 } catch (Exception $exception) {243 } catch (Exception $exception) {244 Sentry::captureException($exception);244 Sentry::captureException($exception);245245246-return (new Error(Constants::HTTP_ERROR, 'Operation failed'))->getResponse();246+return new Error(Constants::HTTP_ERROR, 'Operation failed')->getResponse();247 }247 }248 }248 }249249@@ -252,12 +252,12 @@ public function getGroup(Request $request, string $id): JsonResponse252$team = $this->getTeamFromRequest($request);252$team = $this->getTeamFromRequest($request);253253254if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {254if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {255-return (new Error())->getResponse();255+return new Error()->getResponse();256 }256 }257257258$group = $this->scimProvisioning->getGroupById($id);258$group = $this->scimProvisioning->getGroupById($id);259259260-return (new CoreGroup())260+return new CoreGroup()261 ->excludeAttributes($request)261 ->excludeAttributes($request)262 ->setHttpCode(Constants::HTTP_OK)262 ->setHttpCode(Constants::HTTP_OK)263 ->createFromGroup($group)263 ->createFromGroup($group)@@ -268,19 +268,19 @@ public function getGroupByQuery(Request $request): JsonResponse268 {268 {269$team = $this->getTeamFromRequest($request);269$team = $this->getTeamFromRequest($request);270270271-$dto = (new GetGroupByQuery())->createFromRequestPayload($request);271+$dto = new GetGroupByQuery()->createFromRequestPayload($request);272272273try {273try {274$groupsQuery = $this->scimProvisioning->translateQueryStringsToGroupModelQuery($dto, $team);274$groupsQuery = $this->scimProvisioning->translateQueryStringsToGroupModelQuery($dto, $team);275 } catch (Exception $exception) {275 } catch (Exception $exception) {276-return (new ListResponse())->createEmpty()->getResponse();276+return new ListResponse()->createEmpty()->getResponse();277 }277 }278278279$groupsQuery279$groupsQuery280 ->limit($dto->getCount())280 ->limit($dto->getCount())281 ->offset($dto->getStartIndex() - 1);281 ->offset($dto->getStartIndex() - 1);282282283-return (new ListResponse())283+return new ListResponse()284 ->setHttpCode(Constants::HTTP_OK)284 ->setHttpCode(Constants::HTTP_OK)285 ->setStartIndex($dto->getStartIndex())285 ->setStartIndex($dto->getStartIndex())286 ->createFromCollection($groupsQuery->get(), $dto->getExcludeAttributes())286 ->createFromCollection($groupsQuery->get(), $dto->getExcludeAttributes())@@ -292,30 +292,30 @@ public function patchGroup(Request $request, $id): JsonResponse292$team = $this->getTeamFromRequest($request);292$team = $this->getTeamFromRequest($request);293293294if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {294if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {295-return (new Error())->getResponse();295+return new Error()->getResponse();296 }296 }297297298-$dto = (new PatchGroup())->createFromRequestPayload($request);298+$dto = new PatchGroup()->createFromRequestPayload($request);299299300$group = Group::uuid($id);300$group = Group::uuid($id);301301302$this->scimProvisioning->patchGroupOperation($dto, $team, $group);302$this->scimProvisioning->patchGroupOperation($dto, $team, $group);303303304-return (new JsonResponse())->setStatusCode(Constants::HTTP_NO_CONTENT);304+return new JsonResponse()->setStatusCode(Constants::HTTP_NO_CONTENT);305 }305 }306306307public function deleteGroup(Request $request, $id): JsonResponse307public function deleteGroup(Request $request, $id): JsonResponse308 {308 {309$team = $this->getTeamFromRequest($request);309$team = $this->getTeamFromRequest($request);310310311if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {311if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {312-return (new Error())->getResponse();312+return new Error()->getResponse();313 }313 }314314315if ($this->scimProvisioning->deleteGroup(Group::uuid($id))) {315if ($this->scimProvisioning->deleteGroup(Group::uuid($id))) {316-return (new JsonResponse())->setStatusCode(Constants::HTTP_NO_CONTENT);316+return new JsonResponse()->setStatusCode(Constants::HTTP_NO_CONTENT);317 } else {317 } else {318-return (new Error(Constants::HTTP_ERROR, 'Operation failed'))->getResponse();318+return new Error(Constants::HTTP_ERROR, 'Operation failed')->getResponse();319 }319 }320 }320 }321321</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>JY-18091 | Update composer to support php 8.5 by n</tabTitle>” with “<selection>@@ -40,15 +40,15 @@ public function postUsers(Request $request): JsonResponse40 {40 {41$team = $this->getTeamFromRequest($request);41$team = $this->getTeamFromRequest($request);424243-$requestDTO = (new CoreUserRequest())->createFromRequestPayload($request);43+$requestDTO = new CoreUserRequest()->createFromRequestPayload($request);444445if ($requestDTO->getPrimaryPhoneNumber() !== null) {45if ($requestDTO->getPrimaryPhoneNumber() !== null) {46$user = $this->userRepository->findByPhone(46$user = $this->userRepository->findByPhone(47 phone: $requestDTO->getPrimaryPhoneNumber(),47 phone: $requestDTO->getPrimaryPhoneNumber(),48 );48 );494950if ($user !== null && $user->getEmailAddress() !== $requestDTO->getEmailPrimaryWork()) {50if ($user !== null && $user->getEmailAddress() !== $requestDTO->getEmailPrimaryWork()) {51-return (new Error(Constants::HTTP_BAD_REQUEST, 'User primary phone already exists'))51+return new Error(Constants::HTTP_BAD_REQUEST, 'User primary phone already exists')52 ->getResponse();52 ->getResponse();53 }53 }54 }54 }@@ -59,7 +59,7 @@ public function postUsers(Request $request): JsonResponse59 );59 );606061if ($user !== null && $user->getEmailAddress() !== $requestDTO->getEmailPrimaryWork()) {61if ($user !== null && $user->getEmailAddress() !== $requestDTO->getEmailPrimaryWork()) {62-return (new Error(Constants::HTTP_BAD_REQUEST, 'User secondary phone already exists'))62+return new Error(Constants::HTTP_BAD_REQUEST, 'User secondary phone already exists')63 ->getResponse();63 ->getResponse();64 }64 }65 }65 }@@ -74,11 +74,11 @@ public function postUsers(Request $request): JsonResponse74$newUser = $this->scimProvisioning->createOrUpdateNewUser($requestDTO, $team);74$newUser = $this->scimProvisioning->createOrUpdateNewUser($requestDTO, $team);757576if (! $newUser instanceof User) {76if (! $newUser instanceof User) {77-return (new Error(Constants::HTTP_BAD_REQUEST, 'User create operation failed'))77+return new Error(Constants::HTTP_BAD_REQUEST, 'User create operation failed')78 ->getResponse();78 ->getResponse();79 }79 }808081-return (new CoreUser())81+return new CoreUser()82 ->createFromUser($newUser)82 ->createFromUser($newUser)83 ->setExternalId($requestDTO->getUserName())83 ->setExternalId($requestDTO->getUserName())84 ->setHttpCode(Constants::HTTP_CREATED)84 ->setHttpCode(Constants::HTTP_CREATED)@@ -93,22 +93,22 @@ public function putUsers(Request $request, string $id): JsonResponse93$team = $this->getTeamFromRequest($request);93$team = $this->getTeamFromRequest($request);949495if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {95if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {96-return (new Error())->getResponse();96+return new Error()->getResponse();97 }97 }989899-$requestDTO = (new CoreUserRequest())->createFromRequestPayload($request);99+$requestDTO = new CoreUserRequest()->createFromRequestPayload($request);100100101try {101try {102$user = $this->scimProvisioning->updateUserAttributesIfChanged(User::uuid($id), $requestDTO, $team);102$user = $this->scimProvisioning->updateUserAttributesIfChanged(User::uuid($id), $requestDTO, $team);103103104-return (new CoreUser())104+return new CoreUser()105 ->createFromUser($user)105 ->createFromUser($user)106 ->setHttpCode(Constants::HTTP_OK)106 ->setHttpCode(Constants::HTTP_OK)107 ->getResponse();107 ->getResponse();108 } catch (Exception $exception) {108 } catch (Exception $exception) {109 Sentry::captureException($exception);109 Sentry::captureException($exception);110110111-return (new Error(Constants::HTTP_ERROR, 'Operation failed'))->getResponse();111+return new Error(Constants::HTTP_ERROR, 'Operation failed')->getResponse();112 }112 }113 }113 }114114@@ -117,7 +117,7 @@ public function deleteUser(Request $request, string $id): JsonResponse117$team = $this->getTeamFromRequest($request);117$team = $this->getTeamFromRequest($request);118118119if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {119if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {120-return (new Error())->getResponse();120+return new Error()->getResponse();121 }121 }122122123$user = $this->scimProvisioning->getUserById($id);123$user = $this->scimProvisioning->getUserById($id);@@ -131,18 +131,18 @@ public function deleteUser(Request $request, string $id): JsonResponse131 ),131 ),132 );132 );133133134-return (new JsonResponse())->setStatusCode(204);134+return new JsonResponse()->setStatusCode(204);135 }135 }136136137public function patchUser(Request $request, string $id): JsonResponse137public function patchUser(Request $request, string $id): JsonResponse138 {138 {139$team = $this->getTeamFromRequest($request);139$team = $this->getTeamFromRequest($request);140140141if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {141if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {142-return (new Error())->getResponse();142+return new Error()->getResponse();143 }143 }144144145-$requestDTO = (new PatchUser())145+$requestDTO = new PatchUser()146 ->setResourceLocalId($id)146 ->setResourceLocalId($id)147 ->createFromRequestPayload($request);147 ->createFromRequestPayload($request);148148@@ -153,10 +153,10 @@ public function patchUser(Request $request, string $id): JsonResponse153 } catch (Exception $e) {153 } catch (Exception $e) {154 Sentry::captureException($e);154 Sentry::captureException($e);155155156-return (new Error(Constants::HTTP_BAD_REQUEST, 'Operation failed'))->getResponse();156+return new Error(Constants::HTTP_BAD_REQUEST, 'Operation failed')->getResponse();157 }157 }158158159-return (new CoreUser())159+return new CoreUser()160 ->createFromUser($user)160 ->createFromUser($user)161 ->setHttpCode(Constants::HTTP_OK)161 ->setHttpCode(Constants::HTTP_OK)162 ->getResponse();162 ->getResponse();@@ -167,16 +167,16 @@ public function getUser(Request $request, string $id): JsonResponse167$team = $this->getTeamFromRequest($request);167$team = $this->getTeamFromRequest($request);168168169if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {169if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {170-return (new Error())->getResponse();170+return new Error()->getResponse();171 }171 }172172173$user = $this->scimProvisioning->getUserById($id);173$user = $this->scimProvisioning->getUserById($id);174174175if (! $team->isSame($user->getTeam())) {175if (! $team->isSame($user->getTeam())) {176-return (new Error(Constants::HTTP_ACCESS_DENIED, 'Context does not match.'))->getResponse();176+return new Error(Constants::HTTP_ACCESS_DENIED, 'Context does not match.')->getResponse();177 }177 }178178179-return (new CoreUser())179+return new CoreUser()180 ->setHttpCode(Constants::HTTP_OK)180 ->setHttpCode(Constants::HTTP_OK)181 ->createFromUser($user)181 ->createFromUser($user)182 ->getResponse();182 ->getResponse();@@ -186,20 +186,20 @@ public function getUsersByQuery(Request $request): JsonResponse186 {186 {187$team = $this->getTeamFromRequest($request);187$team = $this->getTeamFromRequest($request);188188189-$requestDto = (new GetUserByQuery())->createFromRequestPayload($request);189+$requestDto = new GetUserByQuery()->createFromRequestPayload($request);190190191try {191try {192$resultsQuery = $this->scimProvisioning192$resultsQuery = $this->scimProvisioning193 ->translateQueryStringsToUserModelQuery($requestDto, $team);193 ->translateQueryStringsToUserModelQuery($requestDto, $team);194 } catch (Exception $exception) {194 } catch (Exception $exception) {195-return (new ListResponse())->createEmpty()->getResponse();195+return new ListResponse()->createEmpty()->getResponse();196 }196 }197197198$resultsQuery198$resultsQuery199 ->limit($requestDto->getCount())199 ->limit($requestDto->getCount())200 ->offset($requestDto->getStartIndex() - 1);200 ->offset($requestDto->getStartIndex() - 1);201201202-return (new ListResponse())202+return new ListResponse()203 ->setHttpCode(Constants::HTTP_OK)203 ->setHttpCode(Constants::HTTP_OK)204 ->setStartIndex($requestDto->getStartIndex())204 ->setStartIndex($requestDto->getStartIndex())205 ->createFromCollection($resultsQuery->get())205 ->createFromCollection($resultsQuery->get())@@ -210,15 +210,15 @@ public function postGroups(Request $request): JsonResponse210 {210 {211$team = $this->getTeamFromRequest($request);211$team = $this->getTeamFromRequest($request);212212213-$requestDto = (new CoreGroupRequest())->createFromRequestPayload($request);213+$requestDto = new CoreGroupRequest()->createFromRequestPayload($request);214214215$group = $this->scimProvisioning->createNewGroup($requestDto, $team);215$group = $this->scimProvisioning->createNewGroup($requestDto, $team);216216217if (! $group instanceof Group) {217if (! $group instanceof Group) {218-return (new Error(Constants::HTTP_BAD_REQUEST, 'Group resource create failed'))->getResponse();218+return new Error(Constants::HTTP_BAD_REQUEST, 'Group resource create failed')->getResponse();219 }219 }220220221-$response = (new CoreGroup())->createFromGroup($group)->setHttpCode(Constants::HTTP_CREATED)->getResponse();221+$response = new CoreGroup()->createFromGroup($group)->setHttpCode(Constants::HTTP_CREATED)->getResponse();222222223return $response;223return $response;224 }224 }@@ -228,22 +228,22 @@ public function putGroups(Request $request, string $id): JsonResponse228$team = $this->getTeamFromRequest($request);228$team = $this->getTeamFromRequest($request);229229230if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {230if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {231-return (new Error())->getResponse();231+return new Error()->getResponse();232 }232 }233233234-$requestDTO = (new CoreGroupRequest())->createFromRequestPayload($request);234+$requestDTO = new CoreGroupRequest()->createFromRequestPayload($request);235235236try {236try {237$group = $this->scimProvisioning->updateGroupAttributesIfChanged(Group::uuid($id), $requestDTO, $team);237$group = $this->scimProvisioning->updateGroupAttributesIfChanged(Group::uuid($id), $requestDTO, $team);238238239-return (new CoreGroup())239+return new CoreGroup()240 ->createFromGroup($group)240 ->createFromGroup($group)241 ->setHttpCode(Constants::HTTP_OK)241 ->setHttpCode(Constants::HTTP_OK)242 ->getResponse();242 ->getResponse();243 } catch (Exception $exception) {243 } catch (Exception $exception) {244 Sentry::captureException($exception);244 Sentry::captureException($exception);245245246-return (new Error(Constants::HTTP_ERROR, 'Operation failed'))->getResponse();246+return new Error(Constants::HTTP_ERROR, 'Operation failed')->getResponse();247 }247 }248 }248 }249249@@ -252,12 +252,12 @@ public function getGroup(Request $request, string $id): JsonResponse252$team = $this->getTeamFromRequest($request);252$team = $this->getTeamFromRequest($request);253253254if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {254if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {255-return (new Error())->getResponse();255+return new Error()->getResponse();256 }256 }257257258$group = $this->scimProvisioning->getGroupById($id);258$group = $this->scimProvisioning->getGroupById($id);259259260-return (new CoreGroup())260+return new CoreGroup()261 ->excludeAttributes($request)261 ->excludeAttributes($request)262 ->setHttpCode(Constants::HTTP_OK)262 ->setHttpCode(Constants::HTTP_OK)263 ->createFromGroup($group)263 ->createFromGroup($group)@@ -268,19 +268,19 @@ public function getGroupByQuery(Request $request): JsonResponse268 {268 {269$team = $this->getTeamFromRequest($request);269$team = $this->getTeamFromRequest($request);270270271-$dto = (new GetGroupByQuery())->createFromRequestPayload($request);271+$dto = new GetGroupByQuery()->createFromRequestPayload($request);272272273try {273try {274$groupsQuery = $this->scimProvisioning->translateQueryStringsToGroupModelQuery($dto, $team);274$groupsQuery = $this->scimProvisioning->translateQueryStringsToGroupModelQuery($dto, $team);275 } catch (Exception $exception) {275 } catch (Exception $exception) {276-return (new ListResponse())->createEmpty()->getResponse();276+return new ListResponse()->createEmpty()->getResponse();277 }277 }278278279$groupsQuery279$groupsQuery280 ->limit($dto->getCount())280 ->limit($dto->getCount())281 ->offset($dto->getStartIndex() - 1);281 ->offset($dto->getStartIndex() - 1);282282283-return (new ListResponse())283+return new ListResponse()284 ->setHttpCode(Constants::HTTP_OK)284 ->setHttpCode(Constants::HTTP_OK)285 ->setStartIndex($dto->getStartIndex())285 ->setStartIndex($dto->getStartIndex())286 ->createFromCollection($groupsQuery->get(), $dto->getExcludeAttributes())286 ->createFromCollection($groupsQuery->get(), $dto->getExcludeAttributes())@@ -292,30 +292,30 @@ public function patchGroup(Request $request, $id): JsonResponse292$team = $this->getTeamFromRequest($request);292$team = $this->getTeamFromRequest($request);293293294if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {294if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {295-return (new Error())->getResponse();295+return new Error()->getResponse();296 }296 }297297298-$dto = (new PatchGroup())->createFromRequestPayload($request);298+$dto = new PatchGroup()->createFromRequestPayload($request);299299300$group = Group::uuid($id);300$group = Group::uuid($id);301301302$this->scimProvisioning->patchGroupOperation($dto, $team, $group);302$this->scimProvisioning->patchGroupOperation($dto, $team, $group);303303304-return (new JsonResponse())->setStatusCode(Constants::HTTP_NO_CONTENT);304+return new JsonResponse()->setStatusCode(Constants::HTTP_NO_CONTENT);305 }305 }306306307public function deleteGroup(Request $request, $id): JsonResponse307public function deleteGroup(Request $request, $id): JsonResponse308 {308 {309$team = $this->getTeamFromRequest($request);309$team = $this->getTeamFromRequest($request);310310311if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {311if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {312-return (new Error())->getResponse();312+return new Error()->getResponse();313 }313 }314314315if ($this->scimProvisioning->deleteGroup(Group::uuid($id))) {315if ($this->scimProvisioning->deleteGroup(Group::uuid($id))) {316-return (new JsonResponse())->setStatusCode(Constants::HTTP_NO_CONTENT);316+return new JsonResponse()->setStatusCode(Constants::HTTP_NO_CONTENT);317 } else {317 } else {318-return (new Error(Constants::HTTP_ERROR, 'Operation failed'))->getResponse();318+return new Error(Constants::HTTP_ERROR, 'Operation failed')->getResponse();319 }319 }320 }320 }321321</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Listen","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The selected code changes represent a mechanical refactoring of a PHP controller to align with modern syntax standards, likely as part of the upgrade to","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PHP 8.5","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Core Change: Member Access on Instantiation","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Core Change: Member Access on Instantiation","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The primary modification across the entire file is the removal of unnecessary parentheses when performing member access (method chaining) on a newly instantiated object.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-9092916235951063923
|
4311055633121820104
|
click
|
accessibility
|
NULL
|
Unnamed Group
Platform Sprint 4 Q2 - Platform Team Unnamed Group
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
[SRD-6849] Recorded call does not appear on the dashboard - Jira
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Usage | Windsurf
Usage | Windsurf
Unnamed Group
[SRD-6848] Sidekick SMS issue - Jira
[SRD-6848] Sidekick SMS issue - Jira
[JY-20891] Sidekick SMS issue - Jira
[JY-20891] Sidekick SMS issue - Jira
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
Unnamed Group
[SRD-6849] Recorded call does not appear on the dashboard - Jira
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Jiminny
Jiminny
Jiminny
Jiminny
Jiminny
Jiminny
Unnamed Group
[SRD-6853] Moxso - Potential deal stages bug - Jira
[SRD-6853] Moxso - Potential deal stages bug - Jira
[JY-20903] Recorded call does not appear on the dashboard - Jira
[JY-20903] Recorded call does not appear on the dashboard - Jira
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
Useful commands - Engineering - Confluence
Useful commands - Engineering - Confluence
Dev Tools - Elastic
Dev Tools - Elastic
Jiminny
Jiminny
CloudWatch | eu-west-1
CloudWatch | eu-west-1
CloudWatch | eu-west-1
CloudWatch | eu-west-1
CloudWatch | eu-west-1
CloudWatch | eu-west-1
Unnamed Group
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
Pipedrive Developers Corner | Pipedrive API sandbox
Pipedrive Developers Corner | Pipedrive API sandbox
daniti/pipedrive-laravel: A Pipedrive App boilerplate with Laravel
daniti/pipedrive-laravel: A Pipedrive App boilerplate with Laravel
pipedrive/client-php: Pipedrive API client for PHP
pipedrive/client-php: Pipedrive API client for PHP
IsraelOrtuno/pipedrive: Complete Pipedrive API client for PHP
IsraelOrtuno/pipedrive: Complete Pipedrive API client for PHP
Code search results
Code search results
Jiminny
Jiminny
[JY-20903] Recorded call does not appear on the dashboard - Jira
[JY-20903] Recorded call does not appear on the dashboard - Jira
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
AI Chat settings
Close
WORK, Google Account: [EMAIL]
Main menu
New Chat
Open menu for conversation actions.
Conversation with Gemini
Conversation with Gemini
Copy prompt
You said I’m on page “<tabTitle>JY-18091 | Update composer to support php 8.5 by n</tabTitle>” with “<selection>@@ -40,15 +40,15 @@ public function postUsers(Request $request): JsonResponse40 {40 {41$team = $this->getTeamFromRequest($request);41$team = $this->getTeamFromRequest($request);424243-$requestDTO = (new CoreUserRequest())->createFromRequestPayload($request);43+$requestDTO = new CoreUserRequest()->createFromRequestPayload($request);444445if ($requestDTO->getPrimaryPhoneNumber() !== null) {45if ($requestDTO->getPrimaryPhoneNumber() !== null) {46$user = $this->userRepository->findByPhone(46$user = $this->userRepository->findByPhone(47 phone: $requestDTO->getPrimaryPhoneNumber(),47 phone: $requestDTO->getPrimaryPhoneNumber(),48 );48 );494950if ($user !== null && $user->getEmailAddress() !== $requestDTO->getEmailPrimaryWork()) {50if ($user !== null && $user->getEmailAddress() !== $requestDTO->getEmailPrimaryWork()) {51-return (new Error(Constants::HTTP_BAD_REQUEST, 'User primary phone already exists'))51+return new Error(Constants::HTTP_BAD_REQUEST, 'User primary phone already exists')52 ->getResponse();52 ->getResponse();53 }53 }54 }54 }@@ -59,7 +59,7 @@ public function postUsers(Request $request): JsonResponse59 );59 );606061if ($user !== null && $user->getEmailAddress() !== $requestDTO->getEmailPrimaryWork()) {61if ($user !== null && $user->getEmailAddress() !== $requestDTO->getEmailPrimaryWork()) {62-return (new Error(Constants::HTTP_BAD_REQUEST, 'User secondary phone already exists'))62+return new Error(Constants::HTTP_BAD_REQUEST, 'User secondary phone already exists')63 ->getResponse();63 ->getResponse();64 }64 }65 }65 }@@ -74,11 +74,11 @@ public function postUsers(Request $request): JsonResponse74$newUser = $this->scimProvisioning->createOrUpdateNewUser($requestDTO, $team);74$newUser = $this->scimProvisioning->createOrUpdateNewUser($requestDTO, $team);757576if (! $newUser instanceof User) {76if (! $newUser instanceof User) {77-return (new Error(Constants::HTTP_BAD_REQUEST, 'User create operation failed'))77+return new Error(Constants::HTTP_BAD_REQUEST, 'User create operation failed')78 ->getResponse();78 ->getResponse();79 }79 }808081-return (new CoreUser())81+return new CoreUser()82 ->createFromUser($newUser)82 ->createFromUser($newUser)83 ->setExternalId($requestDTO->getUserName())83 ->setExternalId($requestDTO->getUserName())84 ->setHttpCode(Constants::HTTP_CREATED)84 ->setHttpCode(Constants::HTTP_CREATED)@@ -93,22 +93,22 @@ public function putUsers(Request $request, string $id): JsonResponse93$team = $this->getTeamFromRequest($request);93$team = $this->getTeamFromRequest($request);949495if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {95if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {96-return (new Error())->getResponse();96+return new Error()->getResponse();97 }97 }989899-$requestDTO = (new CoreUserRequest())->createFromRequestPayload($request);99+$requestDTO = new CoreUserRequest()->createFromRequestPayload($request);100100101try {101try {102$user = $this->scimProvisioning->updateUserAttributesIfChanged(User::uuid($id), $requestDTO, $team);102$user = $this->scimProvisioning->updateUserAttributesIfChanged(User::uuid($id), $requestDTO, $team);103103104-return (new CoreUser())104+return new CoreUser()105 ->createFromUser($user)105 ->createFromUser($user)106 ->setHttpCode(Constants::HTTP_OK)106 ->setHttpCode(Constants::HTTP_OK)107 ->getResponse();107 ->getResponse();108 } catch (Exception $exception) {108 } catch (Exception $exception) {109 Sentry::captureException($exception);109 Sentry::captureException($exception);110110111-return (new Error(Constants::HTTP_ERROR, 'Operation failed'))->getResponse();111+return new Error(Constants::HTTP_ERROR, 'Operation failed')->getResponse();112 }112 }113 }113 }114114@@ -117,7 +117,7 @@ public function deleteUser(Request $request, string $id): JsonResponse117$team = $this->getTeamFromRequest($request);117$team = $this->getTeamFromRequest($request);118118119if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {119if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {120-return (new Error())->getResponse();120+return new Error()->getResponse();121 }121 }122122123$user = $this->scimProvisioning->getUserById($id);123$user = $this->scimProvisioning->getUserById($id);@@ -131,18 +131,18 @@ public function deleteUser(Request $request, string $id): JsonResponse131 ),131 ),132 );132 );133133134-return (new JsonResponse())->setStatusCode(204);134+return new JsonResponse()->setStatusCode(204);135 }135 }136136137public function patchUser(Request $request, string $id): JsonResponse137public function patchUser(Request $request, string $id): JsonResponse138 {138 {139$team = $this->getTeamFromRequest($request);139$team = $this->getTeamFromRequest($request);140140141if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {141if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {142-return (new Error())->getResponse();142+return new Error()->getResponse();143 }143 }144144145-$requestDTO = (new PatchUser())145+$requestDTO = new PatchUser()146 ->setResourceLocalId($id)146 ->setResourceLocalId($id)147 ->createFromRequestPayload($request);147 ->createFromRequestPayload($request);148148@@ -153,10 +153,10 @@ public function patchUser(Request $request, string $id): JsonResponse153 } catch (Exception $e) {153 } catch (Exception $e) {154 Sentry::captureException($e);154 Sentry::captureException($e);155155156-return (new Error(Constants::HTTP_BAD_REQUEST, 'Operation failed'))->getResponse();156+return new Error(Constants::HTTP_BAD_REQUEST, 'Operation failed')->getResponse();157 }157 }158158159-return (new CoreUser())159+return new CoreUser()160 ->createFromUser($user)160 ->createFromUser($user)161 ->setHttpCode(Constants::HTTP_OK)161 ->setHttpCode(Constants::HTTP_OK)162 ->getResponse();162 ->getResponse();@@ -167,16 +167,16 @@ public function getUser(Request $request, string $id): JsonResponse167$team = $this->getTeamFromRequest($request);167$team = $this->getTeamFromRequest($request);168168169if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {169if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {170-return (new Error())->getResponse();170+return new Error()->getResponse();171 }171 }172172173$user = $this->scimProvisioning->getUserById($id);173$user = $this->scimProvisioning->getUserById($id);174174175if (! $team->isSame($user->getTeam())) {175if (! $team->isSame($user->getTeam())) {176-return (new Error(Constants::HTTP_ACCESS_DENIED, 'Context does not match.'))->getResponse();176+return new Error(Constants::HTTP_ACCESS_DENIED, 'Context does not match.')->getResponse();177 }177 }178178179-return (new CoreUser())179+return new CoreUser()180 ->setHttpCode(Constants::HTTP_OK)180 ->setHttpCode(Constants::HTTP_OK)181 ->createFromUser($user)181 ->createFromUser($user)182 ->getResponse();182 ->getResponse();@@ -186,20 +186,20 @@ public function getUsersByQuery(Request $request): JsonResponse186 {186 {187$team = $this->getTeamFromRequest($request);187$team = $this->getTeamFromRequest($request);188188189-$requestDto = (new GetUserByQuery())->createFromRequestPayload($request);189+$requestDto = new GetUserByQuery()->createFromRequestPayload($request);190190191try {191try {192$resultsQuery = $this->scimProvisioning192$resultsQuery = $this->scimProvisioning193 ->translateQueryStringsToUserModelQuery($requestDto, $team);193 ->translateQueryStringsToUserModelQuery($requestDto, $team);194 } catch (Exception $exception) {194 } catch (Exception $exception) {195-return (new ListResponse())->createEmpty()->getResponse();195+return new ListResponse()->createEmpty()->getResponse();196 }196 }197197198$resultsQuery198$resultsQuery199 ->limit($requestDto->getCount())199 ->limit($requestDto->getCount())200 ->offset($requestDto->getStartIndex() - 1);200 ->offset($requestDto->getStartIndex() - 1);201201202-return (new ListResponse())202+return new ListResponse()203 ->setHttpCode(Constants::HTTP_OK)203 ->setHttpCode(Constants::HTTP_OK)204 ->setStartIndex($requestDto->getStartIndex())204 ->setStartIndex($requestDto->getStartIndex())205 ->createFromCollection($resultsQuery->get())205 ->createFromCollection($resultsQuery->get())@@ -210,15 +210,15 @@ public function postGroups(Request $request): JsonResponse210 {210 {211$team = $this->getTeamFromRequest($request);211$team = $this->getTeamFromRequest($request);212212213-$requestDto = (new CoreGroupRequest())->createFromRequestPayload($request);213+$requestDto = new CoreGroupRequest()->createFromRequestPayload($request);214214215$group = $this->scimProvisioning->createNewGroup($requestDto, $team);215$group = $this->scimProvisioning->createNewGroup($requestDto, $team);216216217if (! $group instanceof Group) {217if (! $group instanceof Group) {218-return (new Error(Constants::HTTP_BAD_REQUEST, 'Group resource create failed'))->getResponse();218+return new Error(Constants::HTTP_BAD_REQUEST, 'Group resource create failed')->getResponse();219 }219 }220220221-$response = (new CoreGroup())->createFromGroup($group)->setHttpCode(Constants::HTTP_CREATED)->getResponse();221+$response = new CoreGroup()->createFromGroup($group)->setHttpCode(Constants::HTTP_CREATED)->getResponse();222222223return $response;223return $response;224 }224 }@@ -228,22 +228,22 @@ public function putGroups(Request $request, string $id): JsonResponse228$team = $this->getTeamFromRequest($request);228$team = $this->getTeamFromRequest($request);229229230if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {230if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {231-return (new Error())->getResponse();231+return new Error()->getResponse();232 }232 }233233234-$requestDTO = (new CoreGroupRequest())->createFromRequestPayload($request);234+$requestDTO = new CoreGroupRequest()->createFromRequestPayload($request);235235236try {236try {237$group = $this->scimProvisioning->updateGroupAttributesIfChanged(Group::uuid($id), $requestDTO, $team);237$group = $this->scimProvisioning->updateGroupAttributesIfChanged(Group::uuid($id), $requestDTO, $team);238238239-return (new CoreGroup())239+return new CoreGroup()240 ->createFromGroup($group)240 ->createFromGroup($group)241 ->setHttpCode(Constants::HTTP_OK)241 ->setHttpCode(Constants::HTTP_OK)242 ->getResponse();242 ->getResponse();243 } catch (Exception $exception) {243 } catch (Exception $exception) {244 Sentry::captureException($exception);244 Sentry::captureException($exception);245245246-return (new Error(Constants::HTTP_ERROR, 'Operation failed'))->getResponse();246+return new Error(Constants::HTTP_ERROR, 'Operation failed')->getResponse();247 }247 }248 }248 }249249@@ -252,12 +252,12 @@ public function getGroup(Request $request, string $id): JsonResponse252$team = $this->getTeamFromRequest($request);252$team = $this->getTeamFromRequest($request);253253254if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {254if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {255-return (new Error())->getResponse();255+return new Error()->getResponse();256 }256 }257257258$group = $this->scimProvisioning->getGroupById($id);258$group = $this->scimProvisioning->getGroupById($id);259259260-return (new CoreGroup())260+return new CoreGroup()261 ->excludeAttributes($request)261 ->excludeAttributes($request)262 ->setHttpCode(Constants::HTTP_OK)262 ->setHttpCode(Constants::HTTP_OK)263 ->createFromGroup($group)263 ->createFromGroup($group)@@ -268,19 +268,19 @@ public function getGroupByQuery(Request $request): JsonResponse268 {268 {269$team = $this->getTeamFromRequest($request);269$team = $this->getTeamFromRequest($request);270270271-$dto = (new GetGroupByQuery())->createFromRequestPayload($request);271+$dto = new GetGroupByQuery()->createFromRequestPayload($request);272272273try {273try {274$groupsQuery = $this->scimProvisioning->translateQueryStringsToGroupModelQuery($dto, $team);274$groupsQuery = $this->scimProvisioning->translateQueryStringsToGroupModelQuery($dto, $team);275 } catch (Exception $exception) {275 } catch (Exception $exception) {276-return (new ListResponse())->createEmpty()->getResponse();276+return new ListResponse()->createEmpty()->getResponse();277 }277 }278278279$groupsQuery279$groupsQuery280 ->limit($dto->getCount())280 ->limit($dto->getCount())281 ->offset($dto->getStartIndex() - 1);281 ->offset($dto->getStartIndex() - 1);282282283-return (new ListResponse())283+return new ListResponse()284 ->setHttpCode(Constants::HTTP_OK)284 ->setHttpCode(Constants::HTTP_OK)285 ->setStartIndex($dto->getStartIndex())285 ->setStartIndex($dto->getStartIndex())286 ->createFromCollection($groupsQuery->get(), $dto->getExcludeAttributes())286 ->createFromCollection($groupsQuery->get(), $dto->getExcludeAttributes())@@ -292,30 +292,30 @@ public function patchGroup(Request $request, $id): JsonResponse292$team = $this->getTeamFromRequest($request);292$team = $this->getTeamFromRequest($request);293293294if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {294if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {295-return (new Error())->getResponse();295+return new Error()->getResponse();296 }296 }297297298-$dto = (new PatchGroup())->createFromRequestPayload($request);298+$dto = new PatchGroup()->createFromRequestPayload($request);299299300$group = Group::uuid($id);300$group = Group::uuid($id);301301302$this->scimProvisioning->patchGroupOperation($dto, $team, $group);302$this->scimProvisioning->patchGroupOperation($dto, $team, $group);303303304-return (new JsonResponse())->setStatusCode(Constants::HTTP_NO_CONTENT);304+return new JsonResponse()->setStatusCode(Constants::HTTP_NO_CONTENT);305 }305 }306306307public function deleteGroup(Request $request, $id): JsonResponse307public function deleteGroup(Request $request, $id): JsonResponse308 {308 {309$team = $this->getTeamFromRequest($request);309$team = $this->getTeamFromRequest($request);310310311if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {311if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {312-return (new Error())->getResponse();312+return new Error()->getResponse();313 }313 }314314315if ($this->scimProvisioning->deleteGroup(Group::uuid($id))) {315if ($this->scimProvisioning->deleteGroup(Group::uuid($id))) {316-return (new JsonResponse())->setStatusCode(Constants::HTTP_NO_CONTENT);316+return new JsonResponse()->setStatusCode(Constants::HTTP_NO_CONTENT);317 } else {317 } else {318-return (new Error(Constants::HTTP_ERROR, 'Operation failed'))->getResponse();318+return new Error(Constants::HTTP_ERROR, 'Operation failed')->getResponse();319 }319 }320 }320 }321321</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>JY-18091 | Update composer to support php 8.5 by n</tabTitle>” with “<selection>@@ -40,15 +40,15 @@ public function postUsers(Request $request): JsonResponse40 {40 {41$team = $this->getTeamFromRequest($request);41$team = $this->getTeamFromRequest($request);424243-$requestDTO = (new CoreUserRequest())->createFromRequestPayload($request);43+$requestDTO = new CoreUserRequest()->createFromRequestPayload($request);444445if ($requestDTO->getPrimaryPhoneNumber() !== null) {45if ($requestDTO->getPrimaryPhoneNumber() !== null) {46$user = $this->userRepository->findByPhone(46$user = $this->userRepository->findByPhone(47 phone: $requestDTO->getPrimaryPhoneNumber(),47 phone: $requestDTO->getPrimaryPhoneNumber(),48 );48 );494950if ($user !== null && $user->getEmailAddress() !== $requestDTO->getEmailPrimaryWork()) {50if ($user !== null && $user->getEmailAddress() !== $requestDTO->getEmailPrimaryWork()) {51-return (new Error(Constants::HTTP_BAD_REQUEST, 'User primary phone already exists'))51+return new Error(Constants::HTTP_BAD_REQUEST, 'User primary phone already exists')52 ->getResponse();52 ->getResponse();53 }53 }54 }54 }@@ -59,7 +59,7 @@ public function postUsers(Request $request): JsonResponse59 );59 );606061if ($user !== null && $user->getEmailAddress() !== $requestDTO->getEmailPrimaryWork()) {61if ($user !== null && $user->getEmailAddress() !== $requestDTO->getEmailPrimaryWork()) {62-return (new Error(Constants::HTTP_BAD_REQUEST, 'User secondary phone already exists'))62+return new Error(Constants::HTTP_BAD_REQUEST, 'User secondary phone already exists')63 ->getResponse();63 ->getResponse();64 }64 }65 }65 }@@ -74,11 +74,11 @@ public function postUsers(Request $request): JsonResponse74$newUser = $this->scimProvisioning->createOrUpdateNewUser($requestDTO, $team);74$newUser = $this->scimProvisioning->createOrUpdateNewUser($requestDTO, $team);757576if (! $newUser instanceof User) {76if (! $newUser instanceof User) {77-return (new Error(Constants::HTTP_BAD_REQUEST, 'User create operation failed'))77+return new Error(Constants::HTTP_BAD_REQUEST, 'User create operation failed')78 ->getResponse();78 ->getResponse();79 }79 }808081-return (new CoreUser())81+return new CoreUser()82 ->createFromUser($newUser)82 ->createFromUser($newUser)83 ->setExternalId($requestDTO->getUserName())83 ->setExternalId($requestDTO->getUserName())84 ->setHttpCode(Constants::HTTP_CREATED)84 ->setHttpCode(Constants::HTTP_CREATED)@@ -93,22 +93,22 @@ public function putUsers(Request $request, string $id): JsonResponse93$team = $this->getTeamFromRequest($request);93$team = $this->getTeamFromRequest($request);949495if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {95if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {96-return (new Error())->getResponse();96+return new Error()->getResponse();97 }97 }989899-$requestDTO = (new CoreUserRequest())->createFromRequestPayload($request);99+$requestDTO = new CoreUserRequest()->createFromRequestPayload($request);100100101try {101try {102$user = $this->scimProvisioning->updateUserAttributesIfChanged(User::uuid($id), $requestDTO, $team);102$user = $this->scimProvisioning->updateUserAttributesIfChanged(User::uuid($id), $requestDTO, $team);103103104-return (new CoreUser())104+return new CoreUser()105 ->createFromUser($user)105 ->createFromUser($user)106 ->setHttpCode(Constants::HTTP_OK)106 ->setHttpCode(Constants::HTTP_OK)107 ->getResponse();107 ->getResponse();108 } catch (Exception $exception) {108 } catch (Exception $exception) {109 Sentry::captureException($exception);109 Sentry::captureException($exception);110110111-return (new Error(Constants::HTTP_ERROR, 'Operation failed'))->getResponse();111+return new Error(Constants::HTTP_ERROR, 'Operation failed')->getResponse();112 }112 }113 }113 }114114@@ -117,7 +117,7 @@ public function deleteUser(Request $request, string $id): JsonResponse117$team = $this->getTeamFromRequest($request);117$team = $this->getTeamFromRequest($request);118118119if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {119if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {120-return (new Error())->getResponse();120+return new Error()->getResponse();121 }121 }122122123$user = $this->scimProvisioning->getUserById($id);123$user = $this->scimProvisioning->getUserById($id);@@ -131,18 +131,18 @@ public function deleteUser(Request $request, string $id): JsonResponse131 ),131 ),132 );132 );133133134-return (new JsonResponse())->setStatusCode(204);134+return new JsonResponse()->setStatusCode(204);135 }135 }136136137public function patchUser(Request $request, string $id): JsonResponse137public function patchUser(Request $request, string $id): JsonResponse138 {138 {139$team = $this->getTeamFromRequest($request);139$team = $this->getTeamFromRequest($request);140140141if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {141if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {142-return (new Error())->getResponse();142+return new Error()->getResponse();143 }143 }144144145-$requestDTO = (new PatchUser())145+$requestDTO = new PatchUser()146 ->setResourceLocalId($id)146 ->setResourceLocalId($id)147 ->createFromRequestPayload($request);147 ->createFromRequestPayload($request);148148@@ -153,10 +153,10 @@ public function patchUser(Request $request, string $id): JsonResponse153 } catch (Exception $e) {153 } catch (Exception $e) {154 Sentry::captureException($e);154 Sentry::captureException($e);155155156-return (new Error(Constants::HTTP_BAD_REQUEST, 'Operation failed'))->getResponse();156+return new Error(Constants::HTTP_BAD_REQUEST, 'Operation failed')->getResponse();157 }157 }158158159-return (new CoreUser())159+return new CoreUser()160 ->createFromUser($user)160 ->createFromUser($user)161 ->setHttpCode(Constants::HTTP_OK)161 ->setHttpCode(Constants::HTTP_OK)162 ->getResponse();162 ->getResponse();@@ -167,16 +167,16 @@ public function getUser(Request $request, string $id): JsonResponse167$team = $this->getTeamFromRequest($request);167$team = $this->getTeamFromRequest($request);168168169if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {169if (! $this->scimProvisioning->validateUserIdInput($id, $team)) {170-return (new Error())->getResponse();170+return new Error()->getResponse();171 }171 }172172173$user = $this->scimProvisioning->getUserById($id);173$user = $this->scimProvisioning->getUserById($id);174174175if (! $team->isSame($user->getTeam())) {175if (! $team->isSame($user->getTeam())) {176-return (new Error(Constants::HTTP_ACCESS_DENIED, 'Context does not match.'))->getResponse();176+return new Error(Constants::HTTP_ACCESS_DENIED, 'Context does not match.')->getResponse();177 }177 }178178179-return (new CoreUser())179+return new CoreUser()180 ->setHttpCode(Constants::HTTP_OK)180 ->setHttpCode(Constants::HTTP_OK)181 ->createFromUser($user)181 ->createFromUser($user)182 ->getResponse();182 ->getResponse();@@ -186,20 +186,20 @@ public function getUsersByQuery(Request $request): JsonResponse186 {186 {187$team = $this->getTeamFromRequest($request);187$team = $this->getTeamFromRequest($request);188188189-$requestDto = (new GetUserByQuery())->createFromRequestPayload($request);189+$requestDto = new GetUserByQuery()->createFromRequestPayload($request);190190191try {191try {192$resultsQuery = $this->scimProvisioning192$resultsQuery = $this->scimProvisioning193 ->translateQueryStringsToUserModelQuery($requestDto, $team);193 ->translateQueryStringsToUserModelQuery($requestDto, $team);194 } catch (Exception $exception) {194 } catch (Exception $exception) {195-return (new ListResponse())->createEmpty()->getResponse();195+return new ListResponse()->createEmpty()->getResponse();196 }196 }197197198$resultsQuery198$resultsQuery199 ->limit($requestDto->getCount())199 ->limit($requestDto->getCount())200 ->offset($requestDto->getStartIndex() - 1);200 ->offset($requestDto->getStartIndex() - 1);201201202-return (new ListResponse())202+return new ListResponse()203 ->setHttpCode(Constants::HTTP_OK)203 ->setHttpCode(Constants::HTTP_OK)204 ->setStartIndex($requestDto->getStartIndex())204 ->setStartIndex($requestDto->getStartIndex())205 ->createFromCollection($resultsQuery->get())205 ->createFromCollection($resultsQuery->get())@@ -210,15 +210,15 @@ public function postGroups(Request $request): JsonResponse210 {210 {211$team = $this->getTeamFromRequest($request);211$team = $this->getTeamFromRequest($request);212212213-$requestDto = (new CoreGroupRequest())->createFromRequestPayload($request);213+$requestDto = new CoreGroupRequest()->createFromRequestPayload($request);214214215$group = $this->scimProvisioning->createNewGroup($requestDto, $team);215$group = $this->scimProvisioning->createNewGroup($requestDto, $team);216216217if (! $group instanceof Group) {217if (! $group instanceof Group) {218-return (new Error(Constants::HTTP_BAD_REQUEST, 'Group resource create failed'))->getResponse();218+return new Error(Constants::HTTP_BAD_REQUEST, 'Group resource create failed')->getResponse();219 }219 }220220221-$response = (new CoreGroup())->createFromGroup($group)->setHttpCode(Constants::HTTP_CREATED)->getResponse();221+$response = new CoreGroup()->createFromGroup($group)->setHttpCode(Constants::HTTP_CREATED)->getResponse();222222223return $response;223return $response;224 }224 }@@ -228,22 +228,22 @@ public function putGroups(Request $request, string $id): JsonResponse228$team = $this->getTeamFromRequest($request);228$team = $this->getTeamFromRequest($request);229229230if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {230if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {231-return (new Error())->getResponse();231+return new Error()->getResponse();232 }232 }233233234-$requestDTO = (new CoreGroupRequest())->createFromRequestPayload($request);234+$requestDTO = new CoreGroupRequest()->createFromRequestPayload($request);235235236try {236try {237$group = $this->scimProvisioning->updateGroupAttributesIfChanged(Group::uuid($id), $requestDTO, $team);237$group = $this->scimProvisioning->updateGroupAttributesIfChanged(Group::uuid($id), $requestDTO, $team);238238239-return (new CoreGroup())239+return new CoreGroup()240 ->createFromGroup($group)240 ->createFromGroup($group)241 ->setHttpCode(Constants::HTTP_OK)241 ->setHttpCode(Constants::HTTP_OK)242 ->getResponse();242 ->getResponse();243 } catch (Exception $exception) {243 } catch (Exception $exception) {244 Sentry::captureException($exception);244 Sentry::captureException($exception);245245246-return (new Error(Constants::HTTP_ERROR, 'Operation failed'))->getResponse();246+return new Error(Constants::HTTP_ERROR, 'Operation failed')->getResponse();247 }247 }248 }248 }249249@@ -252,12 +252,12 @@ public function getGroup(Request $request, string $id): JsonResponse252$team = $this->getTeamFromRequest($request);252$team = $this->getTeamFromRequest($request);253253254if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {254if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {255-return (new Error())->getResponse();255+return new Error()->getResponse();256 }256 }257257258$group = $this->scimProvisioning->getGroupById($id);258$group = $this->scimProvisioning->getGroupById($id);259259260-return (new CoreGroup())260+return new CoreGroup()261 ->excludeAttributes($request)261 ->excludeAttributes($request)262 ->setHttpCode(Constants::HTTP_OK)262 ->setHttpCode(Constants::HTTP_OK)263 ->createFromGroup($group)263 ->createFromGroup($group)@@ -268,19 +268,19 @@ public function getGroupByQuery(Request $request): JsonResponse268 {268 {269$team = $this->getTeamFromRequest($request);269$team = $this->getTeamFromRequest($request);270270271-$dto = (new GetGroupByQuery())->createFromRequestPayload($request);271+$dto = new GetGroupByQuery()->createFromRequestPayload($request);272272273try {273try {274$groupsQuery = $this->scimProvisioning->translateQueryStringsToGroupModelQuery($dto, $team);274$groupsQuery = $this->scimProvisioning->translateQueryStringsToGroupModelQuery($dto, $team);275 } catch (Exception $exception) {275 } catch (Exception $exception) {276-return (new ListResponse())->createEmpty()->getResponse();276+return new ListResponse()->createEmpty()->getResponse();277 }277 }278278279$groupsQuery279$groupsQuery280 ->limit($dto->getCount())280 ->limit($dto->getCount())281 ->offset($dto->getStartIndex() - 1);281 ->offset($dto->getStartIndex() - 1);282282283-return (new ListResponse())283+return new ListResponse()284 ->setHttpCode(Constants::HTTP_OK)284 ->setHttpCode(Constants::HTTP_OK)285 ->setStartIndex($dto->getStartIndex())285 ->setStartIndex($dto->getStartIndex())286 ->createFromCollection($groupsQuery->get(), $dto->getExcludeAttributes())286 ->createFromCollection($groupsQuery->get(), $dto->getExcludeAttributes())@@ -292,30 +292,30 @@ public function patchGroup(Request $request, $id): JsonResponse292$team = $this->getTeamFromRequest($request);292$team = $this->getTeamFromRequest($request);293293294if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {294if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {295-return (new Error())->getResponse();295+return new Error()->getResponse();296 }296 }297297298-$dto = (new PatchGroup())->createFromRequestPayload($request);298+$dto = new PatchGroup()->createFromRequestPayload($request);299299300$group = Group::uuid($id);300$group = Group::uuid($id);301301302$this->scimProvisioning->patchGroupOperation($dto, $team, $group);302$this->scimProvisioning->patchGroupOperation($dto, $team, $group);303303304-return (new JsonResponse())->setStatusCode(Constants::HTTP_NO_CONTENT);304+return new JsonResponse()->setStatusCode(Constants::HTTP_NO_CONTENT);305 }305 }306306307public function deleteGroup(Request $request, $id): JsonResponse307public function deleteGroup(Request $request, $id): JsonResponse308 {308 {309$team = $this->getTeamFromRequest($request);309$team = $this->getTeamFromRequest($request);310310311if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {311if (! $this->scimProvisioning->validateGroupIdInput($id, $team)) {312-return (new Error())->getResponse();312+return new Error()->getResponse();313 }313 }314314315if ($this->scimProvisioning->deleteGroup(Group::uuid($id))) {315if ($this->scimProvisioning->deleteGroup(Group::uuid($id))) {316-return (new JsonResponse())->setStatusCode(Constants::HTTP_NO_CONTENT);316+return new JsonResponse()->setStatusCode(Constants::HTTP_NO_CONTENT);317 } else {317 } else {318-return (new Error(Constants::HTTP_ERROR, 'Operation failed'))->getResponse();318+return new Error(Constants::HTTP_ERROR, 'Operation failed')->getResponse();319 }319 }320 }320 }321321</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Listen
Gemini said
Gemini said
The selected code changes represent a mechanical refactoring of a PHP controller to align with modern syntax standards, likely as part of the upgrade to
PHP 8.5
.
Core Change: Member Access on Instantiation
Core Change: Member Access on Instantiation
The primary modification across the entire file is the removal of unnecessary parentheses when performing member access (method chaining) on a newly instantiated object....
|
42930
|
NULL
|
NULL
|
NULL
|
|
25084
|
1052
|
26
|
2026-05-12T10:42:32.049545+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-12/1778 /Users/lukas/.screenpipe/data/data/2026-05-12/1778582552049_m1.jpg...
|
Firefox
|
JY-20773 fix user pilot tracking ofr automated rep JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12024
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Unnamed Group
TypeError: League\Flysystem\Filesyst Unnamed Group
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
CloudWatch | us-east-2
CloudWatch | us-east-2
Unnamed Group
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
Pull requests · jiminny/app
Pull requests · jiminny/app
[JY-20773] User Pilot not receiving events on report generated - Jira
[JY-20773] User Pilot not receiving events on report generated - Jira
JY-20773 fix user pilot tracking for automated report generated by LakyLak · Pull Request #12024 · jiminny/app
JY-20773 fix user pilot tracking for automated report generated by LakyLak · Pull Request #12024 · jiminny/app
[JY-20776] Automated report - sentry - Jira
[JY-20776] Automated report - sentry - Jira
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
JY-20625 | JY-20742 | MCP POC by yalokin-jiminny · Pull Request #12036 · jiminny/app
JY-20625 | JY-20742 | MCP POC by yalokin-jiminny · Pull Request #12036 · jiminny/app
Data Explorer
Data Explorer
[JY-20776] Automated report - sentry - Jira
[JY-20776] Automated report - sentry - Jira
Jiminny
Jiminny
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
Platform Team - Backlog - Jira
Platform Team - Backlog - Jira
[JY-20773] User Pilot not receiving events on report generated - Jira
[JY-20773] User Pilot not receiving events on report generated - Jira
JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app
JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app
Close tab
Pipelines - jiminny/app
Pipelines - jiminny/app
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
All issues(g then i)
All pull requests
All repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (32)
Pull requests
(
32
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (4)
Security and quality
(
4
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
JY-20773 fix user pilot tracking for automated report generated #12024 Edit title
JY-20773 fix user pilot tracking for automated report generated
#
12024
Edit title
Awaiting approval
Awaiting approval
Code
Code
Open
LakyLak
LakyLak
wants to merge 2 commits into
master
master
from
JY-20773-fix-automated-reports-user-pilot-tracking
JY-20773-fix-automated-reports-user-pilot-tracking
Copy head branch name to clipboard
Lines changed: 3 additions & 0 deletions
Conversation (1)
Conversation
(
1
)
Commits (2)
Commits
(
2
)
Checks (3)
Checks
(
3
)
Files changed (1)
Files changed
(
1
)
Conversation...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"TypeError: League\\Flysystem\\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"TypeError: League\\Flysystem\\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pull requests · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20773] User Pilot not receiving events on report generated - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20773] User Pilot not receiving events on report generated - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20773 fix user pilot tracking for automated report generated by LakyLak · Pull Request #12024 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20773 fix user pilot tracking for automated report generated by LakyLak · Pull Request #12024 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20776] Automated report - sentry - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20776] Automated report - sentry - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20625 | JY-20742 | MCP POC by yalokin-jiminny · Pull Request #12036 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20625 | JY-20742 | MCP POC by yalokin-jiminny · Pull Request #12036 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Data Explorer","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Data Explorer","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20776] Automated report - sentry - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20776] Automated report - sentry - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Team - Backlog - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Team - Backlog - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20773] User Pilot not receiving events on report generated - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20773] User Pilot not receiving events on report generated - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to content","depth":6,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"All issues(g then i)","depth":9,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All pull requests","depth":9,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All repositories","depth":9,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (32)","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"32","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Agents","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Agents","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security and quality (4)","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security and quality","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Important update","depth":10,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Important update","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Review this update","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Review this update","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and manage your preferences in your","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"GitHub account settings","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"GitHub account settings","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Dismiss banner","depth":9,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"JY-20773 fix user pilot tracking for automated report generated #12024 Edit title","depth":13,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JY-20773 fix user pilot tracking for automated report generated","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12024","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit title","depth":14,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Awaiting approval","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Awaiting approval","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Code","depth":13,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Code","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"LakyLak","depth":15,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 2 commits into","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":15,"on_screen":true,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-20773-fix-automated-reports-user-pilot-tracking","depth":16,"on_screen":true,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20773-fix-automated-reports-user-pilot-tracking","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":16,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lines changed: 3 additions & 0 deletions","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Conversation (1)","depth":16,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Conversation","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Commits (2)","depth":16,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Commits","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Checks (3)","depth":16,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Files changed (1)","depth":16,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Files changed","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Conversation","depth":12,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"}]...
|
-9092849697388020852
|
-426343107148416896
|
click
|
accessibility
|
NULL
|
Unnamed Group
TypeError: League\Flysystem\Filesyst Unnamed Group
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
CloudWatch | us-east-2
CloudWatch | us-east-2
Unnamed Group
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
Pull requests · jiminny/app
Pull requests · jiminny/app
[JY-20773] User Pilot not receiving events on report generated - Jira
[JY-20773] User Pilot not receiving events on report generated - Jira
JY-20773 fix user pilot tracking for automated report generated by LakyLak · Pull Request #12024 · jiminny/app
JY-20773 fix user pilot tracking for automated report generated by LakyLak · Pull Request #12024 · jiminny/app
[JY-20776] Automated report - sentry - Jira
[JY-20776] Automated report - sentry - Jira
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
JY-20625 | JY-20742 | MCP POC by yalokin-jiminny · Pull Request #12036 · jiminny/app
JY-20625 | JY-20742 | MCP POC by yalokin-jiminny · Pull Request #12036 · jiminny/app
Data Explorer
Data Explorer
[JY-20776] Automated report - sentry - Jira
[JY-20776] Automated report - sentry - Jira
Jiminny
Jiminny
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
Platform Team - Backlog - Jira
Platform Team - Backlog - Jira
[JY-20773] User Pilot not receiving events on report generated - Jira
[JY-20773] User Pilot not receiving events on report generated - Jira
JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app
JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app
Close tab
Pipelines - jiminny/app
Pipelines - jiminny/app
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
All issues(g then i)
All pull requests
All repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (32)
Pull requests
(
32
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (4)
Security and quality
(
4
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
JY-20773 fix user pilot tracking for automated report generated #12024 Edit title
JY-20773 fix user pilot tracking for automated report generated
#
12024
Edit title
Awaiting approval
Awaiting approval
Code
Code
Open
LakyLak
LakyLak
wants to merge 2 commits into
master
master
from
JY-20773-fix-automated-reports-user-pilot-tracking
JY-20773-fix-automated-reports-user-pilot-tracking
Copy head branch name to clipboard
Lines changed: 3 additions & 0 deletions
Conversation (1)
Conversation
(
1
)
Commits (2)
Commits
(
2
)
Checks (3)
Checks
(
3
)
Files changed (1)
Files changed
(
1
)
Conversation...
|
25082
|
NULL
|
NULL
|
NULL
|
|
3192
|
123
|
10
|
2026-05-07T12:06:01.015973+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778155561015_m1.jpg...
|
iTerm2
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
PostmanFileEditViewWindowHelp$0lohlSupport Daily • PostmanFileEditViewWindowHelp$0lohlSupport Daily • now100% <8DEV (docker)DOCKERO ₴1DEV (docker)H82APP (-zsh)-zsh• $4screenpipe"•$55.95ms DONE-zshThu 7 May 15:06:00T81₴6viewsjiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00: stoppedworker-nudges:worker-nudges_00:stoppedjiminny-worker-processing-2:jiminny-worker-processing-2_00: stoppedJiminny-worker-processing-3:7iminny-worker-processing-3_00: stoppedjiminny-worker-processing-4:jiminny-worker-processing-4_00:stoppedjiminny-worker-processing-5: jiminny-worker-processing-5_00: stoppedworker-crm-update:worker-crm-update_00: stoppedworker-analytics:worker-analytics_00: stoppedworker-download:worker-download_00: stoppedworker:worker_00: stoppedjiminny-worker-processing-1:jiminny-worker-processing-1_00: stoppedworker-calendar:worker-calendar_00:stoppedworker-conferences:worker-conferences_00: stoppedworker-crm-sync:worker-crm-sync_00: stoppedworker-audio:worker-audio_00: stoppedworker-emails:worker-emails_00: stoppedartisan-schedule:artisan-schedule_00: stoppedworker-es-update:worker-es-update_00: stoppedartisan-schedule:artisan-schedule_00: startedjiminny-worker-processing-1:jiminny-worker-processing-1_00: startedjiminny-worker-processing-2:1iminny-worker-processing-2 00: startedjiminny-worker-processing-3:jiminny-worker-processing-3_00: startedjiminny-worker-processing-4:jiminny-worker-processing-4_00: startedjiminny-worker-processing-5:jiminny-worker-processing-5_00: startedjiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00: startedworker:worker_00: startedworker-analytics:worker-analytics_00: startedworker-audio:worker-audio_00: startedworker-calendar:worker-calendar_00: startedworker-conferences:worker-conferences_00: startedworker-crm-sync:worker-crm-sync_00: startedworker-crm-update:worker-crm-update_00: startedworker-download:worker-download_00: startedworker-emails:worker-emails_00: startedworker-es-update:worker-es-update_00: startedworker-nudges:worker-nudges_00: startedroot@docker_lamp_1:/home/jiminny# php artisan jiminny:debugSyncing opportunity 0Syncing opportunity 25Syncing opportunity 50Syncing opportunity 75Syncing opportunity 100root@docker_lamp_1:/home/jiminny# ]DEV...
|
NULL
|
-9092464748005920751
|
NULL
|
click
|
ocr
|
NULL
|
PostmanFileEditViewWindowHelp$0lohlSupport Daily • PostmanFileEditViewWindowHelp$0lohlSupport Daily • now100% <8DEV (docker)DOCKERO ₴1DEV (docker)H82APP (-zsh)-zsh• $4screenpipe"•$55.95ms DONE-zshThu 7 May 15:06:00T81₴6viewsjiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00: stoppedworker-nudges:worker-nudges_00:stoppedjiminny-worker-processing-2:jiminny-worker-processing-2_00: stoppedJiminny-worker-processing-3:7iminny-worker-processing-3_00: stoppedjiminny-worker-processing-4:jiminny-worker-processing-4_00:stoppedjiminny-worker-processing-5: jiminny-worker-processing-5_00: stoppedworker-crm-update:worker-crm-update_00: stoppedworker-analytics:worker-analytics_00: stoppedworker-download:worker-download_00: stoppedworker:worker_00: stoppedjiminny-worker-processing-1:jiminny-worker-processing-1_00: stoppedworker-calendar:worker-calendar_00:stoppedworker-conferences:worker-conferences_00: stoppedworker-crm-sync:worker-crm-sync_00: stoppedworker-audio:worker-audio_00: stoppedworker-emails:worker-emails_00: stoppedartisan-schedule:artisan-schedule_00: stoppedworker-es-update:worker-es-update_00: stoppedartisan-schedule:artisan-schedule_00: startedjiminny-worker-processing-1:jiminny-worker-processing-1_00: startedjiminny-worker-processing-2:1iminny-worker-processing-2 00: startedjiminny-worker-processing-3:jiminny-worker-processing-3_00: startedjiminny-worker-processing-4:jiminny-worker-processing-4_00: startedjiminny-worker-processing-5:jiminny-worker-processing-5_00: startedjiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00: startedworker:worker_00: startedworker-analytics:worker-analytics_00: startedworker-audio:worker-audio_00: startedworker-calendar:worker-calendar_00: startedworker-conferences:worker-conferences_00: startedworker-crm-sync:worker-crm-sync_00: startedworker-crm-update:worker-crm-update_00: startedworker-download:worker-download_00: startedworker-emails:worker-emails_00: startedworker-es-update:worker-es-update_00: startedworker-nudges:worker-nudges_00: startedroot@docker_lamp_1:/home/jiminny# php artisan jiminny:debugSyncing opportunity 0Syncing opportunity 25Syncing opportunity 50Syncing opportunity 75Syncing opportunity 100root@docker_lamp_1:/home/jiminny# ]DEV...
|
3190
|
NULL
|
NULL
|
NULL
|
|
82845
|
2865
|
11
|
2026-05-28T09:54:28.045101+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779962068045_m1.jpg...
|
Firefox
|
Jiminny Mail — Work
|
1
|
mail.google.com/mail/u/0/#inbox?compose=new
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix missing header by LakyLak · Pull Request #12136 · jiminny/app
JY-20915 fix missing header by LakyLak · Pull Request #12136 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny Mail
Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Unnamed Group
Jiminny
Jiminny
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Sona Subramanian at 27/05/2026, 17:46:08 - Session Replay - LogRocket
Sona Subramanian at 27/05/2026, 17:46:08 - Session Replay - LogRocket
Iliyana Netseva at 27/05/2026, 18:48:18 - Session Replay - LogRocket
Iliyana Netseva at 27/05/2026, 18:48:18 - Session Replay - LogRocket
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
You have one new message.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Search or ask a question
Search chat
Status: Active
Support
Settings
Studio
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1732 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1732 unread
Inbox
1,732
Starred
Starred
Snoozed
Snoozed
Sent
Sent
Drafts 3 unread
Drafts
3
Collapse label: Categories
Categories expanded
Categories
Purchases 2 unread has menu
Purchases
2
More labels
More
Labels
Labels
Create new label
Labels
Labels
App emails has menu
App emails
Collapse label: Github
Github 738 unread expanded has menu
Github
738
app 5649 unread has menu
app
5,649
extension 217 unread has menu
extension
217
vuejs has menu
vuejs
JIRA has menu
JIRA
Collapse label: Notes
Notes expanded has menu
Notes
Shared has menu
Shared
Sentry 103254 unread has menu
Sentry
103,254
Utilities has menu
Utilities
New chat
New chat
Shortcuts
Shortcuts
Home
Mentions
star
Starred
Ask Gemini
Direct messages 31 unread messages
Direct messages
31
Unread Daily - Platform - May 28Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
Daily - Platform - May 28
Meeting conversation
Open in a pop-up
Options
Unread [Platform] Refinement - May 26Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
[Platform] Refinement
- May 26...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"BE upgrade libraries","depth":4,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Text relay","depth":4,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 fix missing header by LakyLak · Pull Request #12136 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 fix missing header by LakyLak · Pull Request #12136 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Login | Salesforce","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Login | Salesforce","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny Mail","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Jiminny Mail","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Sona Subramanian at 27/05/2026, 17:46:08 - Session Replay - LogRocket","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sona Subramanian at 27/05/2026, 17:46:08 - Session Replay - LogRocket","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Iliyana Netseva at 27/05/2026, 18:48:18 - Session Replay - LogRocket","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Iliyana Netseva at 27/05/2026, 18:48:18 - Session Replay - LogRocket","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"You have one new message.","depth":2,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Skip to content","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Using Jiminny Mail with screen readers","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Using Jiminny Mail with screen readers","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Main menu","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXLink","text":"Gmail","depth":6,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Search mail","depth":6,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Search mail","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"Search or ask a question","depth":12,"on_screen":true,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Search chat","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Status: Active","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Support","depth":7,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Settings","depth":7,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Studio","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Google apps","depth":8,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":9,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Mail, 1732 unread messages","depth":3,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Mail","depth":3,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mail","depth":4,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat, 32 unread messages","depth":3,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"32 new messages","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Chat","depth":3,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Chat","depth":4,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Meet","depth":3,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Meet","depth":3,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meet","depth":4,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Compose","depth":3,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":7,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Inbox 1732 unread","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false},{"role":"AXStaticText","text":"Inbox","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1,732","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Starred","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Starred","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Snoozed","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Snoozed","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sent","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sent","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Drafts 3 unread","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Drafts","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Categories","depth":9,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Categories expanded","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Categories","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Purchases 2 unread has menu","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Purchases","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"More labels","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"More","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Labels","depth":6,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create new label","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":7,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"App emails has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"App emails","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Github","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Github 738 unread expanded has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Github","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"738","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app 5649 unread has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5,649","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"extension 217 unread has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"extension","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"217","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"vuejs has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"vuejs","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JIRA has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JIRA","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Notes","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Notes expanded has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notes","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Shared has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Shared","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sentry 103254 unread has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sentry","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"103,254","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Utilities has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Utilities","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New chat","depth":2,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New chat","depth":4,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Shortcuts","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Shortcuts","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Home","depth":6,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mentions","depth":6,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"star","depth":6,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Starred","depth":6,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Ask Gemini","depth":6,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Direct messages 31 unread messages","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Direct messages","depth":6,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"31","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Unread Daily - Platform - May 28Meeting conversation Press tab for more options or Option + 0 to preview the last message.","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Unread","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Daily - Platform - May 28","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meeting conversation","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open in a pop-up","depth":14,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Options","depth":14,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Unread [Platform] Refinement - May 26Meeting conversation Press tab for more options or Option + 0 to preview the last message.","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Unread","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[Platform] Refinement","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"- May 26","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-9092457490658667550
|
-1132800849227483923
|
click
|
accessibility
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix missing header by LakyLak · Pull Request #12136 · jiminny/app
JY-20915 fix missing header by LakyLak · Pull Request #12136 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny Mail
Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Unnamed Group
Jiminny
Jiminny
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Sona Subramanian at 27/05/2026, 17:46:08 - Session Replay - LogRocket
Sona Subramanian at 27/05/2026, 17:46:08 - Session Replay - LogRocket
Iliyana Netseva at 27/05/2026, 18:48:18 - Session Replay - LogRocket
Iliyana Netseva at 27/05/2026, 18:48:18 - Session Replay - LogRocket
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
You have one new message.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Search or ask a question
Search chat
Status: Active
Support
Settings
Studio
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1732 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1732 unread
Inbox
1,732
Starred
Starred
Snoozed
Snoozed
Sent
Sent
Drafts 3 unread
Drafts
3
Collapse label: Categories
Categories expanded
Categories
Purchases 2 unread has menu
Purchases
2
More labels
More
Labels
Labels
Create new label
Labels
Labels
App emails has menu
App emails
Collapse label: Github
Github 738 unread expanded has menu
Github
738
app 5649 unread has menu
app
5,649
extension 217 unread has menu
extension
217
vuejs has menu
vuejs
JIRA has menu
JIRA
Collapse label: Notes
Notes expanded has menu
Notes
Shared has menu
Shared
Sentry 103254 unread has menu
Sentry
103,254
Utilities has menu
Utilities
New chat
New chat
Shortcuts
Shortcuts
Home
Mentions
star
Starred
Ask Gemini
Direct messages 31 unread messages
Direct messages
31
Unread Daily - Platform - May 28Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
Daily - Platform - May 28
Meeting conversation
Open in a pop-up
Options
Unread [Platform] Refinement - May 26Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
[Platform] Refinement
- May 26...
|
82842
|
NULL
|
NULL
|
NULL
|
|
17045
|
762
|
17
|
2026-05-11T09:59:44.371650+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778493584371_m1.jpg...
|
PhpStorm
|
faVsco.js – HandleHubspotRateLimitTest.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Tests\Unit\Jobs\Middleware;
use Exception;
use Illuminate\Contracts\Queue\Job;
use Illuminate\Support\Facades\Log;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
#[CoversClass(HandleHubspotRateLimit::class)]
class HandleHubspotRateLimitTest extends TestCase
{
private const int MAX_RETRY_DELAY = 600;
private const int MIN_RETRY_DELAY = 1;
private const int JITTER_SECONDS = 5;
private HandleHubspotRateLimit $middleware;
protected function setUp(): void
{
parent::setUp();
$this->middleware = new HandleHubspotRateLimit();
}
public function testPassesThroughWhenNoExceptionThrown(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$called = false;
$next = function (object $passed) use ($job, &$called): void {
$this->assertSame($job, $passed);
$called = true;
};
$this->middleware->handle($job, $next);
$this->assertTrue($called);
}
public function testPropagatesNonRateLimitExceptions(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$next = static function (): void {
throw new Exception('Database is down');
};
$this->expectException(Exception::class);
$this->expectExceptionMessage('Database is down');
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>
*/
public static function delayClampingProvider(): array
{
return [
'short retry uses retry_after as floor' => [
'retryAfter' => 1,
'expectedMin' => self::MIN_RETRY_DELAY,
'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,
],
'medium retry passes through' => [
'retryAfter' => 30,
'expectedMin' => 30,
'expectedMax' => 30 + self::JITTER_SECONDS,
],
'large retry clamped to max' => [
'retryAfter' => 86400,
'expectedMin' => self::MAX_RETRY_DELAY,
'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,
],
];
}
#[DataProvider('delayClampingProvider')]
public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void
{
Log::shouldReceive('info')->zeroOrMoreTimes();
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn(1);
$job->expects($this->once())
->method('release')
->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {
return $delay >= $expectedMin && $delay <= $expectedMax;
}));
$next = static function () use ($retryAfter): void {
throw new RateLimitException('rate limited', $retryAfter);
};
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{attempts: int, shouldLog: bool}>
*/
public static function logSamplingProvider(): array
{
return [
'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],
'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],
'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],
'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],
'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],
'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],
'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],
'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],
];
}
#[DataProvider('logSamplingProvider')]
public function testLogSampling(int $attempts, bool $shouldLog): void
{
if ($shouldLog) {
Log::shouldReceive('info')
->once()
->with(
'[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',
$this->callback(static function (array $context) use ($attempts): bool {
return $context['attempts'] === $attempts
&& $context['retry_after'] === 1
&& isset($context['delay']);
})
);
} else {
Log::shouldReceive('info')->never();
}
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn($attempts);
$job->expects($this->once())->method('release');
$next = static function (): void {
throw new RateLimitException('rate limited', 1);
};
$this->middleware->handle($job, $next);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
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":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","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":"HandleHubspotRateLimitTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'HandleHubspotRateLimitTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'HandleHubspotRateLimitTest'","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 Tests\\Unit\\Jobs\\Middleware;\n\nuse Exception;\nuse Illuminate\\Contracts\\Queue\\Job;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Jobs\\Middleware\\HandleHubspotRateLimit;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\n#[CoversClass(HandleHubspotRateLimit::class)]\nclass HandleHubspotRateLimitTest extends TestCase\n{\n private const int MAX_RETRY_DELAY = 600;\n private const int MIN_RETRY_DELAY = 1;\n private const int JITTER_SECONDS = 5;\n\n private HandleHubspotRateLimit $middleware;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n $this->middleware = new HandleHubspotRateLimit();\n }\n\n public function testPassesThroughWhenNoExceptionThrown(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $called = false;\n $next = function (object $passed) use ($job, &$called): void {\n $this->assertSame($job, $passed);\n $called = true;\n };\n\n $this->middleware->handle($job, $next);\n\n $this->assertTrue($called);\n }\n\n public function testPropagatesNonRateLimitExceptions(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $next = static function (): void {\n throw new Exception('Database is down');\n };\n\n $this->expectException(Exception::class);\n $this->expectExceptionMessage('Database is down');\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>\n */\n public static function delayClampingProvider(): array\n {\n return [\n 'short retry uses retry_after as floor' => [\n 'retryAfter' => 1,\n 'expectedMin' => self::MIN_RETRY_DELAY,\n 'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n 'medium retry passes through' => [\n 'retryAfter' => 30,\n 'expectedMin' => 30,\n 'expectedMax' => 30 + self::JITTER_SECONDS,\n ],\n 'large retry clamped to max' => [\n 'retryAfter' => 86400,\n 'expectedMin' => self::MAX_RETRY_DELAY,\n 'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n ];\n }\n\n #[DataProvider('delayClampingProvider')]\n public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void\n {\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn(1);\n $job->expects($this->once())\n ->method('release')\n ->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {\n return $delay >= $expectedMin && $delay <= $expectedMax;\n }));\n\n $next = static function () use ($retryAfter): void {\n throw new RateLimitException('rate limited', $retryAfter);\n };\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{attempts: int, shouldLog: bool}>\n */\n public static function logSamplingProvider(): array\n {\n return [\n 'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],\n 'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],\n 'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],\n 'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],\n 'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],\n 'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],\n 'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],\n 'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],\n ];\n }\n\n #[DataProvider('logSamplingProvider')]\n public function testLogSampling(int $attempts, bool $shouldLog): void\n {\n if ($shouldLog) {\n Log::shouldReceive('info')\n ->once()\n ->with(\n '[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',\n $this->callback(static function (array $context) use ($attempts): bool {\n return $context['attempts'] === $attempts\n && $context['retry_after'] === 1\n && isset($context['delay']);\n })\n );\n } else {\n Log::shouldReceive('info')->never();\n }\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn($attempts);\n $job->expects($this->once())->method('release');\n\n $next = static function (): void {\n throw new RateLimitException('rate limited', 1);\n };\n\n $this->middleware->handle($job, $next);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Jobs\\Middleware;\n\nuse Exception;\nuse Illuminate\\Contracts\\Queue\\Job;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Jobs\\Middleware\\HandleHubspotRateLimit;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\n#[CoversClass(HandleHubspotRateLimit::class)]\nclass HandleHubspotRateLimitTest extends TestCase\n{\n private const int MAX_RETRY_DELAY = 600;\n private const int MIN_RETRY_DELAY = 1;\n private const int JITTER_SECONDS = 5;\n\n private HandleHubspotRateLimit $middleware;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n $this->middleware = new HandleHubspotRateLimit();\n }\n\n public function testPassesThroughWhenNoExceptionThrown(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $called = false;\n $next = function (object $passed) use ($job, &$called): void {\n $this->assertSame($job, $passed);\n $called = true;\n };\n\n $this->middleware->handle($job, $next);\n\n $this->assertTrue($called);\n }\n\n public function testPropagatesNonRateLimitExceptions(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $next = static function (): void {\n throw new Exception('Database is down');\n };\n\n $this->expectException(Exception::class);\n $this->expectExceptionMessage('Database is down');\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>\n */\n public static function delayClampingProvider(): array\n {\n return [\n 'short retry uses retry_after as floor' => [\n 'retryAfter' => 1,\n 'expectedMin' => self::MIN_RETRY_DELAY,\n 'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n 'medium retry passes through' => [\n 'retryAfter' => 30,\n 'expectedMin' => 30,\n 'expectedMax' => 30 + self::JITTER_SECONDS,\n ],\n 'large retry clamped to max' => [\n 'retryAfter' => 86400,\n 'expectedMin' => self::MAX_RETRY_DELAY,\n 'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n ];\n }\n\n #[DataProvider('delayClampingProvider')]\n public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void\n {\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn(1);\n $job->expects($this->once())\n ->method('release')\n ->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {\n return $delay >= $expectedMin && $delay <= $expectedMax;\n }));\n\n $next = static function () use ($retryAfter): void {\n throw new RateLimitException('rate limited', $retryAfter);\n };\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{attempts: int, shouldLog: bool}>\n */\n public static function logSamplingProvider(): array\n {\n return [\n 'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],\n 'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],\n 'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],\n 'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],\n 'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],\n 'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],\n 'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],\n 'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],\n ];\n }\n\n #[DataProvider('logSamplingProvider')]\n public function testLogSampling(int $attempts, bool $shouldLog): void\n {\n if ($shouldLog) {\n Log::shouldReceive('info')\n ->once()\n ->with(\n '[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',\n $this->callback(static function (array $context) use ($attempts): bool {\n return $context['attempts'] === $attempts\n && $context['retry_after'] === 1\n && isset($context['delay']);\n })\n );\n } else {\n Log::shouldReceive('info')->never();\n }\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn($attempts);\n $job->expects($this->once())->method('release');\n\n $next = static function (): void {\n throw new RateLimitException('rate limited', 1);\n };\n\n $this->middleware->handle($job, $next);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"19","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":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"on_screen":true,"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","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}]...
|
-9091955119622055673
|
-2526499794819392950
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Tests\Unit\Jobs\Middleware;
use Exception;
use Illuminate\Contracts\Queue\Job;
use Illuminate\Support\Facades\Log;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
#[CoversClass(HandleHubspotRateLimit::class)]
class HandleHubspotRateLimitTest extends TestCase
{
private const int MAX_RETRY_DELAY = 600;
private const int MIN_RETRY_DELAY = 1;
private const int JITTER_SECONDS = 5;
private HandleHubspotRateLimit $middleware;
protected function setUp(): void
{
parent::setUp();
$this->middleware = new HandleHubspotRateLimit();
}
public function testPassesThroughWhenNoExceptionThrown(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$called = false;
$next = function (object $passed) use ($job, &$called): void {
$this->assertSame($job, $passed);
$called = true;
};
$this->middleware->handle($job, $next);
$this->assertTrue($called);
}
public function testPropagatesNonRateLimitExceptions(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$next = static function (): void {
throw new Exception('Database is down');
};
$this->expectException(Exception::class);
$this->expectExceptionMessage('Database is down');
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>
*/
public static function delayClampingProvider(): array
{
return [
'short retry uses retry_after as floor' => [
'retryAfter' => 1,
'expectedMin' => self::MIN_RETRY_DELAY,
'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,
],
'medium retry passes through' => [
'retryAfter' => 30,
'expectedMin' => 30,
'expectedMax' => 30 + self::JITTER_SECONDS,
],
'large retry clamped to max' => [
'retryAfter' => 86400,
'expectedMin' => self::MAX_RETRY_DELAY,
'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,
],
];
}
#[DataProvider('delayClampingProvider')]
public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void
{
Log::shouldReceive('info')->zeroOrMoreTimes();
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn(1);
$job->expects($this->once())
->method('release')
->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {
return $delay >= $expectedMin && $delay <= $expectedMax;
}));
$next = static function () use ($retryAfter): void {
throw new RateLimitException('rate limited', $retryAfter);
};
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{attempts: int, shouldLog: bool}>
*/
public static function logSamplingProvider(): array
{
return [
'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],
'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],
'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],
'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],
'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],
'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],
'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],
'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],
];
}
#[DataProvider('logSamplingProvider')]
public function testLogSampling(int $attempts, bool $shouldLog): void
{
if ($shouldLog) {
Log::shouldReceive('info')
->once()
->with(
'[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',
$this->callback(static function (array $context) use ($attempts): bool {
return $context['attempts'] === $attempts
&& $context['retry_after'] === 1
&& isset($context['delay']);
})
);
} else {
Log::shouldReceive('info')->never();
}
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn($attempts);
$job->expects($this->once())->method('release');
$next = static function (): void {
throw new RateLimitException('rate limited', 1);
};
$this->middleware->handle($job, $next);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
17046
|
763
|
20
|
2026-05-11T09:59:44.338323+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778493584338_m2.jpg...
|
PhpStorm
|
faVsco.js – HandleHubspotRateLimitTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Tests\Unit\Jobs\Middleware;
use Exception;
use Illuminate\Contracts\Queue\Job;
use Illuminate\Support\Facades\Log;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
#[CoversClass(HandleHubspotRateLimit::class)]
class HandleHubspotRateLimitTest extends TestCase
{
private const int MAX_RETRY_DELAY = 600;
private const int MIN_RETRY_DELAY = 1;
private const int JITTER_SECONDS = 5;
private HandleHubspotRateLimit $middleware;
protected function setUp(): void
{
parent::setUp();
$this->middleware = new HandleHubspotRateLimit();
}
public function testPassesThroughWhenNoExceptionThrown(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$called = false;
$next = function (object $passed) use ($job, &$called): void {
$this->assertSame($job, $passed);
$called = true;
};
$this->middleware->handle($job, $next);
$this->assertTrue($called);
}
public function testPropagatesNonRateLimitExceptions(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$next = static function (): void {
throw new Exception('Database is down');
};
$this->expectException(Exception::class);
$this->expectExceptionMessage('Database is down');
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>
*/
public static function delayClampingProvider(): array
{
return [
'short retry uses retry_after as floor' => [
'retryAfter' => 1,
'expectedMin' => self::MIN_RETRY_DELAY,
'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,
],
'medium retry passes through' => [
'retryAfter' => 30,
'expectedMin' => 30,
'expectedMax' => 30 + self::JITTER_SECONDS,
],
'large retry clamped to max' => [
'retryAfter' => 86400,
'expectedMin' => self::MAX_RETRY_DELAY,
'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,
],
];
}
#[DataProvider('delayClampingProvider')]
public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void
{
Log::shouldReceive('info')->zeroOrMoreTimes();
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn(1);
$job->expects($this->once())
->method('release')
->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {
return $delay >= $expectedMin && $delay <= $expectedMax;
}));
$next = static function () use ($retryAfter): void {
throw new RateLimitException('rate limited', $retryAfter);
};
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{attempts: int, shouldLog: bool}>
*/
public static function logSamplingProvider(): array
{
return [
'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],
'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],
'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],
'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],
'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],
'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],
'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],
'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],
];
}
#[DataProvider('logSamplingProvider')]
public function testLogSampling(int $attempts, bool $shouldLog): void
{
if ($shouldLog) {
Log::shouldReceive('info')
->once()
->with(
'[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',
$this->callback(static function (array $context) use ($attempts): bool {
return $context['attempts'] === $attempts
&& $context['retry_after'] === 1
&& isset($context['delay']);
})
);
} else {
Log::shouldReceive('info')->never();
}
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn($attempts);
$job->expects($this->once())->method('release');
$next = static function (): void {
throw new RateLimitException('rate limited', 1);
};
$this->middleware->handle($job, $next);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
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":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","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.82413566,"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":"HandleHubspotRateLimitTest","depth":6,"bounds":{"left":0.8394282,"top":0.019952115,"width":0.076130316,"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 'HandleHubspotRateLimitTest'","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 'HandleHubspotRateLimitTest'","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 Tests\\Unit\\Jobs\\Middleware;\n\nuse Exception;\nuse Illuminate\\Contracts\\Queue\\Job;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Jobs\\Middleware\\HandleHubspotRateLimit;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\n#[CoversClass(HandleHubspotRateLimit::class)]\nclass HandleHubspotRateLimitTest extends TestCase\n{\n private const int MAX_RETRY_DELAY = 600;\n private const int MIN_RETRY_DELAY = 1;\n private const int JITTER_SECONDS = 5;\n\n private HandleHubspotRateLimit $middleware;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n $this->middleware = new HandleHubspotRateLimit();\n }\n\n public function testPassesThroughWhenNoExceptionThrown(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $called = false;\n $next = function (object $passed) use ($job, &$called): void {\n $this->assertSame($job, $passed);\n $called = true;\n };\n\n $this->middleware->handle($job, $next);\n\n $this->assertTrue($called);\n }\n\n public function testPropagatesNonRateLimitExceptions(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $next = static function (): void {\n throw new Exception('Database is down');\n };\n\n $this->expectException(Exception::class);\n $this->expectExceptionMessage('Database is down');\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>\n */\n public static function delayClampingProvider(): array\n {\n return [\n 'short retry uses retry_after as floor' => [\n 'retryAfter' => 1,\n 'expectedMin' => self::MIN_RETRY_DELAY,\n 'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n 'medium retry passes through' => [\n 'retryAfter' => 30,\n 'expectedMin' => 30,\n 'expectedMax' => 30 + self::JITTER_SECONDS,\n ],\n 'large retry clamped to max' => [\n 'retryAfter' => 86400,\n 'expectedMin' => self::MAX_RETRY_DELAY,\n 'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n ];\n }\n\n #[DataProvider('delayClampingProvider')]\n public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void\n {\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn(1);\n $job->expects($this->once())\n ->method('release')\n ->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {\n return $delay >= $expectedMin && $delay <= $expectedMax;\n }));\n\n $next = static function () use ($retryAfter): void {\n throw new RateLimitException('rate limited', $retryAfter);\n };\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{attempts: int, shouldLog: bool}>\n */\n public static function logSamplingProvider(): array\n {\n return [\n 'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],\n 'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],\n 'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],\n 'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],\n 'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],\n 'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],\n 'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],\n 'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],\n ];\n }\n\n #[DataProvider('logSamplingProvider')]\n public function testLogSampling(int $attempts, bool $shouldLog): void\n {\n if ($shouldLog) {\n Log::shouldReceive('info')\n ->once()\n ->with(\n '[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',\n $this->callback(static function (array $context) use ($attempts): bool {\n return $context['attempts'] === $attempts\n && $context['retry_after'] === 1\n && isset($context['delay']);\n })\n );\n } else {\n Log::shouldReceive('info')->never();\n }\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn($attempts);\n $job->expects($this->once())->method('release');\n\n $next = static function (): void {\n throw new RateLimitException('rate limited', 1);\n };\n\n $this->middleware->handle($job, $next);\n }\n}","depth":4,"bounds":{"left":0.122340426,"top":0.24581006,"width":0.30585107,"height":0.75418997},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Jobs\\Middleware;\n\nuse Exception;\nuse Illuminate\\Contracts\\Queue\\Job;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Jobs\\Middleware\\HandleHubspotRateLimit;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\n#[CoversClass(HandleHubspotRateLimit::class)]\nclass HandleHubspotRateLimitTest extends TestCase\n{\n private const int MAX_RETRY_DELAY = 600;\n private const int MIN_RETRY_DELAY = 1;\n private const int JITTER_SECONDS = 5;\n\n private HandleHubspotRateLimit $middleware;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n $this->middleware = new HandleHubspotRateLimit();\n }\n\n public function testPassesThroughWhenNoExceptionThrown(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $called = false;\n $next = function (object $passed) use ($job, &$called): void {\n $this->assertSame($job, $passed);\n $called = true;\n };\n\n $this->middleware->handle($job, $next);\n\n $this->assertTrue($called);\n }\n\n public function testPropagatesNonRateLimitExceptions(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $next = static function (): void {\n throw new Exception('Database is down');\n };\n\n $this->expectException(Exception::class);\n $this->expectExceptionMessage('Database is down');\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>\n */\n public static function delayClampingProvider(): array\n {\n return [\n 'short retry uses retry_after as floor' => [\n 'retryAfter' => 1,\n 'expectedMin' => self::MIN_RETRY_DELAY,\n 'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n 'medium retry passes through' => [\n 'retryAfter' => 30,\n 'expectedMin' => 30,\n 'expectedMax' => 30 + self::JITTER_SECONDS,\n ],\n 'large retry clamped to max' => [\n 'retryAfter' => 86400,\n 'expectedMin' => self::MAX_RETRY_DELAY,\n 'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n ];\n }\n\n #[DataProvider('delayClampingProvider')]\n public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void\n {\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn(1);\n $job->expects($this->once())\n ->method('release')\n ->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {\n return $delay >= $expectedMin && $delay <= $expectedMax;\n }));\n\n $next = static function () use ($retryAfter): void {\n throw new RateLimitException('rate limited', $retryAfter);\n };\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{attempts: int, shouldLog: bool}>\n */\n public static function logSamplingProvider(): array\n {\n return [\n 'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],\n 'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],\n 'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],\n 'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],\n 'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],\n 'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],\n 'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],\n 'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],\n ];\n }\n\n #[DataProvider('logSamplingProvider')]\n public function testLogSampling(int $attempts, bool $shouldLog): void\n {\n if ($shouldLog) {\n Log::shouldReceive('info')\n ->once()\n ->with(\n '[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',\n $this->callback(static function (array $context) use ($attempts): bool {\n return $context['attempts'] === $attempts\n && $context['retry_after'] === 1\n && isset($context['delay']);\n })\n );\n } else {\n Log::shouldReceive('info')->never();\n }\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn($attempts);\n $job->expects($this->once())->method('release');\n\n $next = static function (): void {\n throw new RateLimitException('rate limited', 1);\n };\n\n $this->middleware->handle($job, $next);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"19","depth":4,"bounds":{"left":0.6296542,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6409575,"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.64827126,"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":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"bounds":{"left":0.43018618,"top":0.09736632,"width":0.56981385,"height":0.8818835},"on_screen":true,"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","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}]...
|
-9091955119622055673
|
-2526499794819392950
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Tests\Unit\Jobs\Middleware;
use Exception;
use Illuminate\Contracts\Queue\Job;
use Illuminate\Support\Facades\Log;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
#[CoversClass(HandleHubspotRateLimit::class)]
class HandleHubspotRateLimitTest extends TestCase
{
private const int MAX_RETRY_DELAY = 600;
private const int MIN_RETRY_DELAY = 1;
private const int JITTER_SECONDS = 5;
private HandleHubspotRateLimit $middleware;
protected function setUp(): void
{
parent::setUp();
$this->middleware = new HandleHubspotRateLimit();
}
public function testPassesThroughWhenNoExceptionThrown(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$called = false;
$next = function (object $passed) use ($job, &$called): void {
$this->assertSame($job, $passed);
$called = true;
};
$this->middleware->handle($job, $next);
$this->assertTrue($called);
}
public function testPropagatesNonRateLimitExceptions(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$next = static function (): void {
throw new Exception('Database is down');
};
$this->expectException(Exception::class);
$this->expectExceptionMessage('Database is down');
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>
*/
public static function delayClampingProvider(): array
{
return [
'short retry uses retry_after as floor' => [
'retryAfter' => 1,
'expectedMin' => self::MIN_RETRY_DELAY,
'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,
],
'medium retry passes through' => [
'retryAfter' => 30,
'expectedMin' => 30,
'expectedMax' => 30 + self::JITTER_SECONDS,
],
'large retry clamped to max' => [
'retryAfter' => 86400,
'expectedMin' => self::MAX_RETRY_DELAY,
'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,
],
];
}
#[DataProvider('delayClampingProvider')]
public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void
{
Log::shouldReceive('info')->zeroOrMoreTimes();
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn(1);
$job->expects($this->once())
->method('release')
->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {
return $delay >= $expectedMin && $delay <= $expectedMax;
}));
$next = static function () use ($retryAfter): void {
throw new RateLimitException('rate limited', $retryAfter);
};
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{attempts: int, shouldLog: bool}>
*/
public static function logSamplingProvider(): array
{
return [
'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],
'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],
'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],
'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],
'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],
'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],
'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],
'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],
];
}
#[DataProvider('logSamplingProvider')]
public function testLogSampling(int $attempts, bool $shouldLog): void
{
if ($shouldLog) {
Log::shouldReceive('info')
->once()
->with(
'[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',
$this->callback(static function (array $context) use ($attempts): bool {
return $context['attempts'] === $attempts
&& $context['retry_after'] === 1
&& isset($context['delay']);
})
);
} else {
Log::shouldReceive('info')->never();
}
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn($attempts);
$job->expects($this->once())->method('release');
$next = static function (): void {
throw new RateLimitException('rate limited', 1);
};
$this->middleware->handle($job, $next);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
17039
|
NULL
|
NULL
|
NULL
|
|
17049
|
763
|
22
|
2026-05-11T09:59:54.202862+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778493594202_m2.jpg...
|
PhpStorm
|
faVsco.js – HandleHubspotRateLimitTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Tests\Unit\Jobs\Middleware;
use Exception;
use Illuminate\Contracts\Queue\Job;
use Illuminate\Support\Facades\Log;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
#[CoversClass(HandleHubspotRateLimit::class)]
class HandleHubspotRateLimitTest extends TestCase
{
private const int MAX_RETRY_DELAY = 600;
private const int MIN_RETRY_DELAY = 1;
private const int JITTER_SECONDS = 5;
private HandleHubspotRateLimit $middleware;
protected function setUp(): void
{
parent::setUp();
$this->middleware = new HandleHubspotRateLimit();
}
public function testPassesThroughWhenNoExceptionThrown(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$called = false;
$next = function (object $passed) use ($job, &$called): void {
$this->assertSame($job, $passed);
$called = true;
};
$this->middleware->handle($job, $next);
$this->assertTrue($called);
}
public function testPropagatesNonRateLimitExceptions(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$next = static function (): void {
throw new Exception('Database is down');
};
$this->expectException(Exception::class);
$this->expectExceptionMessage('Database is down');
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>
*/
public static function delayClampingProvider(): array
{
return [
'short retry uses retry_after as floor' => [
'retryAfter' => 1,
'expectedMin' => self::MIN_RETRY_DELAY,
'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,
],
'medium retry passes through' => [
'retryAfter' => 30,
'expectedMin' => 30,
'expectedMax' => 30 + self::JITTER_SECONDS,
],
'large retry clamped to max' => [
'retryAfter' => 86400,
'expectedMin' => self::MAX_RETRY_DELAY,
'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,
],
];
}
#[DataProvider('delayClampingProvider')]
public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void
{
Log::shouldReceive('info')->zeroOrMoreTimes();
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn(1);
$job->expects($this->once())
->method('release')
->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {
return $delay >= $expectedMin && $delay <= $expectedMax;
}));
$next = static function () use ($retryAfter): void {
throw new RateLimitException('rate limited', $retryAfter);
};
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{attempts: int, shouldLog: bool}>
*/
public static function logSamplingProvider(): array
{
return [
'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],
'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],
'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],
'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],
'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],
'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],
'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],
'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],
];
}
#[DataProvider('logSamplingProvider')]
public function testLogSampling(int $attempts, bool $shouldLog): void
{
if ($shouldLog) {
Log::shouldReceive('info')
->once()
->with(
'[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',
$this->callback(static function (array $context) use ($attempts): bool {
return $context['attempts'] === $attempts
&& $context['retry_after'] === 1
&& isset($context['delay']);
})
);
} else {
Log::shouldReceive('info')->never();
}
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn($attempts);
$job->expects($this->once())->method('release');
$next = static function (): void {
throw new RateLimitException('rate limited', 1);
};
$this->middleware->handle($job, $next);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
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":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","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.82413566,"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":"HandleHubspotRateLimitTest","depth":6,"bounds":{"left":0.8394282,"top":0.019952115,"width":0.076130316,"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 'HandleHubspotRateLimitTest'","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 'HandleHubspotRateLimitTest'","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 Tests\\Unit\\Jobs\\Middleware;\n\nuse Exception;\nuse Illuminate\\Contracts\\Queue\\Job;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Jobs\\Middleware\\HandleHubspotRateLimit;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\n#[CoversClass(HandleHubspotRateLimit::class)]\nclass HandleHubspotRateLimitTest extends TestCase\n{\n private const int MAX_RETRY_DELAY = 600;\n private const int MIN_RETRY_DELAY = 1;\n private const int JITTER_SECONDS = 5;\n\n private HandleHubspotRateLimit $middleware;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n $this->middleware = new HandleHubspotRateLimit();\n }\n\n public function testPassesThroughWhenNoExceptionThrown(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $called = false;\n $next = function (object $passed) use ($job, &$called): void {\n $this->assertSame($job, $passed);\n $called = true;\n };\n\n $this->middleware->handle($job, $next);\n\n $this->assertTrue($called);\n }\n\n public function testPropagatesNonRateLimitExceptions(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $next = static function (): void {\n throw new Exception('Database is down');\n };\n\n $this->expectException(Exception::class);\n $this->expectExceptionMessage('Database is down');\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>\n */\n public static function delayClampingProvider(): array\n {\n return [\n 'short retry uses retry_after as floor' => [\n 'retryAfter' => 1,\n 'expectedMin' => self::MIN_RETRY_DELAY,\n 'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n 'medium retry passes through' => [\n 'retryAfter' => 30,\n 'expectedMin' => 30,\n 'expectedMax' => 30 + self::JITTER_SECONDS,\n ],\n 'large retry clamped to max' => [\n 'retryAfter' => 86400,\n 'expectedMin' => self::MAX_RETRY_DELAY,\n 'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n ];\n }\n\n #[DataProvider('delayClampingProvider')]\n public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void\n {\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn(1);\n $job->expects($this->once())\n ->method('release')\n ->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {\n return $delay >= $expectedMin && $delay <= $expectedMax;\n }));\n\n $next = static function () use ($retryAfter): void {\n throw new RateLimitException('rate limited', $retryAfter);\n };\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{attempts: int, shouldLog: bool}>\n */\n public static function logSamplingProvider(): array\n {\n return [\n 'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],\n 'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],\n 'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],\n 'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],\n 'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],\n 'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],\n 'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],\n 'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],\n ];\n }\n\n #[DataProvider('logSamplingProvider')]\n public function testLogSampling(int $attempts, bool $shouldLog): void\n {\n if ($shouldLog) {\n Log::shouldReceive('info')\n ->once()\n ->with(\n '[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',\n $this->callback(static function (array $context) use ($attempts): bool {\n return $context['attempts'] === $attempts\n && $context['retry_after'] === 1\n && isset($context['delay']);\n })\n );\n } else {\n Log::shouldReceive('info')->never();\n }\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn($attempts);\n $job->expects($this->once())->method('release');\n\n $next = static function (): void {\n throw new RateLimitException('rate limited', 1);\n };\n\n $this->middleware->handle($job, $next);\n }\n}","depth":4,"bounds":{"left":0.122340426,"top":0.24581006,"width":0.30585107,"height":0.75418997},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Jobs\\Middleware;\n\nuse Exception;\nuse Illuminate\\Contracts\\Queue\\Job;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Jobs\\Middleware\\HandleHubspotRateLimit;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\n#[CoversClass(HandleHubspotRateLimit::class)]\nclass HandleHubspotRateLimitTest extends TestCase\n{\n private const int MAX_RETRY_DELAY = 600;\n private const int MIN_RETRY_DELAY = 1;\n private const int JITTER_SECONDS = 5;\n\n private HandleHubspotRateLimit $middleware;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n $this->middleware = new HandleHubspotRateLimit();\n }\n\n public function testPassesThroughWhenNoExceptionThrown(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $called = false;\n $next = function (object $passed) use ($job, &$called): void {\n $this->assertSame($job, $passed);\n $called = true;\n };\n\n $this->middleware->handle($job, $next);\n\n $this->assertTrue($called);\n }\n\n public function testPropagatesNonRateLimitExceptions(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $next = static function (): void {\n throw new Exception('Database is down');\n };\n\n $this->expectException(Exception::class);\n $this->expectExceptionMessage('Database is down');\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>\n */\n public static function delayClampingProvider(): array\n {\n return [\n 'short retry uses retry_after as floor' => [\n 'retryAfter' => 1,\n 'expectedMin' => self::MIN_RETRY_DELAY,\n 'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n 'medium retry passes through' => [\n 'retryAfter' => 30,\n 'expectedMin' => 30,\n 'expectedMax' => 30 + self::JITTER_SECONDS,\n ],\n 'large retry clamped to max' => [\n 'retryAfter' => 86400,\n 'expectedMin' => self::MAX_RETRY_DELAY,\n 'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n ];\n }\n\n #[DataProvider('delayClampingProvider')]\n public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void\n {\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn(1);\n $job->expects($this->once())\n ->method('release')\n ->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {\n return $delay >= $expectedMin && $delay <= $expectedMax;\n }));\n\n $next = static function () use ($retryAfter): void {\n throw new RateLimitException('rate limited', $retryAfter);\n };\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{attempts: int, shouldLog: bool}>\n */\n public static function logSamplingProvider(): array\n {\n return [\n 'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],\n 'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],\n 'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],\n 'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],\n 'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],\n 'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],\n 'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],\n 'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],\n ];\n }\n\n #[DataProvider('logSamplingProvider')]\n public function testLogSampling(int $attempts, bool $shouldLog): void\n {\n if ($shouldLog) {\n Log::shouldReceive('info')\n ->once()\n ->with(\n '[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',\n $this->callback(static function (array $context) use ($attempts): bool {\n return $context['attempts'] === $attempts\n && $context['retry_after'] === 1\n && isset($context['delay']);\n })\n );\n } else {\n Log::shouldReceive('info')->never();\n }\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn($attempts);\n $job->expects($this->once())->method('release');\n\n $next = static function (): void {\n throw new RateLimitException('rate limited', 1);\n };\n\n $this->middleware->handle($job, $next);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"19","depth":4,"bounds":{"left":0.6296542,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6409575,"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.64827126,"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":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"bounds":{"left":0.43018618,"top":0.09736632,"width":0.56981385,"height":0.8818835},"on_screen":true,"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","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.05086436,"top":0.05027933,"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":"Expand Selected","depth":4,"bounds":{"left":0.061835106,"top":0.05027933,"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":"Collapse All","depth":4,"bounds":{"left":0.07047872,"top":0.05027933,"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":"Options","depth":4,"bounds":{"left":0.07912234,"top":0.05027933,"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":"Hide","depth":4,"bounds":{"left":0.087765954,"top":0.05027933,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-9091955119622055673
|
-2526499794819392950
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Tests\Unit\Jobs\Middleware;
use Exception;
use Illuminate\Contracts\Queue\Job;
use Illuminate\Support\Facades\Log;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
#[CoversClass(HandleHubspotRateLimit::class)]
class HandleHubspotRateLimitTest extends TestCase
{
private const int MAX_RETRY_DELAY = 600;
private const int MIN_RETRY_DELAY = 1;
private const int JITTER_SECONDS = 5;
private HandleHubspotRateLimit $middleware;
protected function setUp(): void
{
parent::setUp();
$this->middleware = new HandleHubspotRateLimit();
}
public function testPassesThroughWhenNoExceptionThrown(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$called = false;
$next = function (object $passed) use ($job, &$called): void {
$this->assertSame($job, $passed);
$called = true;
};
$this->middleware->handle($job, $next);
$this->assertTrue($called);
}
public function testPropagatesNonRateLimitExceptions(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$next = static function (): void {
throw new Exception('Database is down');
};
$this->expectException(Exception::class);
$this->expectExceptionMessage('Database is down');
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>
*/
public static function delayClampingProvider(): array
{
return [
'short retry uses retry_after as floor' => [
'retryAfter' => 1,
'expectedMin' => self::MIN_RETRY_DELAY,
'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,
],
'medium retry passes through' => [
'retryAfter' => 30,
'expectedMin' => 30,
'expectedMax' => 30 + self::JITTER_SECONDS,
],
'large retry clamped to max' => [
'retryAfter' => 86400,
'expectedMin' => self::MAX_RETRY_DELAY,
'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,
],
];
}
#[DataProvider('delayClampingProvider')]
public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void
{
Log::shouldReceive('info')->zeroOrMoreTimes();
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn(1);
$job->expects($this->once())
->method('release')
->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {
return $delay >= $expectedMin && $delay <= $expectedMax;
}));
$next = static function () use ($retryAfter): void {
throw new RateLimitException('rate limited', $retryAfter);
};
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{attempts: int, shouldLog: bool}>
*/
public static function logSamplingProvider(): array
{
return [
'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],
'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],
'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],
'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],
'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],
'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],
'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],
'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],
];
}
#[DataProvider('logSamplingProvider')]
public function testLogSampling(int $attempts, bool $shouldLog): void
{
if ($shouldLog) {
Log::shouldReceive('info')
->once()
->with(
'[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',
$this->callback(static function (array $context) use ($attempts): bool {
return $context['attempts'] === $attempts
&& $context['retry_after'] === 1
&& isset($context['delay']);
})
);
} else {
Log::shouldReceive('info')->never();
}
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn($attempts);
$job->expects($this->once())->method('release');
$next = static function (): void {
throw new RateLimitException('rate limited', 1);
};
$this->middleware->handle($job, $next);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
17050
|
762
|
19
|
2026-05-11T09:59:56.639170+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778493596639_m1.jpg...
|
PhpStorm
|
faVsco.js – HandleHubspotRateLimitTest.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Tests\Unit\Jobs\Middleware;
use Exception;
use Illuminate\Contracts\Queue\Job;
use Illuminate\Support\Facades\Log;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
#[CoversClass(HandleHubspotRateLimit::class)]
class HandleHubspotRateLimitTest extends TestCase
{
private const int MAX_RETRY_DELAY = 600;
private const int MIN_RETRY_DELAY = 1;
private const int JITTER_SECONDS = 5;
private HandleHubspotRateLimit $middleware;
protected function setUp(): void
{
parent::setUp();
$this->middleware = new HandleHubspotRateLimit();
}
public function testPassesThroughWhenNoExceptionThrown(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$called = false;
$next = function (object $passed) use ($job, &$called): void {
$this->assertSame($job, $passed);
$called = true;
};
$this->middleware->handle($job, $next);
$this->assertTrue($called);
}
public function testPropagatesNonRateLimitExceptions(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$next = static function (): void {
throw new Exception('Database is down');
};
$this->expectException(Exception::class);
$this->expectExceptionMessage('Database is down');
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>
*/
public static function delayClampingProvider(): array
{
return [
'short retry uses retry_after as floor' => [
'retryAfter' => 1,
'expectedMin' => self::MIN_RETRY_DELAY,
'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,
],
'medium retry passes through' => [
'retryAfter' => 30,
'expectedMin' => 30,
'expectedMax' => 30 + self::JITTER_SECONDS,
],
'large retry clamped to max' => [
'retryAfter' => 86400,
'expectedMin' => self::MAX_RETRY_DELAY,
'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,
],
];
}
#[DataProvider('delayClampingProvider')]
public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void
{
Log::shouldReceive('info')->zeroOrMoreTimes();
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn(1);
$job->expects($this->once())
->method('release')
->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {
return $delay >= $expectedMin && $delay <= $expectedMax;
}));
$next = static function () use ($retryAfter): void {
throw new RateLimitException('rate limited', $retryAfter);
};
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{attempts: int, shouldLog: bool}>
*/
public static function logSamplingProvider(): array
{
return [
'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],
'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],
'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],
'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],
'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],
'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],
'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],
'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],
];
}
#[DataProvider('logSamplingProvider')]
public function testLogSampling(int $attempts, bool $shouldLog): void
{
if ($shouldLog) {
Log::shouldReceive('info')
->once()
->with(
'[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',
$this->callback(static function (array $context) use ($attempts): bool {
return $context['attempts'] === $attempts
&& $context['retry_after'] === 1
&& isset($context['delay']);
})
);
} else {
Log::shouldReceive('info')->never();
}
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn($attempts);
$job->expects($this->once())->method('release');
$next = static function (): void {
throw new RateLimitException('rate limited', 1);
};
$this->middleware->handle($job, $next);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
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":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","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":"HandleHubspotRateLimitTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'HandleHubspotRateLimitTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'HandleHubspotRateLimitTest'","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 Tests\\Unit\\Jobs\\Middleware;\n\nuse Exception;\nuse Illuminate\\Contracts\\Queue\\Job;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Jobs\\Middleware\\HandleHubspotRateLimit;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\n#[CoversClass(HandleHubspotRateLimit::class)]\nclass HandleHubspotRateLimitTest extends TestCase\n{\n private const int MAX_RETRY_DELAY = 600;\n private const int MIN_RETRY_DELAY = 1;\n private const int JITTER_SECONDS = 5;\n\n private HandleHubspotRateLimit $middleware;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n $this->middleware = new HandleHubspotRateLimit();\n }\n\n public function testPassesThroughWhenNoExceptionThrown(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $called = false;\n $next = function (object $passed) use ($job, &$called): void {\n $this->assertSame($job, $passed);\n $called = true;\n };\n\n $this->middleware->handle($job, $next);\n\n $this->assertTrue($called);\n }\n\n public function testPropagatesNonRateLimitExceptions(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $next = static function (): void {\n throw new Exception('Database is down');\n };\n\n $this->expectException(Exception::class);\n $this->expectExceptionMessage('Database is down');\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>\n */\n public static function delayClampingProvider(): array\n {\n return [\n 'short retry uses retry_after as floor' => [\n 'retryAfter' => 1,\n 'expectedMin' => self::MIN_RETRY_DELAY,\n 'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n 'medium retry passes through' => [\n 'retryAfter' => 30,\n 'expectedMin' => 30,\n 'expectedMax' => 30 + self::JITTER_SECONDS,\n ],\n 'large retry clamped to max' => [\n 'retryAfter' => 86400,\n 'expectedMin' => self::MAX_RETRY_DELAY,\n 'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n ];\n }\n\n #[DataProvider('delayClampingProvider')]\n public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void\n {\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn(1);\n $job->expects($this->once())\n ->method('release')\n ->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {\n return $delay >= $expectedMin && $delay <= $expectedMax;\n }));\n\n $next = static function () use ($retryAfter): void {\n throw new RateLimitException('rate limited', $retryAfter);\n };\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{attempts: int, shouldLog: bool}>\n */\n public static function logSamplingProvider(): array\n {\n return [\n 'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],\n 'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],\n 'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],\n 'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],\n 'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],\n 'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],\n 'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],\n 'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],\n ];\n }\n\n #[DataProvider('logSamplingProvider')]\n public function testLogSampling(int $attempts, bool $shouldLog): void\n {\n if ($shouldLog) {\n Log::shouldReceive('info')\n ->once()\n ->with(\n '[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',\n $this->callback(static function (array $context) use ($attempts): bool {\n return $context['attempts'] === $attempts\n && $context['retry_after'] === 1\n && isset($context['delay']);\n })\n );\n } else {\n Log::shouldReceive('info')->never();\n }\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn($attempts);\n $job->expects($this->once())->method('release');\n\n $next = static function (): void {\n throw new RateLimitException('rate limited', 1);\n };\n\n $this->middleware->handle($job, $next);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Jobs\\Middleware;\n\nuse Exception;\nuse Illuminate\\Contracts\\Queue\\Job;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Jobs\\Middleware\\HandleHubspotRateLimit;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\n#[CoversClass(HandleHubspotRateLimit::class)]\nclass HandleHubspotRateLimitTest extends TestCase\n{\n private const int MAX_RETRY_DELAY = 600;\n private const int MIN_RETRY_DELAY = 1;\n private const int JITTER_SECONDS = 5;\n\n private HandleHubspotRateLimit $middleware;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n $this->middleware = new HandleHubspotRateLimit();\n }\n\n public function testPassesThroughWhenNoExceptionThrown(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $called = false;\n $next = function (object $passed) use ($job, &$called): void {\n $this->assertSame($job, $passed);\n $called = true;\n };\n\n $this->middleware->handle($job, $next);\n\n $this->assertTrue($called);\n }\n\n public function testPropagatesNonRateLimitExceptions(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $next = static function (): void {\n throw new Exception('Database is down');\n };\n\n $this->expectException(Exception::class);\n $this->expectExceptionMessage('Database is down');\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>\n */\n public static function delayClampingProvider(): array\n {\n return [\n 'short retry uses retry_after as floor' => [\n 'retryAfter' => 1,\n 'expectedMin' => self::MIN_RETRY_DELAY,\n 'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n 'medium retry passes through' => [\n 'retryAfter' => 30,\n 'expectedMin' => 30,\n 'expectedMax' => 30 + self::JITTER_SECONDS,\n ],\n 'large retry clamped to max' => [\n 'retryAfter' => 86400,\n 'expectedMin' => self::MAX_RETRY_DELAY,\n 'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n ];\n }\n\n #[DataProvider('delayClampingProvider')]\n public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void\n {\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn(1);\n $job->expects($this->once())\n ->method('release')\n ->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {\n return $delay >= $expectedMin && $delay <= $expectedMax;\n }));\n\n $next = static function () use ($retryAfter): void {\n throw new RateLimitException('rate limited', $retryAfter);\n };\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{attempts: int, shouldLog: bool}>\n */\n public static function logSamplingProvider(): array\n {\n return [\n 'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],\n 'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],\n 'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],\n 'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],\n 'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],\n 'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],\n 'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],\n 'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],\n ];\n }\n\n #[DataProvider('logSamplingProvider')]\n public function testLogSampling(int $attempts, bool $shouldLog): void\n {\n if ($shouldLog) {\n Log::shouldReceive('info')\n ->once()\n ->with(\n '[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',\n $this->callback(static function (array $context) use ($attempts): bool {\n return $context['attempts'] === $attempts\n && $context['retry_after'] === 1\n && isset($context['delay']);\n })\n );\n } else {\n Log::shouldReceive('info')->never();\n }\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn($attempts);\n $job->expects($this->once())->method('release');\n\n $next = static function (): void {\n throw new RateLimitException('rate limited', 1);\n };\n\n $this->middleware->handle($job, $next);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"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":"19","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":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"on_screen":true,"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","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}]...
|
-9091955119622055673
|
-2526499794819392950
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Tests\Unit\Jobs\Middleware;
use Exception;
use Illuminate\Contracts\Queue\Job;
use Illuminate\Support\Facades\Log;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
#[CoversClass(HandleHubspotRateLimit::class)]
class HandleHubspotRateLimitTest extends TestCase
{
private const int MAX_RETRY_DELAY = 600;
private const int MIN_RETRY_DELAY = 1;
private const int JITTER_SECONDS = 5;
private HandleHubspotRateLimit $middleware;
protected function setUp(): void
{
parent::setUp();
$this->middleware = new HandleHubspotRateLimit();
}
public function testPassesThroughWhenNoExceptionThrown(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$called = false;
$next = function (object $passed) use ($job, &$called): void {
$this->assertSame($job, $passed);
$called = true;
};
$this->middleware->handle($job, $next);
$this->assertTrue($called);
}
public function testPropagatesNonRateLimitExceptions(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$next = static function (): void {
throw new Exception('Database is down');
};
$this->expectException(Exception::class);
$this->expectExceptionMessage('Database is down');
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>
*/
public static function delayClampingProvider(): array
{
return [
'short retry uses retry_after as floor' => [
'retryAfter' => 1,
'expectedMin' => self::MIN_RETRY_DELAY,
'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,
],
'medium retry passes through' => [
'retryAfter' => 30,
'expectedMin' => 30,
'expectedMax' => 30 + self::JITTER_SECONDS,
],
'large retry clamped to max' => [
'retryAfter' => 86400,
'expectedMin' => self::MAX_RETRY_DELAY,
'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,
],
];
}
#[DataProvider('delayClampingProvider')]
public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void
{
Log::shouldReceive('info')->zeroOrMoreTimes();
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn(1);
$job->expects($this->once())
->method('release')
->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {
return $delay >= $expectedMin && $delay <= $expectedMax;
}));
$next = static function () use ($retryAfter): void {
throw new RateLimitException('rate limited', $retryAfter);
};
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{attempts: int, shouldLog: bool}>
*/
public static function logSamplingProvider(): array
{
return [
'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],
'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],
'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],
'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],
'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],
'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],
'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],
'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],
];
}
#[DataProvider('logSamplingProvider')]
public function testLogSampling(int $attempts, bool $shouldLog): void
{
if ($shouldLog) {
Log::shouldReceive('info')
->once()
->with(
'[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',
$this->callback(static function (array $context) use ($attempts): bool {
return $context['attempts'] === $attempts
&& $context['retry_after'] === 1
&& isset($context['delay']);
})
);
} else {
Log::shouldReceive('info')->never();
}
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn($attempts);
$job->expects($this->once())->method('release');
$next = static function (): void {
throw new RateLimitException('rate limited', 1);
};
$this->middleware->handle($job, $next);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
17067
|
764
|
4
|
2026-05-11T10:02:30.291987+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778493750291_m1.jpg...
|
PhpStorm
|
faVsco.js – HandleHubspotRateLimitTest.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Tests\Unit\Jobs\Middleware;
use Exception;
use Illuminate\Contracts\Queue\Job;
use Illuminate\Support\Facades\Log;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
#[CoversClass(HandleHubspotRateLimit::class)]
class HandleHubspotRateLimitTest extends TestCase
{
private const int MAX_RETRY_DELAY = 600;
private const int MIN_RETRY_DELAY = 1;
private const int JITTER_SECONDS = 5;
private HandleHubspotRateLimit $middleware;
protected function setUp(): void
{
parent::setUp();
$this->middleware = new HandleHubspotRateLimit();
}
public function testPassesThroughWhenNoExceptionThrown(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$called = false;
$next = function (object $passed) use ($job, &$called): void {
$this->assertSame($job, $passed);
$called = true;
};
$this->middleware->handle($job, $next);
$this->assertTrue($called);
}
public function testPropagatesNonRateLimitExceptions(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$next = static function (): void {
throw new Exception('Database is down');
};
$this->expectException(Exception::class);
$this->expectExceptionMessage('Database is down');
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>
*/
public static function delayClampingProvider(): array
{
return [
'short retry uses retry_after as floor' => [
'retryAfter' => 1,
'expectedMin' => self::MIN_RETRY_DELAY,
'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,
],
'medium retry passes through' => [
'retryAfter' => 30,
'expectedMin' => 30,
'expectedMax' => 30 + self::JITTER_SECONDS,
],
'large retry clamped to max' => [
'retryAfter' => 86400,
'expectedMin' => self::MAX_RETRY_DELAY,
'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,
],
];
}
#[DataProvider('delayClampingProvider')]
public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void
{
Log::shouldReceive('info')->zeroOrMoreTimes();
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn(1);
$job->expects($this->once())
->method('release')
->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {
return $delay >= $expectedMin && $delay <= $expectedMax;
}));
$next = static function () use ($retryAfter): void {
throw new RateLimitException('rate limited', $retryAfter);
};
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{attempts: int, shouldLog: bool}>
*/
public static function logSamplingProvider(): array
{
return [
'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],
'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],
'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],
'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],
'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],
'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],
'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],
'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],
];
}
#[DataProvider('logSamplingProvider')]
public function testLogSampling(int $attempts, bool $shouldLog): void
{
if ($shouldLog) {
Log::shouldReceive('info')
->once()
->with(
'[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',
$this->callback(static function (array $context) use ($attempts): bool {
return $context['attempts'] === $attempts
&& $context['retry_after'] === 1
&& isset($context['delay']);
})
);
} else {
Log::shouldReceive('info')->never();
}
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn($attempts);
$job->expects($this->once())->method('release');
$next = static function (): void {
throw new RateLimitException('rate limited', 1);
};
$this->middleware->handle($job, $next);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
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":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","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":"HandleHubspotRateLimitTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'HandleHubspotRateLimitTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'HandleHubspotRateLimitTest'","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 Tests\\Unit\\Jobs\\Middleware;\n\nuse Exception;\nuse Illuminate\\Contracts\\Queue\\Job;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Jobs\\Middleware\\HandleHubspotRateLimit;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\n#[CoversClass(HandleHubspotRateLimit::class)]\nclass HandleHubspotRateLimitTest extends TestCase\n{\n private const int MAX_RETRY_DELAY = 600;\n private const int MIN_RETRY_DELAY = 1;\n private const int JITTER_SECONDS = 5;\n\n private HandleHubspotRateLimit $middleware;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n $this->middleware = new HandleHubspotRateLimit();\n }\n\n public function testPassesThroughWhenNoExceptionThrown(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $called = false;\n $next = function (object $passed) use ($job, &$called): void {\n $this->assertSame($job, $passed);\n $called = true;\n };\n\n $this->middleware->handle($job, $next);\n\n $this->assertTrue($called);\n }\n\n public function testPropagatesNonRateLimitExceptions(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $next = static function (): void {\n throw new Exception('Database is down');\n };\n\n $this->expectException(Exception::class);\n $this->expectExceptionMessage('Database is down');\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>\n */\n public static function delayClampingProvider(): array\n {\n return [\n 'short retry uses retry_after as floor' => [\n 'retryAfter' => 1,\n 'expectedMin' => self::MIN_RETRY_DELAY,\n 'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n 'medium retry passes through' => [\n 'retryAfter' => 30,\n 'expectedMin' => 30,\n 'expectedMax' => 30 + self::JITTER_SECONDS,\n ],\n 'large retry clamped to max' => [\n 'retryAfter' => 86400,\n 'expectedMin' => self::MAX_RETRY_DELAY,\n 'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n ];\n }\n\n #[DataProvider('delayClampingProvider')]\n public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void\n {\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn(1);\n $job->expects($this->once())\n ->method('release')\n ->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {\n return $delay >= $expectedMin && $delay <= $expectedMax;\n }));\n\n $next = static function () use ($retryAfter): void {\n throw new RateLimitException('rate limited', $retryAfter);\n };\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{attempts: int, shouldLog: bool}>\n */\n public static function logSamplingProvider(): array\n {\n return [\n 'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],\n 'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],\n 'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],\n 'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],\n 'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],\n 'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],\n 'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],\n 'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],\n ];\n }\n\n #[DataProvider('logSamplingProvider')]\n public function testLogSampling(int $attempts, bool $shouldLog): void\n {\n if ($shouldLog) {\n Log::shouldReceive('info')\n ->once()\n ->with(\n '[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',\n $this->callback(static function (array $context) use ($attempts): bool {\n return $context['attempts'] === $attempts\n && $context['retry_after'] === 1\n && isset($context['delay']);\n })\n );\n } else {\n Log::shouldReceive('info')->never();\n }\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn($attempts);\n $job->expects($this->once())->method('release');\n\n $next = static function (): void {\n throw new RateLimitException('rate limited', 1);\n };\n\n $this->middleware->handle($job, $next);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Jobs\\Middleware;\n\nuse Exception;\nuse Illuminate\\Contracts\\Queue\\Job;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Jobs\\Middleware\\HandleHubspotRateLimit;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\n#[CoversClass(HandleHubspotRateLimit::class)]\nclass HandleHubspotRateLimitTest extends TestCase\n{\n private const int MAX_RETRY_DELAY = 600;\n private const int MIN_RETRY_DELAY = 1;\n private const int JITTER_SECONDS = 5;\n\n private HandleHubspotRateLimit $middleware;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n $this->middleware = new HandleHubspotRateLimit();\n }\n\n public function testPassesThroughWhenNoExceptionThrown(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $called = false;\n $next = function (object $passed) use ($job, &$called): void {\n $this->assertSame($job, $passed);\n $called = true;\n };\n\n $this->middleware->handle($job, $next);\n\n $this->assertTrue($called);\n }\n\n public function testPropagatesNonRateLimitExceptions(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $next = static function (): void {\n throw new Exception('Database is down');\n };\n\n $this->expectException(Exception::class);\n $this->expectExceptionMessage('Database is down');\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>\n */\n public static function delayClampingProvider(): array\n {\n return [\n 'short retry uses retry_after as floor' => [\n 'retryAfter' => 1,\n 'expectedMin' => self::MIN_RETRY_DELAY,\n 'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n 'medium retry passes through' => [\n 'retryAfter' => 30,\n 'expectedMin' => 30,\n 'expectedMax' => 30 + self::JITTER_SECONDS,\n ],\n 'large retry clamped to max' => [\n 'retryAfter' => 86400,\n 'expectedMin' => self::MAX_RETRY_DELAY,\n 'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n ];\n }\n\n #[DataProvider('delayClampingProvider')]\n public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void\n {\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn(1);\n $job->expects($this->once())\n ->method('release')\n ->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {\n return $delay >= $expectedMin && $delay <= $expectedMax;\n }));\n\n $next = static function () use ($retryAfter): void {\n throw new RateLimitException('rate limited', $retryAfter);\n };\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{attempts: int, shouldLog: bool}>\n */\n public static function logSamplingProvider(): array\n {\n return [\n 'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],\n 'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],\n 'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],\n 'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],\n 'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],\n 'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],\n 'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],\n 'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],\n ];\n }\n\n #[DataProvider('logSamplingProvider')]\n public function testLogSampling(int $attempts, bool $shouldLog): void\n {\n if ($shouldLog) {\n Log::shouldReceive('info')\n ->once()\n ->with(\n '[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',\n $this->callback(static function (array $context) use ($attempts): bool {\n return $context['attempts'] === $attempts\n && $context['retry_after'] === 1\n && isset($context['delay']);\n })\n );\n } else {\n Log::shouldReceive('info')->never();\n }\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn($attempts);\n $job->expects($this->once())->method('release');\n\n $next = static function (): void {\n throw new RateLimitException('rate limited', 1);\n };\n\n $this->middleware->handle($job, $next);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"19","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":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"on_screen":true,"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","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}]...
|
-9091955119622055673
|
-2526499794819392950
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Tests\Unit\Jobs\Middleware;
use Exception;
use Illuminate\Contracts\Queue\Job;
use Illuminate\Support\Facades\Log;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
#[CoversClass(HandleHubspotRateLimit::class)]
class HandleHubspotRateLimitTest extends TestCase
{
private const int MAX_RETRY_DELAY = 600;
private const int MIN_RETRY_DELAY = 1;
private const int JITTER_SECONDS = 5;
private HandleHubspotRateLimit $middleware;
protected function setUp(): void
{
parent::setUp();
$this->middleware = new HandleHubspotRateLimit();
}
public function testPassesThroughWhenNoExceptionThrown(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$called = false;
$next = function (object $passed) use ($job, &$called): void {
$this->assertSame($job, $passed);
$called = true;
};
$this->middleware->handle($job, $next);
$this->assertTrue($called);
}
public function testPropagatesNonRateLimitExceptions(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$next = static function (): void {
throw new Exception('Database is down');
};
$this->expectException(Exception::class);
$this->expectExceptionMessage('Database is down');
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>
*/
public static function delayClampingProvider(): array
{
return [
'short retry uses retry_after as floor' => [
'retryAfter' => 1,
'expectedMin' => self::MIN_RETRY_DELAY,
'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,
],
'medium retry passes through' => [
'retryAfter' => 30,
'expectedMin' => 30,
'expectedMax' => 30 + self::JITTER_SECONDS,
],
'large retry clamped to max' => [
'retryAfter' => 86400,
'expectedMin' => self::MAX_RETRY_DELAY,
'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,
],
];
}
#[DataProvider('delayClampingProvider')]
public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void
{
Log::shouldReceive('info')->zeroOrMoreTimes();
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn(1);
$job->expects($this->once())
->method('release')
->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {
return $delay >= $expectedMin && $delay <= $expectedMax;
}));
$next = static function () use ($retryAfter): void {
throw new RateLimitException('rate limited', $retryAfter);
};
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{attempts: int, shouldLog: bool}>
*/
public static function logSamplingProvider(): array
{
return [
'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],
'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],
'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],
'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],
'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],
'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],
'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],
'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],
];
}
#[DataProvider('logSamplingProvider')]
public function testLogSampling(int $attempts, bool $shouldLog): void
{
if ($shouldLog) {
Log::shouldReceive('info')
->once()
->with(
'[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',
$this->callback(static function (array $context) use ($attempts): bool {
return $context['attempts'] === $attempts
&& $context['retry_after'] === 1
&& isset($context['delay']);
})
);
} else {
Log::shouldReceive('info')->never();
}
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn($attempts);
$job->expects($this->once())->method('release');
$next = static function (): void {
throw new RateLimitException('rate limited', 1);
};
$this->middleware->handle($job, $next);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
17065
|
NULL
|
NULL
|
NULL
|
|
17068
|
765
|
4
|
2026-05-11T10:02:30.291948+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778493750291_m2.jpg...
|
PhpStorm
|
faVsco.js – HandleHubspotRateLimitTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Tests\Unit\Jobs\Middleware;
use Exception;
use Illuminate\Contracts\Queue\Job;
use Illuminate\Support\Facades\Log;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
#[CoversClass(HandleHubspotRateLimit::class)]
class HandleHubspotRateLimitTest extends TestCase
{
private const int MAX_RETRY_DELAY = 600;
private const int MIN_RETRY_DELAY = 1;
private const int JITTER_SECONDS = 5;
private HandleHubspotRateLimit $middleware;
protected function setUp(): void
{
parent::setUp();
$this->middleware = new HandleHubspotRateLimit();
}
public function testPassesThroughWhenNoExceptionThrown(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$called = false;
$next = function (object $passed) use ($job, &$called): void {
$this->assertSame($job, $passed);
$called = true;
};
$this->middleware->handle($job, $next);
$this->assertTrue($called);
}
public function testPropagatesNonRateLimitExceptions(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$next = static function (): void {
throw new Exception('Database is down');
};
$this->expectException(Exception::class);
$this->expectExceptionMessage('Database is down');
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>
*/
public static function delayClampingProvider(): array
{
return [
'short retry uses retry_after as floor' => [
'retryAfter' => 1,
'expectedMin' => self::MIN_RETRY_DELAY,
'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,
],
'medium retry passes through' => [
'retryAfter' => 30,
'expectedMin' => 30,
'expectedMax' => 30 + self::JITTER_SECONDS,
],
'large retry clamped to max' => [
'retryAfter' => 86400,
'expectedMin' => self::MAX_RETRY_DELAY,
'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,
],
];
}
#[DataProvider('delayClampingProvider')]
public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void
{
Log::shouldReceive('info')->zeroOrMoreTimes();
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn(1);
$job->expects($this->once())
->method('release')
->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {
return $delay >= $expectedMin && $delay <= $expectedMax;
}));
$next = static function () use ($retryAfter): void {
throw new RateLimitException('rate limited', $retryAfter);
};
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{attempts: int, shouldLog: bool}>
*/
public static function logSamplingProvider(): array
{
return [
'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],
'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],
'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],
'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],
'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],
'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],
'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],
'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],
];
}
#[DataProvider('logSamplingProvider')]
public function testLogSampling(int $attempts, bool $shouldLog): void
{
if ($shouldLog) {
Log::shouldReceive('info')
->once()
->with(
'[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',
$this->callback(static function (array $context) use ($attempts): bool {
return $context['attempts'] === $attempts
&& $context['retry_after'] === 1
&& isset($context['delay']);
})
);
} else {
Log::shouldReceive('info')->never();
}
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn($attempts);
$job->expects($this->once())->method('release');
$next = static function (): void {
throw new RateLimitException('rate limited', 1);
};
$this->middleware->handle($job, $next);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
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":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","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.82413566,"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":"HandleHubspotRateLimitTest","depth":6,"bounds":{"left":0.8394282,"top":0.019952115,"width":0.076130316,"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 'HandleHubspotRateLimitTest'","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 'HandleHubspotRateLimitTest'","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 Tests\\Unit\\Jobs\\Middleware;\n\nuse Exception;\nuse Illuminate\\Contracts\\Queue\\Job;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Jobs\\Middleware\\HandleHubspotRateLimit;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\n#[CoversClass(HandleHubspotRateLimit::class)]\nclass HandleHubspotRateLimitTest extends TestCase\n{\n private const int MAX_RETRY_DELAY = 600;\n private const int MIN_RETRY_DELAY = 1;\n private const int JITTER_SECONDS = 5;\n\n private HandleHubspotRateLimit $middleware;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n $this->middleware = new HandleHubspotRateLimit();\n }\n\n public function testPassesThroughWhenNoExceptionThrown(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $called = false;\n $next = function (object $passed) use ($job, &$called): void {\n $this->assertSame($job, $passed);\n $called = true;\n };\n\n $this->middleware->handle($job, $next);\n\n $this->assertTrue($called);\n }\n\n public function testPropagatesNonRateLimitExceptions(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $next = static function (): void {\n throw new Exception('Database is down');\n };\n\n $this->expectException(Exception::class);\n $this->expectExceptionMessage('Database is down');\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>\n */\n public static function delayClampingProvider(): array\n {\n return [\n 'short retry uses retry_after as floor' => [\n 'retryAfter' => 1,\n 'expectedMin' => self::MIN_RETRY_DELAY,\n 'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n 'medium retry passes through' => [\n 'retryAfter' => 30,\n 'expectedMin' => 30,\n 'expectedMax' => 30 + self::JITTER_SECONDS,\n ],\n 'large retry clamped to max' => [\n 'retryAfter' => 86400,\n 'expectedMin' => self::MAX_RETRY_DELAY,\n 'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n ];\n }\n\n #[DataProvider('delayClampingProvider')]\n public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void\n {\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn(1);\n $job->expects($this->once())\n ->method('release')\n ->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {\n return $delay >= $expectedMin && $delay <= $expectedMax;\n }));\n\n $next = static function () use ($retryAfter): void {\n throw new RateLimitException('rate limited', $retryAfter);\n };\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{attempts: int, shouldLog: bool}>\n */\n public static function logSamplingProvider(): array\n {\n return [\n 'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],\n 'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],\n 'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],\n 'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],\n 'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],\n 'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],\n 'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],\n 'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],\n ];\n }\n\n #[DataProvider('logSamplingProvider')]\n public function testLogSampling(int $attempts, bool $shouldLog): void\n {\n if ($shouldLog) {\n Log::shouldReceive('info')\n ->once()\n ->with(\n '[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',\n $this->callback(static function (array $context) use ($attempts): bool {\n return $context['attempts'] === $attempts\n && $context['retry_after'] === 1\n && isset($context['delay']);\n })\n );\n } else {\n Log::shouldReceive('info')->never();\n }\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn($attempts);\n $job->expects($this->once())->method('release');\n\n $next = static function (): void {\n throw new RateLimitException('rate limited', 1);\n };\n\n $this->middleware->handle($job, $next);\n }\n}","depth":4,"bounds":{"left":0.122340426,"top":0.24581006,"width":0.30585107,"height":0.75418997},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Jobs\\Middleware;\n\nuse Exception;\nuse Illuminate\\Contracts\\Queue\\Job;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Jobs\\Middleware\\HandleHubspotRateLimit;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\n#[CoversClass(HandleHubspotRateLimit::class)]\nclass HandleHubspotRateLimitTest extends TestCase\n{\n private const int MAX_RETRY_DELAY = 600;\n private const int MIN_RETRY_DELAY = 1;\n private const int JITTER_SECONDS = 5;\n\n private HandleHubspotRateLimit $middleware;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n $this->middleware = new HandleHubspotRateLimit();\n }\n\n public function testPassesThroughWhenNoExceptionThrown(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $called = false;\n $next = function (object $passed) use ($job, &$called): void {\n $this->assertSame($job, $passed);\n $called = true;\n };\n\n $this->middleware->handle($job, $next);\n\n $this->assertTrue($called);\n }\n\n public function testPropagatesNonRateLimitExceptions(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $next = static function (): void {\n throw new Exception('Database is down');\n };\n\n $this->expectException(Exception::class);\n $this->expectExceptionMessage('Database is down');\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>\n */\n public static function delayClampingProvider(): array\n {\n return [\n 'short retry uses retry_after as floor' => [\n 'retryAfter' => 1,\n 'expectedMin' => self::MIN_RETRY_DELAY,\n 'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n 'medium retry passes through' => [\n 'retryAfter' => 30,\n 'expectedMin' => 30,\n 'expectedMax' => 30 + self::JITTER_SECONDS,\n ],\n 'large retry clamped to max' => [\n 'retryAfter' => 86400,\n 'expectedMin' => self::MAX_RETRY_DELAY,\n 'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n ];\n }\n\n #[DataProvider('delayClampingProvider')]\n public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void\n {\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn(1);\n $job->expects($this->once())\n ->method('release')\n ->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {\n return $delay >= $expectedMin && $delay <= $expectedMax;\n }));\n\n $next = static function () use ($retryAfter): void {\n throw new RateLimitException('rate limited', $retryAfter);\n };\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{attempts: int, shouldLog: bool}>\n */\n public static function logSamplingProvider(): array\n {\n return [\n 'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],\n 'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],\n 'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],\n 'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],\n 'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],\n 'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],\n 'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],\n 'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],\n ];\n }\n\n #[DataProvider('logSamplingProvider')]\n public function testLogSampling(int $attempts, bool $shouldLog): void\n {\n if ($shouldLog) {\n Log::shouldReceive('info')\n ->once()\n ->with(\n '[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',\n $this->callback(static function (array $context) use ($attempts): bool {\n return $context['attempts'] === $attempts\n && $context['retry_after'] === 1\n && isset($context['delay']);\n })\n );\n } else {\n Log::shouldReceive('info')->never();\n }\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn($attempts);\n $job->expects($this->once())->method('release');\n\n $next = static function (): void {\n throw new RateLimitException('rate limited', 1);\n };\n\n $this->middleware->handle($job, $next);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"19","depth":4,"bounds":{"left":0.6296542,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6409575,"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.64827126,"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":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"bounds":{"left":0.43018618,"top":0.09736632,"width":0.56981385,"height":0.8818835},"on_screen":true,"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","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}]...
|
-9091955119622055673
|
-2526499794819392950
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Tests\Unit\Jobs\Middleware;
use Exception;
use Illuminate\Contracts\Queue\Job;
use Illuminate\Support\Facades\Log;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
#[CoversClass(HandleHubspotRateLimit::class)]
class HandleHubspotRateLimitTest extends TestCase
{
private const int MAX_RETRY_DELAY = 600;
private const int MIN_RETRY_DELAY = 1;
private const int JITTER_SECONDS = 5;
private HandleHubspotRateLimit $middleware;
protected function setUp(): void
{
parent::setUp();
$this->middleware = new HandleHubspotRateLimit();
}
public function testPassesThroughWhenNoExceptionThrown(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$called = false;
$next = function (object $passed) use ($job, &$called): void {
$this->assertSame($job, $passed);
$called = true;
};
$this->middleware->handle($job, $next);
$this->assertTrue($called);
}
public function testPropagatesNonRateLimitExceptions(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$next = static function (): void {
throw new Exception('Database is down');
};
$this->expectException(Exception::class);
$this->expectExceptionMessage('Database is down');
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>
*/
public static function delayClampingProvider(): array
{
return [
'short retry uses retry_after as floor' => [
'retryAfter' => 1,
'expectedMin' => self::MIN_RETRY_DELAY,
'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,
],
'medium retry passes through' => [
'retryAfter' => 30,
'expectedMin' => 30,
'expectedMax' => 30 + self::JITTER_SECONDS,
],
'large retry clamped to max' => [
'retryAfter' => 86400,
'expectedMin' => self::MAX_RETRY_DELAY,
'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,
],
];
}
#[DataProvider('delayClampingProvider')]
public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void
{
Log::shouldReceive('info')->zeroOrMoreTimes();
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn(1);
$job->expects($this->once())
->method('release')
->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {
return $delay >= $expectedMin && $delay <= $expectedMax;
}));
$next = static function () use ($retryAfter): void {
throw new RateLimitException('rate limited', $retryAfter);
};
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{attempts: int, shouldLog: bool}>
*/
public static function logSamplingProvider(): array
{
return [
'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],
'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],
'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],
'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],
'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],
'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],
'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],
'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],
];
}
#[DataProvider('logSamplingProvider')]
public function testLogSampling(int $attempts, bool $shouldLog): void
{
if ($shouldLog) {
Log::shouldReceive('info')
->once()
->with(
'[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',
$this->callback(static function (array $context) use ($attempts): bool {
return $context['attempts'] === $attempts
&& $context['retry_after'] === 1
&& isset($context['delay']);
})
);
} else {
Log::shouldReceive('info')->never();
}
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn($attempts);
$job->expects($this->once())->method('release');
$next = static function (): void {
throw new RateLimitException('rate limited', 1);
};
$this->middleware->handle($job, $next);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
17066
|
NULL
|
NULL
|
NULL
|
|
17078
|
764
|
10
|
2026-05-11T10:02:47.531981+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778493767531_m1.jpg...
|
PhpStorm
|
faVsco.js – HandleHubspotRateLimitTest.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Tests\Unit\Jobs\Middleware;
use Exception;
use Illuminate\Contracts\Queue\Job;
use Illuminate\Support\Facades\Log;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
#[CoversClass(HandleHubspotRateLimit::class)]
class HandleHubspotRateLimitTest extends TestCase
{
private const int MAX_RETRY_DELAY = 600;
private const int MIN_RETRY_DELAY = 1;
private const int JITTER_SECONDS = 5;
private HandleHubspotRateLimit $middleware;
protected function setUp(): void
{
parent::setUp();
$this->middleware = new HandleHubspotRateLimit();
}
public function testPassesThroughWhenNoExceptionThrown(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$called = false;
$next = function (object $passed) use ($job, &$called): void {
$this->assertSame($job, $passed);
$called = true;
};
$this->middleware->handle($job, $next);
$this->assertTrue($called);
}
public function testPropagatesNonRateLimitExceptions(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$next = static function (): void {
throw new Exception('Database is down');
};
$this->expectException(Exception::class);
$this->expectExceptionMessage('Database is down');
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>
*/
public static function delayClampingProvider(): array
{
return [
'short retry uses retry_after as floor' => [
'retryAfter' => 1,
'expectedMin' => self::MIN_RETRY_DELAY,
'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,
],
'medium retry passes through' => [
'retryAfter' => 30,
'expectedMin' => 30,
'expectedMax' => 30 + self::JITTER_SECONDS,
],
'large retry clamped to max' => [
'retryAfter' => 86400,
'expectedMin' => self::MAX_RETRY_DELAY,
'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,
],
];
}
#[DataProvider('delayClampingProvider')]
public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void
{
Log::shouldReceive('info')->zeroOrMoreTimes();
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn(1);
$job->expects($this->once())
->method('release')
->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {
return $delay >= $expectedMin && $delay <= $expectedMax;
}));
$next = static function () use ($retryAfter): void {
throw new RateLimitException('rate limited', $retryAfter);
};
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{attempts: int, shouldLog: bool}>
*/
public static function logSamplingProvider(): array
{
return [
'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],
'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],
'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],
'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],
'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],
'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],
'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],
'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],
];
}
#[DataProvider('logSamplingProvider')]
public function testLogSampling(int $attempts, bool $shouldLog): void
{
if ($shouldLog) {
Log::shouldReceive('info')
->once()
->with(
'[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',
$this->callback(static function (array $context) use ($attempts): bool {
return $context['attempts'] === $attempts
&& $context['retry_after'] === 1
&& isset($context['delay']);
})
);
} else {
Log::shouldReceive('info')->never();
}
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn($attempts);
$job->expects($this->once())->method('release');
$next = static function (): void {
throw new RateLimitException('rate limited', 1);
};
$this->middleware->handle($job, $next);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
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":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","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":"HandleHubspotRateLimitTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'HandleHubspotRateLimitTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'HandleHubspotRateLimitTest'","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 Tests\\Unit\\Jobs\\Middleware;\n\nuse Exception;\nuse Illuminate\\Contracts\\Queue\\Job;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Jobs\\Middleware\\HandleHubspotRateLimit;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\n#[CoversClass(HandleHubspotRateLimit::class)]\nclass HandleHubspotRateLimitTest extends TestCase\n{\n private const int MAX_RETRY_DELAY = 600;\n private const int MIN_RETRY_DELAY = 1;\n private const int JITTER_SECONDS = 5;\n\n private HandleHubspotRateLimit $middleware;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n $this->middleware = new HandleHubspotRateLimit();\n }\n\n public function testPassesThroughWhenNoExceptionThrown(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $called = false;\n $next = function (object $passed) use ($job, &$called): void {\n $this->assertSame($job, $passed);\n $called = true;\n };\n\n $this->middleware->handle($job, $next);\n\n $this->assertTrue($called);\n }\n\n public function testPropagatesNonRateLimitExceptions(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $next = static function (): void {\n throw new Exception('Database is down');\n };\n\n $this->expectException(Exception::class);\n $this->expectExceptionMessage('Database is down');\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>\n */\n public static function delayClampingProvider(): array\n {\n return [\n 'short retry uses retry_after as floor' => [\n 'retryAfter' => 1,\n 'expectedMin' => self::MIN_RETRY_DELAY,\n 'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n 'medium retry passes through' => [\n 'retryAfter' => 30,\n 'expectedMin' => 30,\n 'expectedMax' => 30 + self::JITTER_SECONDS,\n ],\n 'large retry clamped to max' => [\n 'retryAfter' => 86400,\n 'expectedMin' => self::MAX_RETRY_DELAY,\n 'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n ];\n }\n\n #[DataProvider('delayClampingProvider')]\n public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void\n {\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn(1);\n $job->expects($this->once())\n ->method('release')\n ->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {\n return $delay >= $expectedMin && $delay <= $expectedMax;\n }));\n\n $next = static function () use ($retryAfter): void {\n throw new RateLimitException('rate limited', $retryAfter);\n };\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{attempts: int, shouldLog: bool}>\n */\n public static function logSamplingProvider(): array\n {\n return [\n 'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],\n 'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],\n 'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],\n 'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],\n 'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],\n 'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],\n 'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],\n 'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],\n ];\n }\n\n #[DataProvider('logSamplingProvider')]\n public function testLogSampling(int $attempts, bool $shouldLog): void\n {\n if ($shouldLog) {\n Log::shouldReceive('info')\n ->once()\n ->with(\n '[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',\n $this->callback(static function (array $context) use ($attempts): bool {\n return $context['attempts'] === $attempts\n && $context['retry_after'] === 1\n && isset($context['delay']);\n })\n );\n } else {\n Log::shouldReceive('info')->never();\n }\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn($attempts);\n $job->expects($this->once())->method('release');\n\n $next = static function (): void {\n throw new RateLimitException('rate limited', 1);\n };\n\n $this->middleware->handle($job, $next);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Jobs\\Middleware;\n\nuse Exception;\nuse Illuminate\\Contracts\\Queue\\Job;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Jobs\\Middleware\\HandleHubspotRateLimit;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\n#[CoversClass(HandleHubspotRateLimit::class)]\nclass HandleHubspotRateLimitTest extends TestCase\n{\n private const int MAX_RETRY_DELAY = 600;\n private const int MIN_RETRY_DELAY = 1;\n private const int JITTER_SECONDS = 5;\n\n private HandleHubspotRateLimit $middleware;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n $this->middleware = new HandleHubspotRateLimit();\n }\n\n public function testPassesThroughWhenNoExceptionThrown(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $called = false;\n $next = function (object $passed) use ($job, &$called): void {\n $this->assertSame($job, $passed);\n $called = true;\n };\n\n $this->middleware->handle($job, $next);\n\n $this->assertTrue($called);\n }\n\n public function testPropagatesNonRateLimitExceptions(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $next = static function (): void {\n throw new Exception('Database is down');\n };\n\n $this->expectException(Exception::class);\n $this->expectExceptionMessage('Database is down');\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>\n */\n public static function delayClampingProvider(): array\n {\n return [\n 'short retry uses retry_after as floor' => [\n 'retryAfter' => 1,\n 'expectedMin' => self::MIN_RETRY_DELAY,\n 'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n 'medium retry passes through' => [\n 'retryAfter' => 30,\n 'expectedMin' => 30,\n 'expectedMax' => 30 + self::JITTER_SECONDS,\n ],\n 'large retry clamped to max' => [\n 'retryAfter' => 86400,\n 'expectedMin' => self::MAX_RETRY_DELAY,\n 'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n ];\n }\n\n #[DataProvider('delayClampingProvider')]\n public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void\n {\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn(1);\n $job->expects($this->once())\n ->method('release')\n ->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {\n return $delay >= $expectedMin && $delay <= $expectedMax;\n }));\n\n $next = static function () use ($retryAfter): void {\n throw new RateLimitException('rate limited', $retryAfter);\n };\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{attempts: int, shouldLog: bool}>\n */\n public static function logSamplingProvider(): array\n {\n return [\n 'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],\n 'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],\n 'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],\n 'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],\n 'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],\n 'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],\n 'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],\n 'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],\n ];\n }\n\n #[DataProvider('logSamplingProvider')]\n public function testLogSampling(int $attempts, bool $shouldLog): void\n {\n if ($shouldLog) {\n Log::shouldReceive('info')\n ->once()\n ->with(\n '[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',\n $this->callback(static function (array $context) use ($attempts): bool {\n return $context['attempts'] === $attempts\n && $context['retry_after'] === 1\n && isset($context['delay']);\n })\n );\n } else {\n Log::shouldReceive('info')->never();\n }\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn($attempts);\n $job->expects($this->once())->method('release');\n\n $next = static function (): void {\n throw new RateLimitException('rate limited', 1);\n };\n\n $this->middleware->handle($job, $next);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"19","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":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"on_screen":true,"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","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}]...
|
-9091955119622055673
|
-2526499794819392950
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Tests\Unit\Jobs\Middleware;
use Exception;
use Illuminate\Contracts\Queue\Job;
use Illuminate\Support\Facades\Log;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
#[CoversClass(HandleHubspotRateLimit::class)]
class HandleHubspotRateLimitTest extends TestCase
{
private const int MAX_RETRY_DELAY = 600;
private const int MIN_RETRY_DELAY = 1;
private const int JITTER_SECONDS = 5;
private HandleHubspotRateLimit $middleware;
protected function setUp(): void
{
parent::setUp();
$this->middleware = new HandleHubspotRateLimit();
}
public function testPassesThroughWhenNoExceptionThrown(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$called = false;
$next = function (object $passed) use ($job, &$called): void {
$this->assertSame($job, $passed);
$called = true;
};
$this->middleware->handle($job, $next);
$this->assertTrue($called);
}
public function testPropagatesNonRateLimitExceptions(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$next = static function (): void {
throw new Exception('Database is down');
};
$this->expectException(Exception::class);
$this->expectExceptionMessage('Database is down');
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>
*/
public static function delayClampingProvider(): array
{
return [
'short retry uses retry_after as floor' => [
'retryAfter' => 1,
'expectedMin' => self::MIN_RETRY_DELAY,
'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,
],
'medium retry passes through' => [
'retryAfter' => 30,
'expectedMin' => 30,
'expectedMax' => 30 + self::JITTER_SECONDS,
],
'large retry clamped to max' => [
'retryAfter' => 86400,
'expectedMin' => self::MAX_RETRY_DELAY,
'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,
],
];
}
#[DataProvider('delayClampingProvider')]
public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void
{
Log::shouldReceive('info')->zeroOrMoreTimes();
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn(1);
$job->expects($this->once())
->method('release')
->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {
return $delay >= $expectedMin && $delay <= $expectedMax;
}));
$next = static function () use ($retryAfter): void {
throw new RateLimitException('rate limited', $retryAfter);
};
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{attempts: int, shouldLog: bool}>
*/
public static function logSamplingProvider(): array
{
return [
'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],
'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],
'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],
'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],
'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],
'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],
'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],
'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],
];
}
#[DataProvider('logSamplingProvider')]
public function testLogSampling(int $attempts, bool $shouldLog): void
{
if ($shouldLog) {
Log::shouldReceive('info')
->once()
->with(
'[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',
$this->callback(static function (array $context) use ($attempts): bool {
return $context['attempts'] === $attempts
&& $context['retry_after'] === 1
&& isset($context['delay']);
})
);
} else {
Log::shouldReceive('info')->never();
}
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn($attempts);
$job->expects($this->once())->method('release');
$next = static function (): void {
throw new RateLimitException('rate limited', 1);
};
$this->middleware->handle($job, $next);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
17076
|
NULL
|
NULL
|
NULL
|
|
17079
|
765
|
9
|
2026-05-11T10:02:47.531965+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778493767531_m2.jpg...
|
PhpStorm
|
faVsco.js – HandleHubspotRateLimitTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Tests\Unit\Jobs\Middleware;
use Exception;
use Illuminate\Contracts\Queue\Job;
use Illuminate\Support\Facades\Log;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
#[CoversClass(HandleHubspotRateLimit::class)]
class HandleHubspotRateLimitTest extends TestCase
{
private const int MAX_RETRY_DELAY = 600;
private const int MIN_RETRY_DELAY = 1;
private const int JITTER_SECONDS = 5;
private HandleHubspotRateLimit $middleware;
protected function setUp(): void
{
parent::setUp();
$this->middleware = new HandleHubspotRateLimit();
}
public function testPassesThroughWhenNoExceptionThrown(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$called = false;
$next = function (object $passed) use ($job, &$called): void {
$this->assertSame($job, $passed);
$called = true;
};
$this->middleware->handle($job, $next);
$this->assertTrue($called);
}
public function testPropagatesNonRateLimitExceptions(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$next = static function (): void {
throw new Exception('Database is down');
};
$this->expectException(Exception::class);
$this->expectExceptionMessage('Database is down');
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>
*/
public static function delayClampingProvider(): array
{
return [
'short retry uses retry_after as floor' => [
'retryAfter' => 1,
'expectedMin' => self::MIN_RETRY_DELAY,
'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,
],
'medium retry passes through' => [
'retryAfter' => 30,
'expectedMin' => 30,
'expectedMax' => 30 + self::JITTER_SECONDS,
],
'large retry clamped to max' => [
'retryAfter' => 86400,
'expectedMin' => self::MAX_RETRY_DELAY,
'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,
],
];
}
#[DataProvider('delayClampingProvider')]
public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void
{
Log::shouldReceive('info')->zeroOrMoreTimes();
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn(1);
$job->expects($this->once())
->method('release')
->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {
return $delay >= $expectedMin && $delay <= $expectedMax;
}));
$next = static function () use ($retryAfter): void {
throw new RateLimitException('rate limited', $retryAfter);
};
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{attempts: int, shouldLog: bool}>
*/
public static function logSamplingProvider(): array
{
return [
'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],
'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],
'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],
'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],
'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],
'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],
'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],
'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],
];
}
#[DataProvider('logSamplingProvider')]
public function testLogSampling(int $attempts, bool $shouldLog): void
{
if ($shouldLog) {
Log::shouldReceive('info')
->once()
->with(
'[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',
$this->callback(static function (array $context) use ($attempts): bool {
return $context['attempts'] === $attempts
&& $context['retry_after'] === 1
&& isset($context['delay']);
})
);
} else {
Log::shouldReceive('info')->never();
}
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn($attempts);
$job->expects($this->once())->method('release');
$next = static function (): void {
throw new RateLimitException('rate limited', 1);
};
$this->middleware->handle($job, $next);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
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":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","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.82413566,"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":"HandleHubspotRateLimitTest","depth":6,"bounds":{"left":0.8394282,"top":0.019952115,"width":0.076130316,"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 'HandleHubspotRateLimitTest'","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 'HandleHubspotRateLimitTest'","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 Tests\\Unit\\Jobs\\Middleware;\n\nuse Exception;\nuse Illuminate\\Contracts\\Queue\\Job;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Jobs\\Middleware\\HandleHubspotRateLimit;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\n#[CoversClass(HandleHubspotRateLimit::class)]\nclass HandleHubspotRateLimitTest extends TestCase\n{\n private const int MAX_RETRY_DELAY = 600;\n private const int MIN_RETRY_DELAY = 1;\n private const int JITTER_SECONDS = 5;\n\n private HandleHubspotRateLimit $middleware;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n $this->middleware = new HandleHubspotRateLimit();\n }\n\n public function testPassesThroughWhenNoExceptionThrown(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $called = false;\n $next = function (object $passed) use ($job, &$called): void {\n $this->assertSame($job, $passed);\n $called = true;\n };\n\n $this->middleware->handle($job, $next);\n\n $this->assertTrue($called);\n }\n\n public function testPropagatesNonRateLimitExceptions(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $next = static function (): void {\n throw new Exception('Database is down');\n };\n\n $this->expectException(Exception::class);\n $this->expectExceptionMessage('Database is down');\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>\n */\n public static function delayClampingProvider(): array\n {\n return [\n 'short retry uses retry_after as floor' => [\n 'retryAfter' => 1,\n 'expectedMin' => self::MIN_RETRY_DELAY,\n 'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n 'medium retry passes through' => [\n 'retryAfter' => 30,\n 'expectedMin' => 30,\n 'expectedMax' => 30 + self::JITTER_SECONDS,\n ],\n 'large retry clamped to max' => [\n 'retryAfter' => 86400,\n 'expectedMin' => self::MAX_RETRY_DELAY,\n 'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n ];\n }\n\n #[DataProvider('delayClampingProvider')]\n public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void\n {\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn(1);\n $job->expects($this->once())\n ->method('release')\n ->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {\n return $delay >= $expectedMin && $delay <= $expectedMax;\n }));\n\n $next = static function () use ($retryAfter): void {\n throw new RateLimitException('rate limited', $retryAfter);\n };\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{attempts: int, shouldLog: bool}>\n */\n public static function logSamplingProvider(): array\n {\n return [\n 'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],\n 'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],\n 'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],\n 'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],\n 'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],\n 'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],\n 'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],\n 'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],\n ];\n }\n\n #[DataProvider('logSamplingProvider')]\n public function testLogSampling(int $attempts, bool $shouldLog): void\n {\n if ($shouldLog) {\n Log::shouldReceive('info')\n ->once()\n ->with(\n '[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',\n $this->callback(static function (array $context) use ($attempts): bool {\n return $context['attempts'] === $attempts\n && $context['retry_after'] === 1\n && isset($context['delay']);\n })\n );\n } else {\n Log::shouldReceive('info')->never();\n }\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn($attempts);\n $job->expects($this->once())->method('release');\n\n $next = static function (): void {\n throw new RateLimitException('rate limited', 1);\n };\n\n $this->middleware->handle($job, $next);\n }\n}","depth":4,"bounds":{"left":0.122340426,"top":0.24581006,"width":0.30585107,"height":0.75418997},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Jobs\\Middleware;\n\nuse Exception;\nuse Illuminate\\Contracts\\Queue\\Job;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Jobs\\Middleware\\HandleHubspotRateLimit;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\n#[CoversClass(HandleHubspotRateLimit::class)]\nclass HandleHubspotRateLimitTest extends TestCase\n{\n private const int MAX_RETRY_DELAY = 600;\n private const int MIN_RETRY_DELAY = 1;\n private const int JITTER_SECONDS = 5;\n\n private HandleHubspotRateLimit $middleware;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n $this->middleware = new HandleHubspotRateLimit();\n }\n\n public function testPassesThroughWhenNoExceptionThrown(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $called = false;\n $next = function (object $passed) use ($job, &$called): void {\n $this->assertSame($job, $passed);\n $called = true;\n };\n\n $this->middleware->handle($job, $next);\n\n $this->assertTrue($called);\n }\n\n public function testPropagatesNonRateLimitExceptions(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $next = static function (): void {\n throw new Exception('Database is down');\n };\n\n $this->expectException(Exception::class);\n $this->expectExceptionMessage('Database is down');\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>\n */\n public static function delayClampingProvider(): array\n {\n return [\n 'short retry uses retry_after as floor' => [\n 'retryAfter' => 1,\n 'expectedMin' => self::MIN_RETRY_DELAY,\n 'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n 'medium retry passes through' => [\n 'retryAfter' => 30,\n 'expectedMin' => 30,\n 'expectedMax' => 30 + self::JITTER_SECONDS,\n ],\n 'large retry clamped to max' => [\n 'retryAfter' => 86400,\n 'expectedMin' => self::MAX_RETRY_DELAY,\n 'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n ];\n }\n\n #[DataProvider('delayClampingProvider')]\n public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void\n {\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn(1);\n $job->expects($this->once())\n ->method('release')\n ->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {\n return $delay >= $expectedMin && $delay <= $expectedMax;\n }));\n\n $next = static function () use ($retryAfter): void {\n throw new RateLimitException('rate limited', $retryAfter);\n };\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{attempts: int, shouldLog: bool}>\n */\n public static function logSamplingProvider(): array\n {\n return [\n 'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],\n 'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],\n 'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],\n 'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],\n 'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],\n 'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],\n 'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],\n 'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],\n ];\n }\n\n #[DataProvider('logSamplingProvider')]\n public function testLogSampling(int $attempts, bool $shouldLog): void\n {\n if ($shouldLog) {\n Log::shouldReceive('info')\n ->once()\n ->with(\n '[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',\n $this->callback(static function (array $context) use ($attempts): bool {\n return $context['attempts'] === $attempts\n && $context['retry_after'] === 1\n && isset($context['delay']);\n })\n );\n } else {\n Log::shouldReceive('info')->never();\n }\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn($attempts);\n $job->expects($this->once())->method('release');\n\n $next = static function (): void {\n throw new RateLimitException('rate limited', 1);\n };\n\n $this->middleware->handle($job, $next);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"19","depth":4,"bounds":{"left":0.6296542,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6409575,"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.64827126,"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":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"bounds":{"left":0.43018618,"top":0.09736632,"width":0.56981385,"height":0.8818835},"on_screen":true,"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","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}]...
|
-9091955119622055673
|
-2526499794819392950
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Tests\Unit\Jobs\Middleware;
use Exception;
use Illuminate\Contracts\Queue\Job;
use Illuminate\Support\Facades\Log;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
#[CoversClass(HandleHubspotRateLimit::class)]
class HandleHubspotRateLimitTest extends TestCase
{
private const int MAX_RETRY_DELAY = 600;
private const int MIN_RETRY_DELAY = 1;
private const int JITTER_SECONDS = 5;
private HandleHubspotRateLimit $middleware;
protected function setUp(): void
{
parent::setUp();
$this->middleware = new HandleHubspotRateLimit();
}
public function testPassesThroughWhenNoExceptionThrown(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$called = false;
$next = function (object $passed) use ($job, &$called): void {
$this->assertSame($job, $passed);
$called = true;
};
$this->middleware->handle($job, $next);
$this->assertTrue($called);
}
public function testPropagatesNonRateLimitExceptions(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$next = static function (): void {
throw new Exception('Database is down');
};
$this->expectException(Exception::class);
$this->expectExceptionMessage('Database is down');
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>
*/
public static function delayClampingProvider(): array
{
return [
'short retry uses retry_after as floor' => [
'retryAfter' => 1,
'expectedMin' => self::MIN_RETRY_DELAY,
'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,
],
'medium retry passes through' => [
'retryAfter' => 30,
'expectedMin' => 30,
'expectedMax' => 30 + self::JITTER_SECONDS,
],
'large retry clamped to max' => [
'retryAfter' => 86400,
'expectedMin' => self::MAX_RETRY_DELAY,
'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,
],
];
}
#[DataProvider('delayClampingProvider')]
public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void
{
Log::shouldReceive('info')->zeroOrMoreTimes();
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn(1);
$job->expects($this->once())
->method('release')
->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {
return $delay >= $expectedMin && $delay <= $expectedMax;
}));
$next = static function () use ($retryAfter): void {
throw new RateLimitException('rate limited', $retryAfter);
};
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{attempts: int, shouldLog: bool}>
*/
public static function logSamplingProvider(): array
{
return [
'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],
'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],
'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],
'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],
'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],
'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],
'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],
'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],
];
}
#[DataProvider('logSamplingProvider')]
public function testLogSampling(int $attempts, bool $shouldLog): void
{
if ($shouldLog) {
Log::shouldReceive('info')
->once()
->with(
'[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',
$this->callback(static function (array $context) use ($attempts): bool {
return $context['attempts'] === $attempts
&& $context['retry_after'] === 1
&& isset($context['delay']);
})
);
} else {
Log::shouldReceive('info')->never();
}
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn($attempts);
$job->expects($this->once())->method('release');
$next = static function (): void {
throw new RateLimitException('rate limited', 1);
};
$this->middleware->handle($job, $next);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
17082
|
765
|
11
|
2026-05-11T10:02:49.676163+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778493769676_m2.jpg...
|
PhpStorm
|
faVsco.js – HandleHubspotRateLimitTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Tests\Unit\Jobs\Middleware;
use Exception;
use Illuminate\Contracts\Queue\Job;
use Illuminate\Support\Facades\Log;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
#[CoversClass(HandleHubspotRateLimit::class)]
class HandleHubspotRateLimitTest extends TestCase
{
private const int MAX_RETRY_DELAY = 600;
private const int MIN_RETRY_DELAY = 1;
private const int JITTER_SECONDS = 5;
private HandleHubspotRateLimit $middleware;
protected function setUp(): void
{
parent::setUp();
$this->middleware = new HandleHubspotRateLimit();
}
public function testPassesThroughWhenNoExceptionThrown(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$called = false;
$next = function (object $passed) use ($job, &$called): void {
$this->assertSame($job, $passed);
$called = true;
};
$this->middleware->handle($job, $next);
$this->assertTrue($called);
}
public function testPropagatesNonRateLimitExceptions(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$next = static function (): void {
throw new Exception('Database is down');
};
$this->expectException(Exception::class);
$this->expectExceptionMessage('Database is down');
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>
*/
public static function delayClampingProvider(): array
{
return [
'short retry uses retry_after as floor' => [
'retryAfter' => 1,
'expectedMin' => self::MIN_RETRY_DELAY,
'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,
],
'medium retry passes through' => [
'retryAfter' => 30,
'expectedMin' => 30,
'expectedMax' => 30 + self::JITTER_SECONDS,
],
'large retry clamped to max' => [
'retryAfter' => 86400,
'expectedMin' => self::MAX_RETRY_DELAY,
'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,
],
];
}
#[DataProvider('delayClampingProvider')]
public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void
{
Log::shouldReceive('info')->zeroOrMoreTimes();
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn(1);
$job->expects($this->once())
->method('release')
->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {
return $delay >= $expectedMin && $delay <= $expectedMax;
}));
$next = static function () use ($retryAfter): void {
throw new RateLimitException('rate limited', $retryAfter);
};
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{attempts: int, shouldLog: bool}>
*/
public static function logSamplingProvider(): array
{
return [
'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],
'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],
'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],
'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],
'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],
'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],
'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],
'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],
];
}
#[DataProvider('logSamplingProvider')]
public function testLogSampling(int $attempts, bool $shouldLog): void
{
if ($shouldLog) {
Log::shouldReceive('info')
->once()
->with(
'[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',
$this->callback(static function (array $context) use ($attempts): bool {
return $context['attempts'] === $attempts
&& $context['retry_after'] === 1
&& isset($context['delay']);
})
);
} else {
Log::shouldReceive('info')->never();
}
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn($attempts);
$job->expects($this->once())->method('release');
$next = static function (): void {
throw new RateLimitException('rate limited', 1);
};
$this->middleware->handle($job, $next);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
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":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","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.82413566,"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":"HandleHubspotRateLimitTest","depth":6,"bounds":{"left":0.8394282,"top":0.019952115,"width":0.076130316,"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 'HandleHubspotRateLimitTest'","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 'HandleHubspotRateLimitTest'","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 Tests\\Unit\\Jobs\\Middleware;\n\nuse Exception;\nuse Illuminate\\Contracts\\Queue\\Job;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Jobs\\Middleware\\HandleHubspotRateLimit;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\n#[CoversClass(HandleHubspotRateLimit::class)]\nclass HandleHubspotRateLimitTest extends TestCase\n{\n private const int MAX_RETRY_DELAY = 600;\n private const int MIN_RETRY_DELAY = 1;\n private const int JITTER_SECONDS = 5;\n\n private HandleHubspotRateLimit $middleware;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n $this->middleware = new HandleHubspotRateLimit();\n }\n\n public function testPassesThroughWhenNoExceptionThrown(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $called = false;\n $next = function (object $passed) use ($job, &$called): void {\n $this->assertSame($job, $passed);\n $called = true;\n };\n\n $this->middleware->handle($job, $next);\n\n $this->assertTrue($called);\n }\n\n public function testPropagatesNonRateLimitExceptions(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $next = static function (): void {\n throw new Exception('Database is down');\n };\n\n $this->expectException(Exception::class);\n $this->expectExceptionMessage('Database is down');\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>\n */\n public static function delayClampingProvider(): array\n {\n return [\n 'short retry uses retry_after as floor' => [\n 'retryAfter' => 1,\n 'expectedMin' => self::MIN_RETRY_DELAY,\n 'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n 'medium retry passes through' => [\n 'retryAfter' => 30,\n 'expectedMin' => 30,\n 'expectedMax' => 30 + self::JITTER_SECONDS,\n ],\n 'large retry clamped to max' => [\n 'retryAfter' => 86400,\n 'expectedMin' => self::MAX_RETRY_DELAY,\n 'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n ];\n }\n\n #[DataProvider('delayClampingProvider')]\n public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void\n {\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn(1);\n $job->expects($this->once())\n ->method('release')\n ->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {\n return $delay >= $expectedMin && $delay <= $expectedMax;\n }));\n\n $next = static function () use ($retryAfter): void {\n throw new RateLimitException('rate limited', $retryAfter);\n };\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{attempts: int, shouldLog: bool}>\n */\n public static function logSamplingProvider(): array\n {\n return [\n 'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],\n 'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],\n 'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],\n 'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],\n 'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],\n 'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],\n 'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],\n 'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],\n ];\n }\n\n #[DataProvider('logSamplingProvider')]\n public function testLogSampling(int $attempts, bool $shouldLog): void\n {\n if ($shouldLog) {\n Log::shouldReceive('info')\n ->once()\n ->with(\n '[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',\n $this->callback(static function (array $context) use ($attempts): bool {\n return $context['attempts'] === $attempts\n && $context['retry_after'] === 1\n && isset($context['delay']);\n })\n );\n } else {\n Log::shouldReceive('info')->never();\n }\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn($attempts);\n $job->expects($this->once())->method('release');\n\n $next = static function (): void {\n throw new RateLimitException('rate limited', 1);\n };\n\n $this->middleware->handle($job, $next);\n }\n}","depth":4,"bounds":{"left":0.122340426,"top":0.24581006,"width":0.30585107,"height":0.75418997},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Jobs\\Middleware;\n\nuse Exception;\nuse Illuminate\\Contracts\\Queue\\Job;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Jobs\\Middleware\\HandleHubspotRateLimit;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Tests\\TestCase;\n\n#[CoversClass(HandleHubspotRateLimit::class)]\nclass HandleHubspotRateLimitTest extends TestCase\n{\n private const int MAX_RETRY_DELAY = 600;\n private const int MIN_RETRY_DELAY = 1;\n private const int JITTER_SECONDS = 5;\n\n private HandleHubspotRateLimit $middleware;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n $this->middleware = new HandleHubspotRateLimit();\n }\n\n public function testPassesThroughWhenNoExceptionThrown(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $called = false;\n $next = function (object $passed) use ($job, &$called): void {\n $this->assertSame($job, $passed);\n $called = true;\n };\n\n $this->middleware->handle($job, $next);\n\n $this->assertTrue($called);\n }\n\n public function testPropagatesNonRateLimitExceptions(): void\n {\n $job = $this->createMock(Job::class);\n $job->expects($this->never())->method('release');\n\n $next = static function (): void {\n throw new Exception('Database is down');\n };\n\n $this->expectException(Exception::class);\n $this->expectExceptionMessage('Database is down');\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>\n */\n public static function delayClampingProvider(): array\n {\n return [\n 'short retry uses retry_after as floor' => [\n 'retryAfter' => 1,\n 'expectedMin' => self::MIN_RETRY_DELAY,\n 'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n 'medium retry passes through' => [\n 'retryAfter' => 30,\n 'expectedMin' => 30,\n 'expectedMax' => 30 + self::JITTER_SECONDS,\n ],\n 'large retry clamped to max' => [\n 'retryAfter' => 86400,\n 'expectedMin' => self::MAX_RETRY_DELAY,\n 'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,\n ],\n ];\n }\n\n #[DataProvider('delayClampingProvider')]\n public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void\n {\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn(1);\n $job->expects($this->once())\n ->method('release')\n ->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {\n return $delay >= $expectedMin && $delay <= $expectedMax;\n }));\n\n $next = static function () use ($retryAfter): void {\n throw new RateLimitException('rate limited', $retryAfter);\n };\n\n $this->middleware->handle($job, $next);\n }\n\n /**\n * @return array<string, array{attempts: int, shouldLog: bool}>\n */\n public static function logSamplingProvider(): array\n {\n return [\n 'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],\n 'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],\n 'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],\n 'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],\n 'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],\n 'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],\n 'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],\n 'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],\n ];\n }\n\n #[DataProvider('logSamplingProvider')]\n public function testLogSampling(int $attempts, bool $shouldLog): void\n {\n if ($shouldLog) {\n Log::shouldReceive('info')\n ->once()\n ->with(\n '[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',\n $this->callback(static function (array $context) use ($attempts): bool {\n return $context['attempts'] === $attempts\n && $context['retry_after'] === 1\n && isset($context['delay']);\n })\n );\n } else {\n Log::shouldReceive('info')->never();\n }\n\n /** @var Job&MockObject $job */\n $job = $this->createMock(Job::class);\n $job->method('attempts')->willReturn($attempts);\n $job->expects($this->once())->method('release');\n\n $next = static function (): void {\n throw new RateLimitException('rate limited', 1);\n };\n\n $this->middleware->handle($job, $next);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"19","depth":4,"bounds":{"left":0.6296542,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6409575,"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.64827126,"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":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"bounds":{"left":0.43018618,"top":0.09736632,"width":0.56981385,"height":0.8818835},"on_screen":true,"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","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}]...
|
-9091955119622055673
|
-2526499794819392950
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Tests\Unit\Jobs\Middleware;
use Exception;
use Illuminate\Contracts\Queue\Job;
use Illuminate\Support\Facades\Log;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
#[CoversClass(HandleHubspotRateLimit::class)]
class HandleHubspotRateLimitTest extends TestCase
{
private const int MAX_RETRY_DELAY = 600;
private const int MIN_RETRY_DELAY = 1;
private const int JITTER_SECONDS = 5;
private HandleHubspotRateLimit $middleware;
protected function setUp(): void
{
parent::setUp();
$this->middleware = new HandleHubspotRateLimit();
}
public function testPassesThroughWhenNoExceptionThrown(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$called = false;
$next = function (object $passed) use ($job, &$called): void {
$this->assertSame($job, $passed);
$called = true;
};
$this->middleware->handle($job, $next);
$this->assertTrue($called);
}
public function testPropagatesNonRateLimitExceptions(): void
{
$job = $this->createMock(Job::class);
$job->expects($this->never())->method('release');
$next = static function (): void {
throw new Exception('Database is down');
};
$this->expectException(Exception::class);
$this->expectExceptionMessage('Database is down');
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{retryAfter: int, expectedMin: int, expectedMax: int}>
*/
public static function delayClampingProvider(): array
{
return [
'short retry uses retry_after as floor' => [
'retryAfter' => 1,
'expectedMin' => self::MIN_RETRY_DELAY,
'expectedMax' => self::MIN_RETRY_DELAY + self::JITTER_SECONDS,
],
'medium retry passes through' => [
'retryAfter' => 30,
'expectedMin' => 30,
'expectedMax' => 30 + self::JITTER_SECONDS,
],
'large retry clamped to max' => [
'retryAfter' => 86400,
'expectedMin' => self::MAX_RETRY_DELAY,
'expectedMax' => self::MAX_RETRY_DELAY + self::JITTER_SECONDS,
],
];
}
#[DataProvider('delayClampingProvider')]
public function testReleasesJobWithClampedDelay(int $retryAfter, int $expectedMin, int $expectedMax): void
{
Log::shouldReceive('info')->zeroOrMoreTimes();
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn(1);
$job->expects($this->once())
->method('release')
->with($this->callback(static function (int $delay) use ($expectedMin, $expectedMax): bool {
return $delay >= $expectedMin && $delay <= $expectedMax;
}));
$next = static function () use ($retryAfter): void {
throw new RateLimitException('rate limited', $retryAfter);
};
$this->middleware->handle($job, $next);
}
/**
* @return array<string, array{attempts: int, shouldLog: bool}>
*/
public static function logSamplingProvider(): array
{
return [
'first attempt logs' => ['attempts' => 1, 'shouldLog' => true],
'second attempt logs' => ['attempts' => 2, 'shouldLog' => true],
'third attempt logs' => ['attempts' => 3, 'shouldLog' => true],
'fourth attempt skipped' => ['attempts' => 4, 'shouldLog' => false],
'ninth attempt skipped' => ['attempts' => 9, 'shouldLog' => false],
'tenth attempt logs (multiple of 10)' => ['attempts' => 10, 'shouldLog' => true],
'eleventh attempt skipped' => ['attempts' => 11, 'shouldLog' => false],
'twentieth attempt logs' => ['attempts' => 20, 'shouldLog' => true],
];
}
#[DataProvider('logSamplingProvider')]
public function testLogSampling(int $attempts, bool $shouldLog): void
{
if ($shouldLog) {
Log::shouldReceive('info')
->once()
->with(
'[HandleHubspotRateLimit] Rate limit caught, releasing job with delay',
$this->callback(static function (array $context) use ($attempts): bool {
return $context['attempts'] === $attempts
&& $context['retry_after'] === 1
&& isset($context['delay']);
})
);
} else {
Log::shouldReceive('info')->never();
}
/** @var Job&MockObject $job */
$job = $this->createMock(Job::class);
$job->method('attempts')->willReturn($attempts);
$job->expects($this->once())->method('release');
$next = static function (): void {
throw new RateLimitException('rate limited', 1);
};
$this->middleware->handle($job, $next);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
82655
|
2859
|
11
|
2026-05-28T09:38:52.998132+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779961132998_m1.jpg...
|
Firefox
|
Jiminny — Work
|
1
|
app.jiminny.eu/dashboard
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix missing header by LakyLak · Pull Request #12136 · jiminny/app
JY-20915 fix missing header by LakyLak · Pull Request #12136 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny Mail
Jiminny Mail
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Unnamed Group
Jiminny
Jiminny
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Sona Subramanian at 27/05/2026, 17:46:08 - Session Replay - LogRocket
Sona Subramanian at 27/05/2026, 17:46:08 - Session Replay - LogRocket
Iliyana Netseva at 27/05/2026, 18:48:18 - Session Replay - LogRocket
Iliyana Netseva at 27/05/2026, 18:48:18 - Session Replay - LogRocket
Jiminny
Jiminny
Close tab
New Tab
New Tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
20
activities
Get Notified
Sort by Sort by: Most recent
Sort by
Sort by:
Most recent
Team:
Engineering
Duration:
1m and above
Recorded:
Only Recorded
Transcript:
price OR pay OR budget
Save Search
Clear all
Saved searches Test transcript
Saved searches
Test transcript
Team
Engineering × Search teams
Engineering
×
Search teams
Host
Search team members Search team members
Search team members
Search team members
Also search as participant
Participant
Search team members Search team members
Search team members
Search team members
Customer
Customer
Transcript
price OR pay OR budget
Clear
Select option Said by
Select option
Said by
Select option Anyone
Select option
Anyone
Period
All time
Topics
Search topics Search topics
Search topics
Search topics
Activity type
Search activity types Search activity types
Search activity types
Search activity types
Duration
Min (minutes)
1
Max (minutes)
AI call score
Select AI call score Select AI call score
Select AI call score
Select AI call score
Key Words Score
Select key words score Select key words score
Select key words score
Select key words score
Coaching score
Select coaching score Select coaching score
Select coaching score
Select coaching score
Coach
Search coaches Search coaches
Search coaches
Search coaches
Stage at call
Search stages Search stages
Search stages
Search stages
Current stage
Search stages Search stages
Search stages
Search stages
Language
Search language Search language
Search language
Search language
Playlist
Search playlists Search playlists
Search playlists
Search playlists
Pending CRM notes
Not logged to CRM
Recorded
Search recorded Only Recorded
Search recorded
Only Recorded
Show internal and external activities
Select option Select option
Select option
Select option
Platform
Search platforms Search platforms
Search platforms
Search platforms
Call type
Search channels Search channels
Search channels
Search channels
Outcome
Search CRM Outcome Search CRM Outcome
Search CRM Outcome
Search CRM Outcome
Deal value
Min (amount)
Max (amount)
Deal close date
All time
Deal age
0
1418d
2836d+
Talking speed
0
177 wpm
252 wpm+
Talk ratio
0%
40%
60%
100%
Patience
0s
1s
2s
3s+
Longest monologue
0
5m
10m+
Longest customer story
0
5m
10m+
Rep questions
0
25
50+
Engaging questions
0
25
50+
Insightful questions
0
25
50+
Customer questions
0
25
50+
Comments
0...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"BE upgrade libraries","depth":4,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Text relay","depth":4,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 fix missing header by LakyLak · Pull Request #12136 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 fix missing header by LakyLak · Pull Request #12136 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Login | Salesforce","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Login | Salesforce","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny Mail","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny Mail","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Sona Subramanian at 27/05/2026, 17:46:08 - Session Replay - LogRocket","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sona Subramanian at 27/05/2026, 17:46:08 - Session Replay - LogRocket","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Iliyana Netseva at 27/05/2026, 18:48:18 - Session Replay - LogRocket","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Iliyana Netseva at 27/05/2026, 18:48:18 - Session Replay - LogRocket","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"20","depth":8,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activities","depth":8,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Get Notified","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXComboBox","text":"Sort by Sort by: Most recent","depth":7,"on_screen":true,"value":"Sort by Sort by: Most recent","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Sort by","depth":8,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sort by:","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Most recent","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Team:","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Engineering","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1m and above","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Recorded:","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Only Recorded","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Transcript:","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"price OR pay OR budget","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Save Search","depth":8,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Clear all","depth":8,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXComboBox","text":"Saved searches Test transcript","depth":7,"on_screen":true,"value":"Saved searches Test transcript","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Saved searches","depth":9,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Test transcript","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Team","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Engineering × Search teams","depth":6,"on_screen":true,"value":"Engineering × Search teams","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Engineering","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"×","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"Search teams","depth":7,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Host","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Search team members Search team members","depth":6,"on_screen":true,"value":"Search team members Search team members","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Search team members","depth":7,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Search team members","depth":8,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Also search as participant","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Participant","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Search team members Search team members","depth":6,"on_screen":true,"value":"Search team members Search team members","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Search team members","depth":7,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Search team members","depth":8,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Customer","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"Customer","depth":6,"on_screen":true,"help_text":"","placeholder":"Customer or Subject","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Transcript","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"price OR pay OR budget","depth":6,"on_screen":true,"value":"price OR pay OR budget","help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Clear","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXComboBox","text":"Select option Said by","depth":6,"on_screen":true,"value":"Select option Said by","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Select option","depth":7,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Said by","depth":8,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Select option Anyone","depth":6,"on_screen":true,"value":"Select option Anyone","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Select option","depth":7,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Anyone","depth":8,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Period","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"All time","depth":8,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Topics","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Search topics Search topics","depth":6,"on_screen":true,"value":"Search topics Search topics","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Search topics","depth":7,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Search topics","depth":8,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Activity type","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Search activity types Search activity types","depth":6,"on_screen":true,"value":"Search activity types Search activity types","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Search activity types","depth":7,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Search activity types","depth":8,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Min (minutes)","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Max (minutes)","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AI call score","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Select AI call score Select AI call score","depth":6,"on_screen":true,"value":"Select AI call score Select AI call score","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Select AI call score","depth":7,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Select AI call score","depth":8,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Key Words Score","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Select key words score Select key words score","depth":6,"on_screen":true,"value":"Select key words score Select key words score","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Select key words score","depth":7,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Select key words score","depth":8,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Coaching score","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Select coaching score Select coaching score","depth":6,"on_screen":true,"value":"Select coaching score Select coaching score","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Select coaching score","depth":7,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Select coaching score","depth":8,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Coach","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Search coaches Search coaches","depth":6,"on_screen":true,"value":"Search coaches Search coaches","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Search coaches","depth":7,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Search coaches","depth":8,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Stage at call","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Search stages Search stages","depth":6,"on_screen":false,"value":"Search stages Search stages","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Search stages","depth":7,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Search stages","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Current stage","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Search stages Search stages","depth":6,"on_screen":false,"value":"Search stages Search stages","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Search stages","depth":7,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Search stages","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Language","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Search language Search language","depth":6,"on_screen":false,"value":"Search language Search language","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Search language","depth":7,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Search language","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Playlist","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Search playlists Search playlists","depth":6,"on_screen":false,"value":"Search playlists Search playlists","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Search playlists","depth":7,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Search playlists","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Pending CRM notes","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Not logged to CRM","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Recorded","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Search recorded Only Recorded","depth":6,"on_screen":false,"value":"Search recorded Only Recorded","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Search recorded","depth":7,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Only Recorded","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Show internal and external activities","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Select option Select option","depth":6,"on_screen":false,"value":"Select option Select option","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Select option","depth":7,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Select option","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Platform","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Search platforms Search platforms","depth":6,"on_screen":false,"value":"Search platforms Search platforms","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Search platforms","depth":7,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Search platforms","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Call type","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Search channels Search channels","depth":6,"on_screen":false,"value":"Search channels Search channels","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Search channels","depth":7,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Search channels","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Outcome","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Search CRM Outcome Search CRM Outcome","depth":6,"on_screen":false,"value":"Search CRM Outcome Search CRM Outcome","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Search CRM Outcome","depth":7,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Search CRM Outcome","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deal value","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Min (amount)","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Max (amount)","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deal close date","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"All time","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deal age","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1418d","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2836d+","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Talking speed","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"177 wpm","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"252 wpm+","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Talk ratio","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0%","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"40%","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"60%","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"100%","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Patience","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1s","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2s","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3s+","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Longest monologue","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5m","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"10m+","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Longest customer story","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5m","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"10m+","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Rep questions","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"50+","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Engaging questions","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"50+","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Insightful questions","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"50+","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Customer questions","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"50+","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Comments","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-9091801616839874418
|
-5692043048197132063
|
typing_pause
|
accessibility
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix missing header by LakyLak · Pull Request #12136 · jiminny/app
JY-20915 fix missing header by LakyLak · Pull Request #12136 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny Mail
Jiminny Mail
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Unnamed Group
Jiminny
Jiminny
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Sona Subramanian at 27/05/2026, 17:46:08 - Session Replay - LogRocket
Sona Subramanian at 27/05/2026, 17:46:08 - Session Replay - LogRocket
Iliyana Netseva at 27/05/2026, 18:48:18 - Session Replay - LogRocket
Iliyana Netseva at 27/05/2026, 18:48:18 - Session Replay - LogRocket
Jiminny
Jiminny
Close tab
New Tab
New Tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
20
activities
Get Notified
Sort by Sort by: Most recent
Sort by
Sort by:
Most recent
Team:
Engineering
Duration:
1m and above
Recorded:
Only Recorded
Transcript:
price OR pay OR budget
Save Search
Clear all
Saved searches Test transcript
Saved searches
Test transcript
Team
Engineering × Search teams
Engineering
×
Search teams
Host
Search team members Search team members
Search team members
Search team members
Also search as participant
Participant
Search team members Search team members
Search team members
Search team members
Customer
Customer
Transcript
price OR pay OR budget
Clear
Select option Said by
Select option
Said by
Select option Anyone
Select option
Anyone
Period
All time
Topics
Search topics Search topics
Search topics
Search topics
Activity type
Search activity types Search activity types
Search activity types
Search activity types
Duration
Min (minutes)
1
Max (minutes)
AI call score
Select AI call score Select AI call score
Select AI call score
Select AI call score
Key Words Score
Select key words score Select key words score
Select key words score
Select key words score
Coaching score
Select coaching score Select coaching score
Select coaching score
Select coaching score
Coach
Search coaches Search coaches
Search coaches
Search coaches
Stage at call
Search stages Search stages
Search stages
Search stages
Current stage
Search stages Search stages
Search stages
Search stages
Language
Search language Search language
Search language
Search language
Playlist
Search playlists Search playlists
Search playlists
Search playlists
Pending CRM notes
Not logged to CRM
Recorded
Search recorded Only Recorded
Search recorded
Only Recorded
Show internal and external activities
Select option Select option
Select option
Select option
Platform
Search platforms Search platforms
Search platforms
Search platforms
Call type
Search channels Search channels
Search channels
Search channels
Outcome
Search CRM Outcome Search CRM Outcome
Search CRM Outcome
Search CRM Outcome
Deal value
Min (amount)
Max (amount)
Deal close date
All time
Deal age
0
1418d
2836d+
Talking speed
0
177 wpm
252 wpm+
Talk ratio
0%
40%
60%
100%
Patience
0s
1s
2s
3s+
Longest monologue
0
5m
10m+
Longest customer story
0
5m
10m+
Rep questions
0
25
50+
Engaging questions
0
25
50+
Insightful questions
0
25
50+
Customer questions
0
25
50+
Comments
0...
|
82652
|
NULL
|
NULL
|
NULL
|
|
53374
|
1871
|
60
|
2026-05-18T12:04:37.408108+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779105877408_m2.jpg...
|
Firefox
|
Meet - [Platform] Refinement 🔍 — Work
|
1
|
meet.google.com/cxs-eips-npt?authuser=lukas.kovali meet.google.com/cxs-eips-npt?authuser=lukas.kovalik%40jiminny.com...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Meet - [Platform] Refinement 🔍
Close tab
New Tab
O Meet - [Platform] Refinement 🔍
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
Return to home screen
[EMAIL]
Switch account
Switch account
Lukas Kovalik
More options
Turn off microphone
Turn off camera
Turn on background blur
Microphone: MacBook Pro Microphone
Speaker: System Default Speaker Device
Camera: FaceTime HD Camera...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Meet - [Platform] Refinement 🔍","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.016123671,"height":-0.051875472},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.27094415,"top":1.0,"width":0.004986702,"height":-0.051875472},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.27310506,"top":1.0,"width":0.010638298,"height":-0.086193085},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Return to home screen","depth":10,"bounds":{"left":0.2917221,"top":1.0,"width":0.034906916,"height":-0.06464481},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"lukas.kovalik@jiminny.com","depth":12,"bounds":{"left":0.67204124,"top":1.0,"width":0.055684842,"height":-0.06584203},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Switch account","depth":11,"bounds":{"left":0.6981383,"top":1.0,"width":0.029587766,"height":-0.07901037},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Switch account","depth":12,"bounds":{"left":0.6981383,"top":1.0,"width":0.029587766,"height":-0.07901037},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Lukas Kovalik","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"More options","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Turn off microphone","depth":14,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Turn off camera","depth":14,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Turn on background blur","depth":13,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Microphone: MacBook Pro Microphone","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Speaker: System Default Speaker Device","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Camera: FaceTime HD Camera","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-9090981097589517096
|
1029202481461942994
|
click
|
hybrid
|
NULL
|
Meet - [Platform] Refinement 🔍
Close tab
New Tab
O Meet - [Platform] Refinement 🔍
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
Return to home screen
[EMAIL]
Switch account
Switch account
Lukas Kovalik
More options
Turn off microphone
Turn off camera
Turn on background blur
Microphone: MacBook Pro Microphone
Speaker: System Default Speaker Device
Camera: FaceTime HD Camera
PhostormVIewINavicatecodeLaravelKeтactolFV faVsco.js°9 master kProledeyCActivityController.ongC BaseService.onp© SoftPhoneManager.php(c) Constants.one(C) CoreUserRequest.onp© ScimProvisioning.php x © CoreUser.php• Scimintertace.ongD Seedern lictonorDoloCannotllavo AdminOriarW sentryW senlalizerSettingsSidekickW Slackw Transcriptionaaiwillio• M Conference™ ConferenceHandlerC) SoftPhoneManader.ohd© ConferenceCallbackHandler.php© ConferenceManager.phpIMnTOTM EventD ExceptionD JobTh Recolverm CorvicoDvO(c) TwilioConstants.onp© TwilioRepository.php0 Uploader0 UrlGeneratora Utility0 Uuid_ Waverorm_WeohooksM Workflow> M ConfigurationM Consolev CommandsM Activities>M Analvticc• Calendarsv MCrmM Huhsnotclass ScimProvisioningprivate function assignRolesFromScim(Usen Susen, ConeUsenRequest Sdto, Tean Steam): voidroles' => Sroleldsi."roles' =>'array'main: 1'0new DependentRolesPule(SvisibleRoles)lnew ListenerRoleCannotHaveAdmin0rManagerPermissionRule(SvisibleRoles),'roles.*' => [anstinet""an.• SvisibleRoles->pluck( value: 'id')->implode( value: ',').now lictononPoloPonuinoclictonenSostunePule/ctoam)lif (Svalidator->fails0) {return:sroles = "viszoleroles->wheren key: "1d', Srolelds):Sdto = new UserRolesSvncoT0roles: Sroles.public function createNewGroup(CoreGroupRequest $dto, Team $team): ?Group{...}public function updateGroupAttributesIfChanged(Group $group, CoreGroupRequest $dto, Team $team): GroIntegrationApp• M TraitsC) Addl avoutEntities.nhn© AutologDelayedCommand.php9 Pockfill@nnortunitvllcorSromAccountle RullbornCommand Ahctraat nhn(e) RullbornDinaCommand nhn=laravel.log4 SF jiminny@localhost]4 HS_local [jiminny@localhost]& console [PROD]# console [euyA console [STAGING] X554555556557558'_563566-567568569570v Tx: AutovGo jiminny_jupiterwhere crm_configuration_id = 1 and pl.playbook_id = 175:BlS BIS XUAselect * from teamsSELECT r.* FROM automated_reports rjoin teams t on r.team id = t.idhckE r.trequency = "dallyAND t.status = 'active'AND (r.expires.at >= now() OR r.expires.at IS NULL);1 automated_report_results where report 1d IN (18, 339select * from users where team_id = 1 and id = 1047;SELE * FROMIsochal accounts WHERE soctable 10 = 10471select * from activity_searches where id = 10932:select * from activity_search_filters where activity_search_id = 10932;select * fromautomated_reports order by id desc;select * from automated_report_results order by id desc;select * from automated_reports where id IN (55);select * from automated_report_results where id IN (81);select * from users where id IN (10633, 13987, 11985);select * from users where group_id IN (3710):SELECT * FROM automated_reports WHERE uuid_to_bin('18a06a75-afd2-476f-aadc-14d4057bdda2') = uuidSELECT * FROM automated_report_results WHERE uuid_to_bin('582d4b50-8cd3-42a9-9819-d676ff8f3b43') = uuidselect * from teams;select * from accounts where team_id = 1:select * trom automated report results where media type = "pdt' and status = 2SFLECT * FROM automated report results WHERE uuid_ to_ bin('82e74956-6144-4cd1-a3d3-af985c3070a4') = uuid)select * from teams where id = 1029;select * fromcrm_configurations where provider = 'pipedrive':SELECTCONCAT(u,id. CASE WHEN u.id = t.owner id THEN • (owner)' ELSE •• END) AS user idisa.*t.owner id FROM social accounts saJJouN users u on u.id = sa.sociable idi1.n<->1: on +.id = u.team idWHEPE uteam id = 1029 and sa-nnoviden = 'ninednive'."usen idl. "23460 ownen)""idi. ko• мon 10 May 10:04.30AskJiminnyReportActivityServiceTest vCascadeImplement Trial OwneFixing Redis Rate LimSalestorce Token FalliSCIM Role Manageme+0 ..inp on LineWARN Metadata found iin doc-comment for method9/ 10 tacke doneWrite unit tests for SCIM role management10) Contact Mario to vodate customer10 / 10 tasks doneselm role managementtor leamtallor has been successtully implemented. Here's what was completed:Implementation Summary:1. CoreUserRequest DTO - Added role parsina from SCIM requests2. SCIM Constants - Added META ROLES and USER_PATCH_ ATTR_ ROLES constants3. RoleAttr Mutator - Created mutator for handlina role assianments via SCIM PATCH operations4. ScimProvisioning - Updated to handle roles during user creation with proper validation5. ListenerRoleCannotHaveAdminOrManagerPermissionRule - Created validation rule to prevent listener role fromhaving admin/manager permissions6. CoreUser Response - Updated to include roles in SClM responses7. Unit Tests - Created comprehensive tests for the validation ruleKey Features:• Customers can now assign roles via SCIM (recorder, recorder & voice, analyst, listener, admin, manager)• Validation ensures listener role is onlv assianable if the LISTENER ROLE feature flaa is enabled•LIstener role cannot de comolned with aamin or manager permissions• Uses existing UpdateUserRolesAction for role syncing and logging• Follows platform validation rules (DependentRolesRule, ListenerRoleRequiresListenerFeatureRule)Next Step: Please contact Mario to update the customer that SCIM role management is now available for Teamtailorintearationposwtn chaten vapp/Component/SClM/ Constants.php +3app/Component/SCIM/ ® ScimProvisioning.php +85-15nse/ CoreUser.php +21ann/Comnonent/SCIM/Mutatore/Attributes/Ueer/M PoleAttr.nhn +17-ites/Userl ẞ RoleAttrTest.ohn +224* Reiect alliiAccent alliAsk anvthina 884-L)« Code SWF-1.60 01W Windsurf Teams 1:1 UTF-8fo 4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
71286
|
2562
|
3
|
2026-05-26T06:37:53.763750+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-26/1779 /Users/lukas/.screenpipe/data/data/2026-05-26/1779777473763_m1.jpg...
|
iTerm2
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
iTerm2ShellEditViewSessionScriptsProfilesWindowHel iTerm2ShellEditViewSessionScriptsProfilesWindowHelpEU (ssh)APP (-zsh)100% C8• Tue 26 May 9:37:53-zsh181DOCKERDOCKER (docker-compose)881DEV (-zsh)₴2msDONEdocker_lamp_1RUNNINGdocker_1amp_1msDONEdocker_lamp_1docker_lamp_11sDONEdocker_lamp_1fd/1'2>&1docker_lamp_12S DONEdocker_1amp_11/fd/1'2>&1docker_lamp_1s]1s DONEdocker_lamp_12026-05-2606:36:22 Jiminny\Jobs\Mailbox\SyncInbox2026-05-2606:36:22 Jiminny\Jobs \Mailbox\SyncInbox100.142026-05-26 06:37:03 Running ['artisan'meeting-bot: schedule-bot]1 '/usr/local/bin/php' 'artisan'meeting-bot: schedule-bot › */proc/1/2026-05-26 06:37:04 Running ['artisan' dialers:monitor-activities].1 '/usr/local/bin/php' 'artisan' dialers:monitor-activities › '/proc/2026-05-26 06:37:07 Running ['artisan' jiminny:monitor-social-account1 '/usr/local/bin/php' 'artisan' jiminny:monitor-social-accounts > '/docker_lamp_12026-05-26 06:37:08 Running ['artisan' mailbox:skip-lists:refresh].1S DONEdocker_lamp_11/fd/1'1 '/usr/local/bin/php' 'artisan'mailbox:skip-lists:refresh > */proc/2>&1docker_1amp_12026-05-26 06:37:10 Running ['artisan'mailbox:batch:process--max-batches=15]1S DONEdocker_lamp_111 '/usr/local/bin/php' 'artisan'mailbox:batch:process --max-batches=15 > */proc/1/fd/1* 2>&1docker_lamp_12026-05-26 06:37:12 Running ['artisan' mailbox:batch: create]2s DONEdocker_1amp_11 '/usr/local/bin/php' 'artisan'mailbox:batch: create › '/proc/1/fd/12>&1docker_lamp_12026-05-26 06:37:14 Running ['artisan' activity:sync 'ringcentral' 'a'telus' 'talkdesk'--from='2026-05-26 06:21:00' --to='2026-05-26 06:37:00']-05-26 06:37:16 Jiminny\Jobs\Mailbox\CreateBatchesdocker_lamp_12026-05-26 06:37:17 Jiminny\Jobs\Mailbox\CreateBatchesmsDONEdocker_lamp_12s DONEdocker_1amp_1'telus''talkdesk''/usr/local/bin/php' 'artisan'activity:sync 'ringcentral' 'avaya'--from='2026-05-26 06:21:00' --to='2026-05-26 06:37:00' > */proc/1/fd/1'2>&1docker_lamp_1docker_lamp_1run_artisan_schedule: Done waiting for schedule:run&3screenpipe"0 ₴4PROD (ssh)See [URL_WITH_CREDENTIALS] T3 EU (ssh)Enable ESM Apps to receive additional future security updates.See [URL_WITH_CREDENTIALS] 0T4STAGE (-zsh)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$QA (-zsh)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 parentsSTAGEy View in Docker Desktopo View Configw Enable Watcht6FE (-zsh)Last login: Wed May2009:14:49 on ttys004Poetry could not find a pyproject.toml file in /Users/lukas or its parentsFRONTENDPoetry could not finda pyproject.toml file in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ IX Y7 EXT (-zsh)Last login: Wed May 20 09:14:49 on ttys004Poetry could not find a pyproject.toml file in /Users/lukas or its parentsEXTENSIONPoetrycould not find a pyproject.toml file in /Users/lukas or its parentskas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~...
|
NULL
|
-9090364250854834796
|
NULL
|
visual_change
|
ocr
|
NULL
|
iTerm2ShellEditViewSessionScriptsProfilesWindowHel iTerm2ShellEditViewSessionScriptsProfilesWindowHelpEU (ssh)APP (-zsh)100% C8• Tue 26 May 9:37:53-zsh181DOCKERDOCKER (docker-compose)881DEV (-zsh)₴2msDONEdocker_lamp_1RUNNINGdocker_1amp_1msDONEdocker_lamp_1docker_lamp_11sDONEdocker_lamp_1fd/1'2>&1docker_lamp_12S DONEdocker_1amp_11/fd/1'2>&1docker_lamp_1s]1s DONEdocker_lamp_12026-05-2606:36:22 Jiminny\Jobs\Mailbox\SyncInbox2026-05-2606:36:22 Jiminny\Jobs \Mailbox\SyncInbox100.142026-05-26 06:37:03 Running ['artisan'meeting-bot: schedule-bot]1 '/usr/local/bin/php' 'artisan'meeting-bot: schedule-bot › */proc/1/2026-05-26 06:37:04 Running ['artisan' dialers:monitor-activities].1 '/usr/local/bin/php' 'artisan' dialers:monitor-activities › '/proc/2026-05-26 06:37:07 Running ['artisan' jiminny:monitor-social-account1 '/usr/local/bin/php' 'artisan' jiminny:monitor-social-accounts > '/docker_lamp_12026-05-26 06:37:08 Running ['artisan' mailbox:skip-lists:refresh].1S DONEdocker_lamp_11/fd/1'1 '/usr/local/bin/php' 'artisan'mailbox:skip-lists:refresh > */proc/2>&1docker_1amp_12026-05-26 06:37:10 Running ['artisan'mailbox:batch:process--max-batches=15]1S DONEdocker_lamp_111 '/usr/local/bin/php' 'artisan'mailbox:batch:process --max-batches=15 > */proc/1/fd/1* 2>&1docker_lamp_12026-05-26 06:37:12 Running ['artisan' mailbox:batch: create]2s DONEdocker_1amp_11 '/usr/local/bin/php' 'artisan'mailbox:batch: create › '/proc/1/fd/12>&1docker_lamp_12026-05-26 06:37:14 Running ['artisan' activity:sync 'ringcentral' 'a'telus' 'talkdesk'--from='2026-05-26 06:21:00' --to='2026-05-26 06:37:00']-05-26 06:37:16 Jiminny\Jobs\Mailbox\CreateBatchesdocker_lamp_12026-05-26 06:37:17 Jiminny\Jobs\Mailbox\CreateBatchesmsDONEdocker_lamp_12s DONEdocker_1amp_1'telus''talkdesk''/usr/local/bin/php' 'artisan'activity:sync 'ringcentral' 'avaya'--from='2026-05-26 06:21:00' --to='2026-05-26 06:37:00' > */proc/1/fd/1'2>&1docker_lamp_1docker_lamp_1run_artisan_schedule: Done waiting for schedule:run&3screenpipe"0 ₴4PROD (ssh)See [URL_WITH_CREDENTIALS] T3 EU (ssh)Enable ESM Apps to receive additional future security updates.See [URL_WITH_CREDENTIALS] 0T4STAGE (-zsh)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$QA (-zsh)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 parentsSTAGEy View in Docker Desktopo View Configw Enable Watcht6FE (-zsh)Last login: Wed May2009:14:49 on ttys004Poetry could not find a pyproject.toml file in /Users/lukas or its parentsFRONTENDPoetry could not finda pyproject.toml file in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ IX Y7 EXT (-zsh)Last login: Wed May 20 09:14:49 on ttys004Poetry could not find a pyproject.toml file in /Users/lukas or its parentsEXTENSIONPoetrycould not find a pyproject.toml file in /Users/lukas or its parentskas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~...
|
71284
|
NULL
|
NULL
|
NULL
|
|
1455
|
57
|
15
|
2026-05-07T08:44:29.207033+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778143469207_m1.jpg...
|
PhpStorm
|
faVsco.js – DeleteCrmEntityTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Code changed:
Hide
Sync Changes
Hide This Notification
37
1
35
64
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993
SELECT * FROM users WHERE id = 25061;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 994;
SELECT * FROM crm_profiles WHERE user_id = 25061;
select * from crm_configurations where id = 834;
SELECT * FROM teams WHERE id = 882;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 882 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 933 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;
SELECT * FROM contacts where crm_configuration_id = 834;
SELECT * FROM opportunities WHERE team_id = 933
# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');
AND id IN (8482561,18352941,19042734,19232139,19445140,19472541);
SELECT * FROM opportunity_contacts
WHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 485; #
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 933 and sa.provider = 'hubspot';
select crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id
where crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')
# and l.converted_at IS NOT NULL
;
# [PASSWORD_DOTS]
SELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')
and opportunity_id IS NULL
order by id desc;
SELECT * FROM teams WHERE id = 604; # 598
SELECT * FROM activities WHERE id = 74410828; # [EMAIL]
SELECT * FROM accounts WHERE id = 20068382;
SELECT * FROM accounts WHERE id = 35186038;
SELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 559 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;
select * from sidekick_settings where team_id = 781;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100
SELECT * FROM crm_layouts WHERE crm_configuration_id = 711;
SELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL
and is_internal = 0 and status = 'completed'
order by id desc;
SELECT * FROM crm_layout_entities
WHERE crm_layout_id IN (2352, 2353);
;
SELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 556 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;
SELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;
select * from contacts
where crm_configuration_id = 530
and crm_provider_id = 872252;
select * from activities where crm_configuration_id = 530
and user_id = 14343 and type like '%softphone%'
and created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);
SELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t
JOIN crm_configurations c ON t.id = c.team_id
WHERE t.status = 'active';
SELECT * FROM teams where id = 1091;
SELECT * FROM crm_configurations where team_id = 1091;
SELECT * FROM activity_providers where team_id = 1091;
SELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT * FROM teams WHERE name LIKE '%Leadventure%';
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1091 and sa.provider = 'salesforce';
SELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812
SELECT * FROM teams where id = 862;
SELECT * FROM crm_configurations where team_id = 862;
SELECT * FROM activity_providers where team_id = 862;
SELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT t.id, crm.id, crm.provider, ap.* FROM teams t
join crm_configurations crm on t.id = crm.team_id
join activity_providers ap on t.id = ap.team_id
where t.status = 'active' and ap.is_enabled = 1
and crm.provider = 'hubspot'
and ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',
'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');
SELECT * FROM teams where id = 1068;
SELECT * FROM crm_configurations where team_id = 1068;
SELECT * FROM activity_providers where team_id = 1068;
SELECT * FROM activities a
where crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')
and a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'
)
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by a.id desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1068 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 933 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262
SELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 882 and sa.provider = 'hubspot';
select * from crm_layouts where crm_configuration_id = 834;
select * from crm_layout_entities where crm_layout_id = 2780;
select * from crm_fields where id IN (321153,321192,321193,321194);
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1057 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8
SELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20
SELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10
SELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #
SELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;
select * from users where team_id = 51; # 7783
SELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130
select * from activity_searches where user_id = 7783;
select * from activity_search_filters where activity_search_id IN (32291, 32292);
SELECT asf.activity_search_id, asf.id, asf.value
FROM activity_search_filters asf
WHERE asf.filter = 'group_id'
AND asf.value IN (
SELECT CONCAT(
HEX(SUBSTR(uuid, 5, 4)), '-',
HEX(SUBSTR(uuid, 3, 2)), '-',
HEX(SUBSTR(uuid, 1, 2)), '-',
HEX(SUBSTR(uuid, 9, 2)), '-',
HEX(SUBSTR(uuid, 11))
)
FROM groups
WHERE deleted_at IS NOT NULL
);
SELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where provider = 'hubspot';
SELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133
SELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null
# [PASSWORD_DOTS]
select * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';
select
cp.*
# DISTINCT t.id
# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields
FROM crm_profiles cp
JOIN crm_configurations crm on crm.id = cp.crm_configuration_id
JOIN users u on u.id = cp.user_id
JOIN teams t ON t.id = crm.team_id
WHERE crm.provider = 'salesforce' and t.status = 'active'
and cp.archived_at IS NULL and u.deleted_at IS NULL
and t.id NOT IN (1093)
and t.id = 2
and cp.contact_fields IS NULL;
# and c.crm_provider_id = '003Uu00000ojD4NIAU';
SELECT * FROM users WHERE id = 26484;
SELECT * FROM crm_profiles WHERE user_id = 26484;
SELECT * FROM social_accounts WHERE sociable_id = 26484;
SELECT * FROM crm_configurations where provider = 'salesforce';
select * from users where id IN (10022, 10403);
select * from users where team_id IN (526);
select * from teams where id IN (526, 532);
select * from crm_configurations where id IN (500, 516);
select * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);
select * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 526 and sa.provider = 'salesforce';
select * from team_settings where team_id IN (526, 532);
select * from users where id IN (22824);
select * from crm_profiles where crm_configuration_id IN (1026);
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1093 and sa.provider = 'salesforce';
select * from teams where id = 1099;
select * from users where id = 29643
select * from activity_processing_states;
SELECT * FROM teams where name LIKE '%Fare%'; # 233
SELECT * FROM opportunities where crm_configuration_id = 215
# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'
;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1088 and sa.provider = 'hubspot';
SELECT * FROM teams order by updated_at DESC
SELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account
select * from crm_configurations where provider = 'pipedrive';
select * from teams where id = 957;
select * from crm_configurations where id = 957;
SELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743
SELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;
select * from users where team_id = 1; # 26726 - Gabriela Dureva
SELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific
select * from activities where user_id = 26726 order by id desc;
select * from contacts where crm_configuration_id = 1
and email IN ('[EMAIL]', '[EMAIL]'); # 2094416, 2093620
SELECT * FROM contacts WHERE id = 6284931;
SELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id
WHERE 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;
select * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);
select * from crm_configurations where id = 1;
43801692-1aeb-32ce-acba-5b80a479701a
44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b
405975c0-b3d0-7aaa-821f-09d59cae6dd1
4caf848d-4bed-2299-b248-7788d41f9fca
49bedc3f-f196-eef3-89c3-dea6a3b4aa63
43420989-a09d-b8f8-9806-c8bbf7a02aac
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1 and sa.provider = 'salesforce';
SELECT * FROM activities WHERE id = 75461988;
SELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;
select * from contacts where id = 17900517;
select * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id
where crm.provider != 'salesforce';
select * from users where id = 21047;
SELECT * FROM crm_configurations WHERE id = 892;
SELECT * FROM teams WHERE id = 942;
select * from opportunities where team_id = 942 order by updated_at desc;
select * from contacts where team_id = 942 order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 942 and sa.provider = 'hubspot';
SELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430
SELECT * FROM crm_configurations WHERE id = 1;
SELECT * FROM teams WHERE crm_id = 1;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1 and sa.provider = 'salesforce';
select id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1
SELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430
select * from teams where id = 852;
select * from groups where id = 2286;
select * from sidekick_settings where team_id = 852;
select * from default_activity_types where team_id = 852;
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1 AND u.deleted_at IS NULL
AND u.crm_required = 1
AND u.team_id = 1
ORDER BY u.team_id;
SELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (
18481
);
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1
AND u.deleted_at IS NULL
AND u.crm_required = 1
# AND u.team_id = 1
AND p.id IS NULL -- Move this condition to WHERE clause
ORDER BY u.team_id;
SELECT * FROM opportunities WHERE id = 20002609;
select * from teams where id = 1122; # Velatir, 29953 - [EMAIL]
select * from crm_configurations where id = 1060;
select * from crm_layouts where crm_configuration_id = 1060;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1122 and sa.provider = 'hubspot';
select * from opportunities where team_id = 1122 order by updated_at desc;
select * from crm_field_data where object_type = 'contact';
SELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 248 and sa.provider = 'salesforce';
SELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS
SELECT * FROM users where id = 24115;
SELECT * FROM accounts where id = 4002896;
SELECT * FROM teams WHERE name LIKE '%adswerve%';
SELECT * 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");
SELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203
SELECT u.id, u.email, ac.name, a.* FROM activities a
JOIN users u ON a.user_id = u.id
JOIN accounts ac ON a.account_id = ac.id
WHERE
uuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or
uuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or
uuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;
select * from users where id = 5825;
SELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;
select * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;
19594, 862
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 862 and sa.provider = 'salesforce';
select * from automated_reports where id = 36;
select ar.frequency, r.*, ar.* from automated_report_results r
join automated_reports ar on r.report_id = ar.id
where ar.frequency != 'one_off';
select s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;
select * from nudges n where n.activity_search_id
select * from teams where created_at > '2026-03-09';
SELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;
select * from users where team_id = 1 and name like '%Lukas%'; # 7160
SELECT * FROM teams WHERE id = 575;
select * from opportunities where team_id = 575;
SELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,
select * from opportunities where team_id = 1126;
SELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,
select * from opportunities where team_id = 1125;
select * from contacts c
where c.team_id = 882;
SELECT * FROM activities WHERE id = 76822967;
SELECT * FROM crm_profiles WHERE user_id = 15440;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 555;
SELECT * FROM crm_configurations WHERE id = 555;
SELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 581 and sa.provider = 'salesforce';
SELECT * FROM automated_report_results order by id desc;
select * from features;
select * from team_features where feature_id = 40;
select * from teams where id = 556;
select * from automated_reports;
where id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , ["pdf","podcast"]
SELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;
select * from automated_report_results order by id desc;
SELECT * FROM automated_report_results WHERE id = 1919;
select * from automated_report_results WHERE report_id = 54;
select * from opportunities where id = 7594349;
SELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067 - [EMAIL]
select * from playbooks where team_id = 711; # event 226147
SELECT * FROM playbook_categories WHERE playbook_id = 5515;
SELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event';
SELECT * FROM crm_fields WHERE id = 226147;
SELECT * FROM crm_field_values WHERE crm_field_id = 226147;
SELECT * FROM crm_configurations WHERE id = 692;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 711 and sa.provider = 'salesforce';
SELECT * FROM crm_profiles cp JOIN users u on u.id = cp.user_id WHERE u.team_id = 711;
select * from leads;
select * from calendars;
SELECT
t.id AS team_id,
t.name,
LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1)) AS calendar_domain
FROM teams t
JOIN users u ON u.team_id = t.id
JOIN calendars c ON c.user_id = u.id AND c.status = 'active' AND c.calendar_provider_id LIKE '%@%'
LEFT JOIN team_domains td
ON td.team_id = t.id
AND td.deleted_at IS NULL
AND td.domain = LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1))
GROUP BY t.id, t.name, calendar_domain
ORDER BY t.name, calendar_domain;
select * from users u join calendars c on c.user_id = u.id
where u.team_id = 882;
select * from activities where id = 74049485; # team 563 crm 537
select * from activities where id = 73272382; # team 563 crm 537
select * from activities where id = 64400389; # team 563 crm 537
select * from activities where id = 58081273; # team 563 crm 537
select * from activities where id = 54520297; # team 563 crm 537
select * 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') = uuid
or uuid_to_bin('47842446-af51-4bcb-854f-cc6560290101') = uuid;
SELECT * FROM crm_configurations WHERE provider = 'hubspot';
select * from rate_limits;
Sync Changes
Hide This Notification
Code changed:
Hide
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Acl, folder
ActionItems, folder
Activity, folder
ActivityAnalytics, folder
ActivitySearch, folder
AiActivityType, folder
AiAutomation, folder
AiCallScoring, folder
AskAnything, folder
Dtos, folder
Events, folder
AskAnythingPromptService.php, class
HistoryService.php, class
AskJiminnyAi, folder
AWS, folder
BillingManagement, folder
Cache, folder
CoachingFeedback, folder
Country, folder
CustomerApi, folder
Database, folder
Datadog, folder
DateTime, folder
DealInsights, folder
DealRisks, folder
ElasticSearch, folder
Eloquent, folder
Encoding, folder
Encryption, folder
ES, folder
Faker, folder
FeatureFlags, folder
FFMpeg, folder
FileSystem, folder
Gecko, folder
Gong, folder
GuzzleHttp, folder
KeyPoints, folder
Kiosk, folder
LanguageDetection, folder
LiveFeed, folder
Locks, folder
Math, folder
MediaPipeline, folder
MeetingBot, folder
MobileSettings, folder
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage, folder
Playlist, folder
Prophet, folder
ProphetAi, folder
ProsperWorks, folder
Queue
Job
RateLimitAware.php, abstract class
RateLimitAwareWrapper.php, class
BotsQueueConstants.php, class
Constants.php, class
ProcessingQueueConstants.php, class
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary
Twilio
Uploader
UrlGenerator
Utility
Exceptions
Service
BaseRateLimiter.php, class
EfficientJsonParser.php, class
ProviderRateLimiter.php, class
RateLimiterInstance.php, class
Uuid
Waveform
Webhooks
Workflow
Configuration
Console
Commands
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php, class
BullhornSessionCommand.php, class
CheckActivityLoggableCommand.php, final class
CleanDuplicateFieldDataCommand.php, class
FullSyncOpportunityCommand.php, class
LogActivitiesCommand.php, final class
ManageSyncStrategyCommand.php, class
MatchCrmObjectsCommand.php, class
MatchOpportunityActivitiesCommand.php, class
MigrateProvider.php, class
ProcessHubspotObjectsSyncBatches.php, class
PurgeDeletedOpportunitiesCommand.php, class
ResetGovernorLimits.php, class
SendNotLogged.php, class
SetupActivityTypeForFollowUp.php, final class
SetupCloseCrm.php, class
SetupCopperCrm.php, class
SetupCrmCommand.php, abstract class
SetupLayouts.php, class
SyncAccount.php, class
SyncContact.php, class
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class...
|
[{"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":"master, menu","depth":5,"on_screen":true,"help_text":"Git Branch: master","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":"Execute","depth":4,"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,"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,"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,"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,"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,"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,"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,"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"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":"37","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"35","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"64","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":"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;","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;","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"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":"2","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\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\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},{"role":"AXStaticText","text":"app ~/jiminny/app","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".circleci","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".cursor","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".github","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".sonarlint","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".vscode","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".windsurf","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"app, sources root","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Actions","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Component","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Acl, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActionItems, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Activity, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActivityAnalytics, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActivitySearch, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiActivityType, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiAutomation, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiCallScoring, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskAnything, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dtos, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Events, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskAnythingPromptService.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HistoryService.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskJiminnyAi, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AWS, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BillingManagement, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Cache, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CoachingFeedback, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Country, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CustomerApi, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Database, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Datadog, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DateTime, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealRisks, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ElasticSearch, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Eloquent, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Encoding, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Encryption, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ES, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Faker, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MeetingBot, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MobileSettings, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Model, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Notification, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Nudge, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ParagraphBreaker, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ParticipantSpeech, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PartitionedCookie, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackPage, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlist, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Prophet, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProsperWorks, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Queue","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Job","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimitAware.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimitAwareWrapper.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BotsQueueConstants.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Constants.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProcessingQueueConstants.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Router","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Saml2","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SCIM","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Seeder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sentry","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Serializer","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Settings","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sidekick","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TeamInsights","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TimeMemoryMapper","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Transcription","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TranscriptionSummary","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Twilio","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Uploader","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"UrlGenerator","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Utility","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Exceptions","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Service","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BaseRateLimiter.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EfficientJsonParser.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProviderRateLimiter.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimiterInstance.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Uuid","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Waveform","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Webhooks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Workflow","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Configuration","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Console","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Commands","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Activities","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Analytics","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Calendars","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Crm","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Hubspot","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IntegrationApp","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Traits","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AddLayoutEntities.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutologDelayedCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornCommandAbstract.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornPingCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSearchCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSessionCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CheckActivityLoggableCommand.php, final class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CleanDuplicateFieldDataCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FullSyncOpportunityCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LogActivitiesCommand.php, final class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ManageSyncStrategyCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchCrmObjectsCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchOpportunityActivitiesCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MigrateProvider.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProcessHubspotObjectsSyncBatches.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeDeletedOpportunitiesCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ResetGovernorLimits.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SendNotLogged.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupActivityTypeForFollowUp.php, final class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCloseCrm.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCopperCrm.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCrmCommand.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupLayouts.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncAccount.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncContact.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncFieldMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotActiveDeals.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncLead.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunitiesMissingFieldDataCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunity.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncProfileMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncTeamMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"}]...
|
-9089949279098730604
|
1065696262816675397
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Code changed:
Hide
Sync Changes
Hide This Notification
37
1
35
64
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993
SELECT * FROM users WHERE id = 25061;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 994;
SELECT * FROM crm_profiles WHERE user_id = 25061;
select * from crm_configurations where id = 834;
SELECT * FROM teams WHERE id = 882;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 882 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 933 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;
SELECT * FROM contacts where crm_configuration_id = 834;
SELECT * FROM opportunities WHERE team_id = 933
# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');
AND id IN (8482561,18352941,19042734,19232139,19445140,19472541);
SELECT * FROM opportunity_contacts
WHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 485; #
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 933 and sa.provider = 'hubspot';
select crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id
where crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')
# and l.converted_at IS NOT NULL
;
# [PASSWORD_DOTS]
SELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')
and opportunity_id IS NULL
order by id desc;
SELECT * FROM teams WHERE id = 604; # 598
SELECT * FROM activities WHERE id = 74410828; # [EMAIL]
SELECT * FROM accounts WHERE id = 20068382;
SELECT * FROM accounts WHERE id = 35186038;
SELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 559 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;
select * from sidekick_settings where team_id = 781;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100
SELECT * FROM crm_layouts WHERE crm_configuration_id = 711;
SELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL
and is_internal = 0 and status = 'completed'
order by id desc;
SELECT * FROM crm_layout_entities
WHERE crm_layout_id IN (2352, 2353);
;
SELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 556 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;
SELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;
select * from contacts
where crm_configuration_id = 530
and crm_provider_id = 872252;
select * from activities where crm_configuration_id = 530
and user_id = 14343 and type like '%softphone%'
and created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);
SELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t
JOIN crm_configurations c ON t.id = c.team_id
WHERE t.status = 'active';
SELECT * FROM teams where id = 1091;
SELECT * FROM crm_configurations where team_id = 1091;
SELECT * FROM activity_providers where team_id = 1091;
SELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT * FROM teams WHERE name LIKE '%Leadventure%';
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1091 and sa.provider = 'salesforce';
SELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812
SELECT * FROM teams where id = 862;
SELECT * FROM crm_configurations where team_id = 862;
SELECT * FROM activity_providers where team_id = 862;
SELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT t.id, crm.id, crm.provider, ap.* FROM teams t
join crm_configurations crm on t.id = crm.team_id
join activity_providers ap on t.id = ap.team_id
where t.status = 'active' and ap.is_enabled = 1
and crm.provider = 'hubspot'
and ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',
'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');
SELECT * FROM teams where id = 1068;
SELECT * FROM crm_configurations where team_id = 1068;
SELECT * FROM activity_providers where team_id = 1068;
SELECT * FROM activities a
where crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')
and a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'
)
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by a.id desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1068 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 933 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262
SELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 882 and sa.provider = 'hubspot';
select * from crm_layouts where crm_configuration_id = 834;
select * from crm_layout_entities where crm_layout_id = 2780;
select * from crm_fields where id IN (321153,321192,321193,321194);
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1057 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8
SELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20
SELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10
SELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #
SELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;
select * from users where team_id = 51; # 7783
SELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130
select * from activity_searches where user_id = 7783;
select * from activity_search_filters where activity_search_id IN (32291, 32292);
SELECT asf.activity_search_id, asf.id, asf.value
FROM activity_search_filters asf
WHERE asf.filter = 'group_id'
AND asf.value IN (
SELECT CONCAT(
HEX(SUBSTR(uuid, 5, 4)), '-',
HEX(SUBSTR(uuid, 3, 2)), '-',
HEX(SUBSTR(uuid, 1, 2)), '-',
HEX(SUBSTR(uuid, 9, 2)), '-',
HEX(SUBSTR(uuid, 11))
)
FROM groups
WHERE deleted_at IS NOT NULL
);
SELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where provider = 'hubspot';
SELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133
SELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null
# [PASSWORD_DOTS]
select * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';
select
cp.*
# DISTINCT t.id
# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields
FROM crm_profiles cp
JOIN crm_configurations crm on crm.id = cp.crm_configuration_id
JOIN users u on u.id = cp.user_id
JOIN teams t ON t.id = crm.team_id
WHERE crm.provider = 'salesforce' and t.status = 'active'
and cp.archived_at IS NULL and u.deleted_at IS NULL
and t.id NOT IN (1093)
and t.id = 2
and cp.contact_fields IS NULL;
# and c.crm_provider_id = '003Uu00000ojD4NIAU';
SELECT * FROM users WHERE id = 26484;
SELECT * FROM crm_profiles WHERE user_id = 26484;
SELECT * FROM social_accounts WHERE sociable_id = 26484;
SELECT * FROM crm_configurations where provider = 'salesforce';
select * from users where id IN (10022, 10403);
select * from users where team_id IN (526);
select * from teams where id IN (526, 532);
select * from crm_configurations where id IN (500, 516);
select * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);
select * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 526 and sa.provider = 'salesforce';
select * from team_settings where team_id IN (526, 532);
select * from users where id IN (22824);
select * from crm_profiles where crm_configuration_id IN (1026);
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1093 and sa.provider = 'salesforce';
select * from teams where id = 1099;
select * from users where id = 29643
select * from activity_processing_states;
SELECT * FROM teams where name LIKE '%Fare%'; # 233
SELECT * FROM opportunities where crm_configuration_id = 215
# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'
;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1088 and sa.provider = 'hubspot';
SELECT * FROM teams order by updated_at DESC
SELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account
select * from crm_configurations where provider = 'pipedrive';
select * from teams where id = 957;
select * from crm_configurations where id = 957;
SELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743
SELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;
select * from users where team_id = 1; # 26726 - Gabriela Dureva
SELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific
select * from activities where user_id = 26726 order by id desc;
select * from contacts where crm_configuration_id = 1
and email IN ('[EMAIL]', '[EMAIL]'); # 2094416, 2093620
SELECT * FROM contacts WHERE id = 6284931;
SELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id
WHERE 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;
select * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);
select * from crm_configurations where id = 1;
43801692-1aeb-32ce-acba-5b80a479701a
44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b
405975c0-b3d0-7aaa-821f-09d59cae6dd1
4caf848d-4bed-2299-b248-7788d41f9fca
49bedc3f-f196-eef3-89c3-dea6a3b4aa63
43420989-a09d-b8f8-9806-c8bbf7a02aac
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1 and sa.provider = 'salesforce';
SELECT * FROM activities WHERE id = 75461988;
SELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;
select * from contacts where id = 17900517;
select * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id
where crm.provider != 'salesforce';
select * from users where id = 21047;
SELECT * FROM crm_configurations WHERE id = 892;
SELECT * FROM teams WHERE id = 942;
select * from opportunities where team_id = 942 order by updated_at desc;
select * from contacts where team_id = 942 order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 942 and sa.provider = 'hubspot';
SELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430
SELECT * FROM crm_configurations WHERE id = 1;
SELECT * FROM teams WHERE crm_id = 1;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1 and sa.provider = 'salesforce';
select id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1
SELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430
select * from teams where id = 852;
select * from groups where id = 2286;
select * from sidekick_settings where team_id = 852;
select * from default_activity_types where team_id = 852;
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1 AND u.deleted_at IS NULL
AND u.crm_required = 1
AND u.team_id = 1
ORDER BY u.team_id;
SELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (
18481
);
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1
AND u.deleted_at IS NULL
AND u.crm_required = 1
# AND u.team_id = 1
AND p.id IS NULL -- Move this condition to WHERE clause
ORDER BY u.team_id;
SELECT * FROM opportunities WHERE id = 20002609;
select * from teams where id = 1122; # Velatir, 29953 - [EMAIL]
select * from crm_configurations where id = 1060;
select * from crm_layouts where crm_configuration_id = 1060;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1122 and sa.provider = 'hubspot';
select * from opportunities where team_id = 1122 order by updated_at desc;
select * from crm_field_data where object_type = 'contact';
SELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 248 and sa.provider = 'salesforce';
SELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS
SELECT * FROM users where id = 24115;
SELECT * FROM accounts where id = 4002896;
SELECT * FROM teams WHERE name LIKE '%adswerve%';
SELECT * 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");
SELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203
SELECT u.id, u.email, ac.name, a.* FROM activities a
JOIN users u ON a.user_id = u.id
JOIN accounts ac ON a.account_id = ac.id
WHERE
uuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or
uuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or
uuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;
select * from users where id = 5825;
SELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;
select * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;
19594, 862
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 862 and sa.provider = 'salesforce';
select * from automated_reports where id = 36;
select ar.frequency, r.*, ar.* from automated_report_results r
join automated_reports ar on r.report_id = ar.id
where ar.frequency != 'one_off';
select s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;
select * from nudges n where n.activity_search_id
select * from teams where created_at > '2026-03-09';
SELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;
select * from users where team_id = 1 and name like '%Lukas%'; # 7160
SELECT * FROM teams WHERE id = 575;
select * from opportunities where team_id = 575;
SELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,
select * from opportunities where team_id = 1126;
SELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,
select * from opportunities where team_id = 1125;
select * from contacts c
where c.team_id = 882;
SELECT * FROM activities WHERE id = 76822967;
SELECT * FROM crm_profiles WHERE user_id = 15440;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 555;
SELECT * FROM crm_configurations WHERE id = 555;
SELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 581 and sa.provider = 'salesforce';
SELECT * FROM automated_report_results order by id desc;
select * from features;
select * from team_features where feature_id = 40;
select * from teams where id = 556;
select * from automated_reports;
where id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , ["pdf","podcast"]
SELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;
select * from automated_report_results order by id desc;
SELECT * FROM automated_report_results WHERE id = 1919;
select * from automated_report_results WHERE report_id = 54;
select * from opportunities where id = 7594349;
SELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067 - [EMAIL]
select * from playbooks where team_id = 711; # event 226147
SELECT * FROM playbook_categories WHERE playbook_id = 5515;
SELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event';
SELECT * FROM crm_fields WHERE id = 226147;
SELECT * FROM crm_field_values WHERE crm_field_id = 226147;
SELECT * FROM crm_configurations WHERE id = 692;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 711 and sa.provider = 'salesforce';
SELECT * FROM crm_profiles cp JOIN users u on u.id = cp.user_id WHERE u.team_id = 711;
select * from leads;
select * from calendars;
SELECT
t.id AS team_id,
t.name,
LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1)) AS calendar_domain
FROM teams t
JOIN users u ON u.team_id = t.id
JOIN calendars c ON c.user_id = u.id AND c.status = 'active' AND c.calendar_provider_id LIKE '%@%'
LEFT JOIN team_domains td
ON td.team_id = t.id
AND td.deleted_at IS NULL
AND td.domain = LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1))
GROUP BY t.id, t.name, calendar_domain
ORDER BY t.name, calendar_domain;
select * from users u join calendars c on c.user_id = u.id
where u.team_id = 882;
select * from activities where id = 74049485; # team 563 crm 537
select * from activities where id = 73272382; # team 563 crm 537
select * from activities where id = 64400389; # team 563 crm 537
select * from activities where id = 58081273; # team 563 crm 537
select * from activities where id = 54520297; # team 563 crm 537
select * 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') = uuid
or uuid_to_bin('47842446-af51-4bcb-854f-cc6560290101') = uuid;
SELECT * FROM crm_configurations WHERE provider = 'hubspot';
select * from rate_limits;
Sync Changes
Hide This Notification
Code changed:
Hide
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Acl, folder
ActionItems, folder
Activity, folder
ActivityAnalytics, folder
ActivitySearch, folder
AiActivityType, folder
AiAutomation, folder
AiCallScoring, folder
AskAnything, folder
Dtos, folder
Events, folder
AskAnythingPromptService.php, class
HistoryService.php, class
AskJiminnyAi, folder
AWS, folder
BillingManagement, folder
Cache, folder
CoachingFeedback, folder
Country, folder
CustomerApi, folder
Database, folder
Datadog, folder
DateTime, folder
DealInsights, folder
DealRisks, folder
ElasticSearch, folder
Eloquent, folder
Encoding, folder
Encryption, folder
ES, folder
Faker, folder
FeatureFlags, folder
FFMpeg, folder
FileSystem, folder
Gecko, folder
Gong, folder
GuzzleHttp, folder
KeyPoints, folder
Kiosk, folder
LanguageDetection, folder
LiveFeed, folder
Locks, folder
Math, folder
MediaPipeline, folder
MeetingBot, folder
MobileSettings, folder
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage, folder
Playlist, folder
Prophet, folder
ProphetAi, folder
ProsperWorks, folder
Queue
Job
RateLimitAware.php, abstract class
RateLimitAwareWrapper.php, class
BotsQueueConstants.php, class
Constants.php, class
ProcessingQueueConstants.php, class
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary
Twilio
Uploader
UrlGenerator
Utility
Exceptions
Service
BaseRateLimiter.php, class
EfficientJsonParser.php, class
ProviderRateLimiter.php, class
RateLimiterInstance.php, class
Uuid
Waveform
Webhooks
Workflow
Configuration
Console
Commands
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php, class
BullhornSessionCommand.php, class
CheckActivityLoggableCommand.php, final class
CleanDuplicateFieldDataCommand.php, class
FullSyncOpportunityCommand.php, class
LogActivitiesCommand.php, final class
ManageSyncStrategyCommand.php, class
MatchCrmObjectsCommand.php, class
MatchOpportunityActivitiesCommand.php, class
MigrateProvider.php, class
ProcessHubspotObjectsSyncBatches.php, class
PurgeDeletedOpportunitiesCommand.php, class
ResetGovernorLimits.php, class
SendNotLogged.php, class
SetupActivityTypeForFollowUp.php, final class
SetupCloseCrm.php, class
SetupCopperCrm.php, class
SetupCrmCommand.php, abstract class
SetupLayouts.php, class
SyncAccount.php, class
SyncContact.php, class
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class...
|
1451
|
NULL
|
NULL
|
NULL
|
|
18020
|
782
|
35
|
2026-05-11T10:45:38.741152+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778496338741_m1.jpg...
|
Notion Calendar
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
SlackFileEditViewGoHistoryWindowHelpDOCKER181DEV ( SlackFileEditViewGoHistoryWindowHelpDOCKER181DEV (docker)$2APP (-zsh)883viewsjiminny-worker-processing-2:jiminny-worker-processing-2_00: stoppedjiminny-worker-processing-3:jiminny-worker-processing-3_00: stoppedjiminny-worker-processing-4: jiminny-worker-processing-4_00: stoppedJiminny-worker-processing-5:j1minny-worker-processing-5_00:jiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00: stoppedworker-analytics:worker-analytics_00: stoppedworker-conferences:worker-conferences_00: stoppedworker-crm-update:worker-crm-update_00: stoppedworker-download:worker-download_00: stoppedworker-emails:worker-emails_00: stoppedworker-nudges:worker-nudges_00: stoppedworker:worker_00: stoppedworker-audio:worker-audio_00: stoppedworker-calendar:worker-calendar_00: stoppedworker-crm-sync:worker-crm-sync_00: stoppedartisan-schedule:artisan-schedule_00: stoppedworker-es-update:worker-es-update_00: stoppedjiminny-worker-processing-1:jiminny-worker-processing-1_00: stoppedartisan-schedule:artisan-schedule_00: startedjiminny-worker-processing-1:jiminny-worker-processing-1_00: startedjiminny-worker-processing-2:jiminny-worker-processing-2_00: startedjiminny-worker-processing-3:jiminny-worker-processing-3_00: startedjiminny-worker-processing-4:jiminny-worker-processing-4_00: startedjiminny-worker-processing-5:jiminny-worker-processing-5_00:jiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00: startedworker:worker_00: startedworker-analytics:worker-analytics_00: startedworker-audio:worker-audio_00: startedworker-calendar:worker-calendar_00: startedworker-conferences:worker-conferences_00: startedworker-crm-sync:worker-crm-sync_00: startedworker-crm-update:worker-crm-update_00: startedworker-download:worker-download_00: startedworker-emails:worker-emails_00: startedworker-es-update:worker-es-update_00: startedworker-nudges:worker-nudges_00: startedroot@docker_lamp_1:/home/jiminny#php artisan jiminny:debugDispatching 100 MatchActivityCrmData jobs (portal=2)Done. Watch logs and run jiminny:debug observeRateLimit to inspect cache state.root@docker_lamp_1:/home/jiminny# php artisan jiminny:debugDispatching 100 MatchActivityCrmData jobs (portal=2)Done. Watch logs and run jiminny:debug observeRateLimit to inspect cache state.root@docker_lamp_1:/home/jiminny# ]DEV (d)-zshHomeDMsActivityFilesLater..•Morelthl• Support Daily • in 1h 15 m100% <78• Mon 11 May 13:45:38ED→Describe what you are looking forJiminny ...# contusion-clinic# curiosity_lab# engineering# general# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...ToastHomeMessages^ Direct messages®. Aneliya Angelova®. Galya Dimitrova& Petko Kashinski&. Stefka StoyanovaVasil Vasilev OlC. Nikolay IvanovAneliya Angelova, ...Stoyan Tanev®. VesE Lukas Kovalik y... 0AboutiminnyFriday, May 8th ~Added by Toast for GitHubToday ~Toast APP10:00 AMReviewapp#12059 Jy 20820 es reindexstream model hydration2 days old • 12 files changed •@Vasil VasilevAdded by Toast for GitHubResolve Conflictsapp#11443 Test hublets latency5 months old • 20 files changed#11327 JY-19501 webhookbased opportunity syncShow moreAdded by Toast for GitHubNeeds Loveapp#12024 JY-20773 fix user pilottracking ofr automated reportgenerated12 days old • 1 file changedE:: AppsMessage ToastJira CloudToast+AaConala CalaNew...
|
NULL
|
-9089781524009160870
|
NULL
|
click
|
ocr
|
NULL
|
SlackFileEditViewGoHistoryWindowHelpDOCKER181DEV ( SlackFileEditViewGoHistoryWindowHelpDOCKER181DEV (docker)$2APP (-zsh)883viewsjiminny-worker-processing-2:jiminny-worker-processing-2_00: stoppedjiminny-worker-processing-3:jiminny-worker-processing-3_00: stoppedjiminny-worker-processing-4: jiminny-worker-processing-4_00: stoppedJiminny-worker-processing-5:j1minny-worker-processing-5_00:jiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00: stoppedworker-analytics:worker-analytics_00: stoppedworker-conferences:worker-conferences_00: stoppedworker-crm-update:worker-crm-update_00: stoppedworker-download:worker-download_00: stoppedworker-emails:worker-emails_00: stoppedworker-nudges:worker-nudges_00: stoppedworker:worker_00: stoppedworker-audio:worker-audio_00: stoppedworker-calendar:worker-calendar_00: stoppedworker-crm-sync:worker-crm-sync_00: stoppedartisan-schedule:artisan-schedule_00: stoppedworker-es-update:worker-es-update_00: stoppedjiminny-worker-processing-1:jiminny-worker-processing-1_00: stoppedartisan-schedule:artisan-schedule_00: startedjiminny-worker-processing-1:jiminny-worker-processing-1_00: startedjiminny-worker-processing-2:jiminny-worker-processing-2_00: startedjiminny-worker-processing-3:jiminny-worker-processing-3_00: startedjiminny-worker-processing-4:jiminny-worker-processing-4_00: startedjiminny-worker-processing-5:jiminny-worker-processing-5_00:jiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00: startedworker:worker_00: startedworker-analytics:worker-analytics_00: startedworker-audio:worker-audio_00: startedworker-calendar:worker-calendar_00: startedworker-conferences:worker-conferences_00: startedworker-crm-sync:worker-crm-sync_00: startedworker-crm-update:worker-crm-update_00: startedworker-download:worker-download_00: startedworker-emails:worker-emails_00: startedworker-es-update:worker-es-update_00: startedworker-nudges:worker-nudges_00: startedroot@docker_lamp_1:/home/jiminny#php artisan jiminny:debugDispatching 100 MatchActivityCrmData jobs (portal=2)Done. Watch logs and run jiminny:debug observeRateLimit to inspect cache state.root@docker_lamp_1:/home/jiminny# php artisan jiminny:debugDispatching 100 MatchActivityCrmData jobs (portal=2)Done. Watch logs and run jiminny:debug observeRateLimit to inspect cache state.root@docker_lamp_1:/home/jiminny# ]DEV (d)-zshHomeDMsActivityFilesLater..•Morelthl• Support Daily • in 1h 15 m100% <78• Mon 11 May 13:45:38ED→Describe what you are looking forJiminny ...# contusion-clinic# curiosity_lab# engineering# general# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...ToastHomeMessages^ Direct messages®. Aneliya Angelova®. Galya Dimitrova& Petko Kashinski&. Stefka StoyanovaVasil Vasilev OlC. Nikolay IvanovAneliya Angelova, ...Stoyan Tanev®. VesE Lukas Kovalik y... 0AboutiminnyFriday, May 8th ~Added by Toast for GitHubToday ~Toast APP10:00 AMReviewapp#12059 Jy 20820 es reindexstream model hydration2 days old • 12 files changed •@Vasil VasilevAdded by Toast for GitHubResolve Conflictsapp#11443 Test hublets latency5 months old • 20 files changed#11327 JY-19501 webhookbased opportunity syncShow moreAdded by Toast for GitHubNeeds Loveapp#12024 JY-20773 fix user pilottracking ofr automated reportgenerated12 days old • 1 file changedE:: AppsMessage ToastJira CloudToast+AaConala CalaNew...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
34802
|
1303
|
0
|
2026-05-13T11:43:04.575840+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-13/1778 /Users/lukas/.screenpipe/data/data/2026-05-13/1778672584575_m1.jpg...
|
PhpStorm
|
faVsco.js – EmailTextRelay.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
1 file committed
JY-20891 add support for secondar 1 file committed
JY-20891 add support for secondary email
text/html
text/html
text/html
Edit Commit Message…
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
TextRelayException
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
1/12
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Sync Changes
Hide This Notification
Code changed:
Hide
29
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Jobs\Mailbox;
use Illuminate\Support\Str;
use Carbon\Carbon;
use Google\Service\Gmail as GoogleGmail;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Contracts\Acl\PermissionEnum;
use Jiminny\Exceptions\TextRelayException;
use Jiminny\Jobs\Job;
use EmailReplyParser\Parser\EmailParser;
use Jiminny\Mail\Activities\SmsRelayFailed;
use Jiminny\Models\Activity;
use Jiminny\Models\TextRelay;
use Jiminny\Repositories\UserRepository;
use Jiminny\Rules\SmsMessage;
use Jiminny\Services\Mail\TextRelayService;
use Jiminny\Services\Telephony\TextMessagingService;
use Vinkla\Hashids\Facades\Hashids;
use Validator;
class EmailTextRelay extends Job implements ShouldQueue
{
use InteractsWithQueue;
use SerializesModels;
private const REASON_CODE_MAILBOX_MISMATCH = 1000;
private const REASON_CODE_SENDER_UNKNOWN = 2000;
private const REASON_CODE_SENDER_MISMATCH = 2100;
private const REASON_CODE_SENDER_FORBIDDEN = 2200;
private const REASON_CODE_RECIPIENT_ORIGIN_INVALID = 3000;
private const REASON_CODE_RECIPIENT_DESTINATION_INVALID = 3100;
private const REASON_CODE_RECIPIENT_MISSING_INVALID = 3200;
private const REASON_CODE_MESSAGE_BODY_TOO_LARGE = 4000;
private const REASON_CODE_MESSAGE_BODY_EMPTY = 4100;
private const REASON_CODE_INVALID_HASH = 5000;
private const REASON_CODE_UNKNOWN = 10000;
private $textRelay;
private $messageId;
private $to;
private $text;
/**
* @var Activity
*/
private $activityOrigin;
/**
* Create a new job instance.
*/
public function __construct(string $messageId, TextRelay $textRelay)
{
$this->messageId = $messageId;
$this->textRelay = $textRelay;
}
/**
* Execute the job.
*/
public function handle(
TextMessagingService $messagingService,
TextRelayService $relayService,
UserRepository $userRepository
): void {
if (config('jiminny.google_text_host') === null) {
return;
}
$mailService = $relayService->getService(config('jiminny.google_text_user'));
try {
// Retrieve the message from the email server.
$message = $this->getMessage($mailService, $this->messageId);
$payload = $message->getPayload();
$headers = $this->getHeaders($payload->getHeaders());
$date = Carbon::createFromFormat(Carbon::RFC2822, $headers['Date']);
$this->textRelay->update([
'email_sent_at' => $date,
'sender' => Str::limit($headers['From'], 191, ''),
'recipient' => Str::limit($headers['To'], 191, ''),
]);
$this->checkIntegrity($payload, $userRepository);
$user = $this->activityOrigin->user;
// Create the activity and send the SMS.
$messagingService->setTeam($user->team);
$message = $messagingService->send(
$user,
$this->to,
$this->text
);
$activity = $messagingService->buildActivity(
$message->sid,
Activity::TYPE_SMS_OUTBOUND,
$user,
$user->softphone_number,
$this->to,
$this->text,
$this->activityOrigin->prospect ? $this->activityOrigin->prospect->crm_provider_id : null,
strtolower($this->activityOrigin->prospect_type),
null
);
$this->textRelay->update([
'origin_activity_id' => $this->activityOrigin->id,
'activity_id' => $activity->id,
'status' => TextRelay::STATUS_PROCESSED,
]);
} catch (TextRelayException $e) {
$this->textRelay->update([
'status' => TextRelay::STATUS_FAILED,
'code' => $e->getCode(),
]);
$sender = $this->parseSender($headers['From']);
// If we can pull the sender, tell them that it failed.
if ($sender) {
$responder = (new SmsRelayFailed($this->textRelay, $e->getMessage(), $e->getCode()))
->onQueue(Constants::QUEUE_EMAILS);
\Mail::to($sender)->queue($responder);
}
} catch (\Exception $e) {
$this->textRelay->update([
'status' => TextRelay::STATUS_FAILED,
'code' => self::REASON_CODE_UNKNOWN,
]);
\Sentry::captureException($e);
} finally {
// Delete the message from the email server (it lives for 30 days).
$this->trashMessage($mailService, $this->messageId);
}
}
/**
* @param GoogleGmail\MessagePartHeader[] $rawHeaders
*/
private function getHeaders(array $rawHeaders): array
{
$headers = [];
foreach ($rawHeaders as $header) {
if (\in_array($header->name, ['To', 'From', 'X-Gm-Original-To', 'Date'])) {
$headers[$header->name] = $header->value;
}
}
return $headers;
}
/**
* @param GoogleGmail\MessagePart[] $rawBodyParts
*/
private function getBody(array $rawBodyParts): ?string
{
$body = null;
foreach ($rawBodyParts as $bodyPart) {
/* @var $bodyPart GoogleGmail\MessagePart */
if ($bodyPart->mimeType === 'text/plain') {
$body = $this->base64UrlDecode($bodyPart->getBody()->getData());
}
}
return $body;
}
private function parseSender(string $sender): ?string
{
preg_match_all(
'/\b[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}\b/i',
$sender,
$matches,
PREG_PATTERN_ORDER
);
return $matches[0][0];
}
/**
* @throws \Exception
*/
private function parseRecipient(string $recipient): string
{
preg_match(
'/\d+\.\d+\.[a-zA-Z0-9]{10}@txt\.(?:staging\.)?jiminny\.com/i',
$recipient,
$matches
);
if ($matches == false) {
// This should not happen. If it does, the mailbox allowed an invalid catch-all.
throw new TextRelayException(
'Recipient is missing or invalid.',
self::REASON_CODE_RECIPIENT_MISSING_INVALID
);
}
// Reply-To consists of digits.digits.string@mailbox
return $matches[0];
}
/**
* @throws \Exception
*/
private function parseRecipientIntoEntities(string $recipient): array
{
// Convert From/To/Activity into their usable types.
[$from, $to, $encodedActivityId] = explode('.', $recipient);
$from = '+' . $from;
$to = '+' . $to;
$activityId = Hashids::decode($encodedActivityId);
return [$from, $to, $activityId];
}
/**
* @throws \Exception
*/
private function checkIntegrity(GoogleGmail\MessagePart $payload, UserRepository $userRepository)
{
$headers = $this->getHeaders($payload->getHeaders());
$body = $this->getBody($payload->getParts());
// Parse the email body and check it can actually be sent.
$email = (new EmailParser())->parse($body);
$text = $email->getVisibleText();
if ($text === '') {
throw new TextRelayException(
'Message body is un-readable or empty.',
self::REASON_CODE_MESSAGE_BODY_EMPTY
);
}
$validator = Validator::make(['data' => $text], ['data' => new SmsMessage()]);
if ($validator->fails()) {
throw new TextRelayException(
'Message body is too large to send as an SMS.',
self::REASON_CODE_MESSAGE_BODY_TOO_LARGE
);
}
$this->text = $text;
$sender = $this->parseSender($headers['From']);
// Extract the Jiminny recipient.
$recipient = explode('@', $this->parseRecipient($headers['To']));
// Check this message is intended for us.
if ($recipient[1] !== config('jiminny.google_text_host')) {
throw new TextRelayException(
'Destination Mailbox does not match.',
self::REASON_CODE_MAILBOX_MISMATCH
);
}
[$from, $to, $activityId] = $this->parseRecipientIntoEntities($recipient[0]);
$validator = Validator::make(['data' => $from], ['data' => 'phone:INTERNATIONAL']);
if ($validator->fails()) {
throw new TextRelayException(
'Recipient origin number is not valid.',
self::REASON_CODE_RECIPIENT_ORIGIN_INVALID
);
}
$validator = Validator::make(['data' => $to], ['data' => 'phone:INTERNATIONAL']);
if ($validator->fails()) {
throw new TextRelayException(
'Recipient destination number is not valid.',
self::REASON_CODE_RECIPIENT_DESTINATION_INVALID
);
}
$user = $userRepository->findByEmail($sender);
if ($user === null) {
throw new TextRelayException(
'Sender does not exist.',
self::REASON_CODE_SENDER_UNKNOWN
);
}
if ($user->softphone_number === null || ! $user->hasPermission(PermissionEnum::SMS)) {
throw new TextRelayException(
'Messaging is not enabled for this account.',
self::REASON_CODE_SENDER_FORBIDDEN
);
}
if ($activityId === null) {
throw new TextRelayException(
'Origin activity has an invalid hash.',
self::REASON_CODE_INVALID_HASH
);
}
$activity = Activity::where('id', $activityId)->first();
if ($activity && $activity->user_id !== $user->id) {
throw new TextRelayException(
'Sender does not match the origin activity.',
self::REASON_CODE_SENDER_MISMATCH
);
}
$this->activityOrigin = $activity;
$this->to = $to;
}
/**
* Returns a base64 decoded web safe string
*
* @param string $string The string to be decoded
*
* @return string Decoded string
*/
private function base64UrlDecode(string $string): string
{
return base64_decode(str_replace(['-', '_'], ['+', '/'], $string));
}
public function getMessage($service, $messageId): GoogleGmail\Message
{
return $service->users_messages->get(config('jiminny.google_text_user'), $messageId);
}
public function trashMessage($service, $messageId)
{
return $service->users_messages->trash(config('jiminny.google_text_user'), $messageId);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
40
1
40
65
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993
SELECT * FROM users WHERE id = 25061;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 994;
SELECT * FROM crm_profiles WHERE user_id = 25061;
select * from crm_configurations where id = 834;
SELECT * FROM teams WHERE id = 882;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 882 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 933 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;
SELECT * FROM contacts where crm_configuration_id = 834;
SELECT * FROM opportunities WHERE team_id = 933
# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');
AND id IN (8482561,18352941,19042734,19232139,19445140,19472541);
SELECT * FROM opportunity_contacts
WHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 485; #
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 933 and sa.provider = 'hubspot';
select crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id
where crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')
# and l.converted_at IS NOT NULL
;
# [PASSWORD_DOTS]
SELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')
and opportunity_id IS NULL
order by id desc;
SELECT * FROM teams WHERE id = 604; # 598
SELECT * FROM activities WHERE id = 74410828; # [EMAIL]
SELECT * FROM accounts WHERE id = 20068382;
SELECT * FROM accounts WHERE id = 35186038;
SELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 559 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;
select * from sidekick_settings where team_id = 781;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100
SELECT * FROM crm_layouts WHERE crm_configuration_id = 711;
SELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL
and is_internal = 0 and status = 'completed'
order by id desc;
SELECT * FROM crm_layout_entities
WHERE crm_layout_id IN (2352, 2353);
;
SELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 556 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;
SELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;
select * from contacts
where crm_configuration_id = 530
and crm_provider_id = 872252;
select * from activities where crm_configuration_id = 530
and user_id = 14343 and type like '%softphone%'
and created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);
SELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t
JOIN crm_configurations c ON t.id = c.team_id
WHERE t.status = 'active';
SELECT * FROM teams where id = 1091;
SELECT * FROM crm_configurations where team_id = 1091;
SELECT * FROM activity_providers where team_id = 1091;
SELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT * FROM teams WHERE name LIKE '%Leadventure%';
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1091 and sa.provider = 'salesforce';
SELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812
SELECT * FROM teams where id = 862;
SELECT * FROM crm_configurations where team_id = 862;
SELECT * FROM activity_providers where team_id = 862;
SELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT t.id, crm.id, crm.provider, ap.* FROM teams t
join crm_configurations crm on t.id = crm.team_id
join activity_providers ap on t.id = ap.team_id
where t.status = 'active' and ap.is_enabled = 1
and crm.provider = 'hubspot'
and ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',
'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');
SELECT * FROM teams where id = 1068;
SELECT * FROM crm_configurations where team_id = 1068;
SELECT * FROM activity_providers where team_id = 1068;
SELECT * FROM activities a
where crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')
and a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'
)
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by a.id desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1068 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 933 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262
SELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 882 and sa.provider = 'hubspot';
select * from crm_layouts where crm_configuration_id = 834;
select * from crm_layout_entities where crm_layout_id = 2780;
select * from crm_fields where id IN (321153,321192,321193,321194);
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1057 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8
SELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20
SELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10
SELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #
SELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;
select * from users where team_id = 51; # 7783
SELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130
select * from activity_searches where user_id = 7783;
select * from activity_search_filters where activity_search_id IN (32291, 32292);
SELECT asf.activity_search_id, asf.id, asf.value
FROM activity_search_filters asf
WHERE asf.filter = 'group_id'
AND asf.value IN (
SELECT CONCAT(
HEX(SUBSTR(uuid, 5, 4)), '-',
HEX(SUBSTR(uuid, 3, 2)), '-',
HEX(SUBSTR(uuid, 1, 2)), '-',
HEX(SUBSTR(uuid, 9, 2)), '-',
HEX(SUBSTR(uuid, 11))
)
FROM groups
WHERE deleted_at IS NOT NULL
);
SELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where provider = 'hubspot';
SELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133
SELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null
# [PASSWORD_DOTS]
select * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';
select
cp.*
# DISTINCT t.id
# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields
FROM crm_profiles cp
JOIN crm_configurations crm on crm.id = cp.crm_configuration_id
JOIN users u on u.id = cp.user_id
JOIN teams t ON t.id = crm.team_id
WHERE crm.provider = 'salesforce' and t.status = 'active'
and cp.archived_at IS NULL and u.deleted_at IS NULL
and t.id NOT IN (1093)
and t.id = 2
and cp.contact_fields IS NULL;
# and c.crm_provider_id = '003Uu00000ojD4NIAU';
SELECT * FROM users WHERE id = 26484;
SELECT * FROM crm_profiles WHERE user_id = 26484;
SELECT * FROM social_accounts WHERE sociable_id = 26484;
SELECT * FROM crm_configurations where provider = 'salesforce';
select * from users where id IN (10022, 10403);
select * from users where team_id IN (526);
select * from teams where id IN (526, 532);
select * from crm_configurations where id IN (500, 516);
select * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);
select * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 526 and sa.provider = 'salesforce';
select * from team_settings where team_id IN (526, 532);
select * from users where id IN (22824);
select * from crm_profiles where crm_configuration_id IN (1026);
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1093 and sa.provider = 'salesforce';
select * from teams where id = 1099;
select * from users where id = 29643
select * from activity_processing_states;
SELECT * FROM teams where name LIKE '%Fare%'; # 233
SELECT * FROM opportunities where crm_configuration_id = 215
# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'
;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1088 and sa.provider = 'hubspot';
SELECT * FROM teams order by updated_at DESC
SELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account
select * from crm_configurations where provider = 'pipedrive';
select * from teams where id = 957;
select * from crm_configurations where id = 957;
SELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743
SELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;
select * from users where team_id = 1; # 26726 - Gabriela Dureva
SELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific
select * from activities where user_id = 26726 order by id desc;
select * from contacts where crm_configuration_id = 1
and email IN ('[EMAIL]', '[EMAIL]'); # 2094416, 2093620
SELECT * FROM contacts WHERE id = 6284931;
SELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id
WHERE 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;
select * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);
select * from crm_configurations where id = 1;
43801692-1aeb-32ce-acba-5b80a479701a
44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b
405975c0-b3d0-7aaa-821f-09d59cae6dd1
4caf848d-4bed-2299-b248-7788d41f9fca
49bedc3f-f196-eef3-89c3-dea6a3b4aa63
43420989-a09d-b8f8-9806-c8bbf7a02aac
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1 and sa.provider = 'salesforce';
SELECT * FROM activities WHERE id = 75461988;
SELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;
select * from contacts where id = 17900517;
select * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id
where crm.provider != 'salesforce';
select * from users where id = 21047;
SELECT * FROM crm_configurations WHERE id = 892;
SELECT * FROM teams WHERE id = 942;
select * from opportunities where team_id = 942 order by updated_at desc;
select * from contacts where team_id = 942 order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 942 and sa.provider = 'hubspot';
SELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430
SELECT * FROM crm_configurations WHERE id = 1;
SELECT * FROM teams WHERE crm_id = 1;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1 and sa.provider = 'salesforce';
select id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1
SELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430
select * from teams where id = 852;
select * from groups where id = 2286;
select * from sidekick_settings where team_id = 852;
select * from default_activity_types where team_id = 852;
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1 AND u.deleted_at IS NULL
AND u.crm_required = 1
AND u.team_id = 1
ORDER BY u.team_id;
SELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (
18481
);
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1
AND u.deleted_at IS NULL
AND u.crm_required = 1
# AND u.team_id = 1
AND p.id IS NULL -- Move this condition to WHERE clause
ORDER BY u.team_id;
SELECT * FROM opportunities WHERE id = 20002609;
select * from teams where id = 1122; # Velatir, 29953 - [EMAIL]
select * from crm_configurations where id = 1060;
select * from crm_layouts where crm_configuration_id = 1060;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1122 and sa.provider = 'hubspot';
select * from opportunities where team_id = 1122 order by updated_at desc;
select * from crm_field_data where object_type = 'contact';
SELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 248 and sa.provider = 'salesforce';
SELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS
SELECT * FROM users where id = 24115;
SELECT * FROM accounts where id = 4002896;
SELECT * FROM teams WHERE name LIKE '%adswerve%';
SELECT * 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");
SELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203
SELECT u.id, u.email, ac.name, a.* FROM activities a
JOIN users u ON a.user_id = u.id
JOIN accounts ac ON a.account_id = ac.id
WHERE
uuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or
uuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or
uuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;
select * from users where id = 5825;
SELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;
select * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;
19594, 862
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 862 and sa.provider = 'salesforce';
select * from automated_reports where id = 36;
select ar.frequency, r.*, ar.* from automated_report_results r
join automated_reports ar on r.report_id = ar.id
where ar.frequency != 'one_off';
select s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;
select * from nudges n where n.activity_search_id
select * from teams where created_at > '2026-03-09';
SELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;
select * from users where team_id = 1 and name like '%Lukas%'; # 7160
SELECT * FROM teams WHERE id = 575;
select * from opportunities where team_id = 575;
SELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,
select * from opportunities where team_id = 1126;
SELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,
select * from opportunities where team_id = 1125;
select * from contacts c
where c.team_id = 882;
SELECT * FROM activities WHERE id = 76822967;
SELECT * FROM crm_profiles WHERE user_id = 15440;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 555;
SELECT * FROM crm_configurations WHERE id = 555;
SELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 581 and sa.provider = 'salesforce';
SELECT * FROM automated_report_results order by id desc;
select * from features;
select * from team_features where feature_id = 40;
select * from teams where id = 556;
select * from automated_reports;
where id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , ["pdf","podcast"]
SELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;
select * from automated_report_results order by id desc;
SELECT * FROM automated_report_results WHERE id = 1919;
select * from automated_report_results WHERE report_id = 54;
select * from opportunities where id = 7594349;
SELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067 - [EMAIL]
select * from playbooks where team_id = 711; # event 226147
SELECT * FROM playbook_categories WHERE playbook_id = 5515;
SELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event';
SELECT * FROM crm_fields WHERE id = 226147;
SELECT * FROM crm_field_values WHERE crm_field_id = 226147;
SELECT * FROM crm_configurations WHERE id = 692;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 711 and sa.provider = 'salesforce';
SELECT * FROM crm_profiles cp JOIN users u on u.id = cp.user_id WHERE u.team_id = 711;
select * from leads;
select * from calendars;
SELECT
t.id AS team_id,
t.name,
LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1)) AS calendar_domain
FROM teams t
JOIN users u ON u.team_id = t.id
JOIN calendars c ON c.user_id = u.id AND c.status = 'active' AND c.calendar_provider_id LIKE '%@%'
LEFT JOIN team_domains td
ON td.team_id = t.id
AND td.deleted_at IS NULL
AND td.domain = LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1))
GROUP BY t.id, t.name, calendar_domain
ORDER BY t.name, calendar_domain;
select * from users u join calendars c on c.user_id = u.id
where u.team_id = 882;
select * from activities where id = 74049485; # team 563 crm 537
select * from activities where id = 73272382; # team 563 crm 537
select * from activities where id = 64400389; # team 563 crm 537
select * from activities where id = 58081273; # team 563 crm 537
select * from activities where id = 54520297; # team 563 crm 537
select * 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') = uuid
or uuid_to_bin('47842446-af51-4bcb-854f-cc6560290101') = uuid;
SELECT * FROM crm_configurations WHERE provider = 'hubspot';
select * from rate_limits;
select * from automated_report_results where media_type = 'pdf' and status = 2
and 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_relays where created_at > '2026-05-01'
# and id IN (32415, 32416);
and id = 32412;
select * from users where team_id = 2 and email like '%scott%' and id = 29510;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"1 file committed","depth":2,"on_screen":true,"role_description":"text"},{"role":"AXTextField","text":"JY-20891 add support for secondary email","depth":3,"on_screen":true,"value":"JY-20891 add support for secondary email","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Edit Commit Message…","depth":2,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"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},{"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":"Show Replace Field","depth":4,"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,"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"TextRelayException","depth":4,"on_screen":true,"value":"TextRelayException","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"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,"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Words","depth":3,"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Regex","depth":3,"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.0,"top":0.0,"width":0.015277778,"height":0.024444444},"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.0,"top":0.0,"width":0.015277778,"height":0.024444444},"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.0,"top":0.0,"width":0.015277778,"height":0.024444444},"on_screen":false,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1/12","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Occurrence","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Occurrence","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Filter Search Results","depth":4,"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,"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,"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":"29","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\nnamespace Jiminny\\Jobs\\Mailbox;\n\nuse Illuminate\\Support\\Str;\nuse Carbon\\Carbon;\nuse Google\\Service\\Gmail as GoogleGmail;\nuse Illuminate\\Queue\\SerializesModels;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Contracts\\Acl\\PermissionEnum;\nuse Jiminny\\Exceptions\\TextRelayException;\nuse Jiminny\\Jobs\\Job;\nuse EmailReplyParser\\Parser\\EmailParser;\nuse Jiminny\\Mail\\Activities\\SmsRelayFailed;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\TextRelay;\nuse Jiminny\\Repositories\\UserRepository;\nuse Jiminny\\Rules\\SmsMessage;\nuse Jiminny\\Services\\Mail\\TextRelayService;\nuse Jiminny\\Services\\Telephony\\TextMessagingService;\nuse Vinkla\\Hashids\\Facades\\Hashids;\nuse Validator;\n\nclass EmailTextRelay extends Job implements ShouldQueue\n{\n use InteractsWithQueue;\n use SerializesModels;\n\n private const REASON_CODE_MAILBOX_MISMATCH = 1000;\n\n private const REASON_CODE_SENDER_UNKNOWN = 2000;\n\n private const REASON_CODE_SENDER_MISMATCH = 2100;\n\n private const REASON_CODE_SENDER_FORBIDDEN = 2200;\n\n private const REASON_CODE_RECIPIENT_ORIGIN_INVALID = 3000;\n\n private const REASON_CODE_RECIPIENT_DESTINATION_INVALID = 3100;\n\n private const REASON_CODE_RECIPIENT_MISSING_INVALID = 3200;\n\n private const REASON_CODE_MESSAGE_BODY_TOO_LARGE = 4000;\n\n private const REASON_CODE_MESSAGE_BODY_EMPTY = 4100;\n\n private const REASON_CODE_INVALID_HASH = 5000;\n\n private const REASON_CODE_UNKNOWN = 10000;\n\n\n private $textRelay;\n\n private $messageId;\n\n private $to;\n\n private $text;\n\n /**\n * @var Activity\n */\n private $activityOrigin;\n\n /**\n * Create a new job instance.\n */\n public function __construct(string $messageId, TextRelay $textRelay)\n {\n $this->messageId = $messageId;\n $this->textRelay = $textRelay;\n }\n\n /**\n * Execute the job.\n */\n public function handle(\n TextMessagingService $messagingService,\n TextRelayService $relayService,\n UserRepository $userRepository\n ): void {\n if (config('jiminny.google_text_host') === null) {\n return;\n }\n\n $mailService = $relayService->getService(config('jiminny.google_text_user'));\n\n try {\n // Retrieve the message from the email server.\n $message = $this->getMessage($mailService, $this->messageId);\n\n $payload = $message->getPayload();\n\n $headers = $this->getHeaders($payload->getHeaders());\n\n $date = Carbon::createFromFormat(Carbon::RFC2822, $headers['Date']);\n\n $this->textRelay->update([\n 'email_sent_at' => $date,\n 'sender' => Str::limit($headers['From'], 191, ''),\n 'recipient' => Str::limit($headers['To'], 191, ''),\n ]);\n\n $this->checkIntegrity($payload, $userRepository);\n\n $user = $this->activityOrigin->user;\n\n // Create the activity and send the SMS.\n $messagingService->setTeam($user->team);\n\n $message = $messagingService->send(\n $user,\n $this->to,\n $this->text\n );\n\n $activity = $messagingService->buildActivity(\n $message->sid,\n Activity::TYPE_SMS_OUTBOUND,\n $user,\n $user->softphone_number,\n $this->to,\n $this->text,\n $this->activityOrigin->prospect ? $this->activityOrigin->prospect->crm_provider_id : null,\n strtolower($this->activityOrigin->prospect_type),\n null\n );\n\n $this->textRelay->update([\n 'origin_activity_id' => $this->activityOrigin->id,\n 'activity_id' => $activity->id,\n 'status' => TextRelay::STATUS_PROCESSED,\n ]);\n } catch (TextRelayException $e) {\n $this->textRelay->update([\n 'status' => TextRelay::STATUS_FAILED,\n 'code' => $e->getCode(),\n ]);\n\n $sender = $this->parseSender($headers['From']);\n\n // If we can pull the sender, tell them that it failed.\n if ($sender) {\n $responder = (new SmsRelayFailed($this->textRelay, $e->getMessage(), $e->getCode()))\n ->onQueue(Constants::QUEUE_EMAILS);\n\n \\Mail::to($sender)->queue($responder);\n }\n } catch (\\Exception $e) {\n $this->textRelay->update([\n 'status' => TextRelay::STATUS_FAILED,\n 'code' => self::REASON_CODE_UNKNOWN,\n ]);\n\n \\Sentry::captureException($e);\n } finally {\n // Delete the message from the email server (it lives for 30 days).\n $this->trashMessage($mailService, $this->messageId);\n }\n }\n\n /**\n * @param GoogleGmail\\MessagePartHeader[] $rawHeaders\n */\n private function getHeaders(array $rawHeaders): array\n {\n $headers = [];\n\n foreach ($rawHeaders as $header) {\n if (\\in_array($header->name, ['To', 'From', 'X-Gm-Original-To', 'Date'])) {\n $headers[$header->name] = $header->value;\n }\n }\n\n return $headers;\n }\n\n /**\n * @param GoogleGmail\\MessagePart[] $rawBodyParts\n */\n private function getBody(array $rawBodyParts): ?string\n {\n $body = null;\n\n foreach ($rawBodyParts as $bodyPart) {\n /* @var $bodyPart GoogleGmail\\MessagePart */\n if ($bodyPart->mimeType === 'text/plain') {\n $body = $this->base64UrlDecode($bodyPart->getBody()->getData());\n }\n }\n\n return $body;\n }\n\n private function parseSender(string $sender): ?string\n {\n preg_match_all(\n '/\\b[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\\.)+[A-Z]{2,6}\\b/i',\n $sender,\n $matches,\n PREG_PATTERN_ORDER\n );\n\n return $matches[0][0];\n }\n\n /**\n * @throws \\Exception\n */\n private function parseRecipient(string $recipient): string\n {\n preg_match(\n '/\\d+\\.\\d+\\.[a-zA-Z0-9]{10}@txt\\.(?:staging\\.)?jiminny\\.com/i',\n $recipient,\n $matches\n );\n\n if ($matches == false) {\n // This should not happen. If it does, the mailbox allowed an invalid catch-all.\n throw new TextRelayException(\n 'Recipient is missing or invalid.',\n self::REASON_CODE_RECIPIENT_MISSING_INVALID\n );\n }\n\n // Reply-To consists of digits.digits.string@mailbox\n return $matches[0];\n }\n\n /**\n * @throws \\Exception\n */\n private function parseRecipientIntoEntities(string $recipient): array\n {\n // Convert From/To/Activity into their usable types.\n [$from, $to, $encodedActivityId] = explode('.', $recipient);\n\n $from = '+' . $from;\n $to = '+' . $to;\n $activityId = Hashids::decode($encodedActivityId);\n\n return [$from, $to, $activityId];\n }\n\n /**\n * @throws \\Exception\n */\n private function checkIntegrity(GoogleGmail\\MessagePart $payload, UserRepository $userRepository)\n {\n $headers = $this->getHeaders($payload->getHeaders());\n $body = $this->getBody($payload->getParts());\n\n // Parse the email body and check it can actually be sent.\n $email = (new EmailParser())->parse($body);\n $text = $email->getVisibleText();\n\n if ($text === '') {\n throw new TextRelayException(\n 'Message body is un-readable or empty.',\n self::REASON_CODE_MESSAGE_BODY_EMPTY\n );\n }\n\n $validator = Validator::make(['data' => $text], ['data' => new SmsMessage()]);\n if ($validator->fails()) {\n throw new TextRelayException(\n 'Message body is too large to send as an SMS.',\n self::REASON_CODE_MESSAGE_BODY_TOO_LARGE\n );\n }\n\n $this->text = $text;\n\n $sender = $this->parseSender($headers['From']);\n\n // Extract the Jiminny recipient.\n $recipient = explode('@', $this->parseRecipient($headers['To']));\n\n // Check this message is intended for us.\n if ($recipient[1] !== config('jiminny.google_text_host')) {\n throw new TextRelayException(\n 'Destination Mailbox does not match.',\n self::REASON_CODE_MAILBOX_MISMATCH\n );\n }\n\n [$from, $to, $activityId] = $this->parseRecipientIntoEntities($recipient[0]);\n\n $validator = Validator::make(['data' => $from], ['data' => 'phone:INTERNATIONAL']);\n if ($validator->fails()) {\n throw new TextRelayException(\n 'Recipient origin number is not valid.',\n self::REASON_CODE_RECIPIENT_ORIGIN_INVALID\n );\n }\n\n $validator = Validator::make(['data' => $to], ['data' => 'phone:INTERNATIONAL']);\n if ($validator->fails()) {\n throw new TextRelayException(\n 'Recipient destination number is not valid.',\n self::REASON_CODE_RECIPIENT_DESTINATION_INVALID\n );\n }\n\n $user = $userRepository->findByEmail($sender);\n if ($user === null) {\n throw new TextRelayException(\n 'Sender does not exist.',\n self::REASON_CODE_SENDER_UNKNOWN\n );\n }\n\n if ($user->softphone_number === null || ! $user->hasPermission(PermissionEnum::SMS)) {\n throw new TextRelayException(\n 'Messaging is not enabled for this account.',\n self::REASON_CODE_SENDER_FORBIDDEN\n );\n }\n\n if ($activityId === null) {\n throw new TextRelayException(\n 'Origin activity has an invalid hash.',\n self::REASON_CODE_INVALID_HASH\n );\n }\n\n $activity = Activity::where('id', $activityId)->first();\n if ($activity && $activity->user_id !== $user->id) {\n throw new TextRelayException(\n 'Sender does not match the origin activity.',\n self::REASON_CODE_SENDER_MISMATCH\n );\n }\n\n $this->activityOrigin = $activity;\n $this->to = $to;\n }\n\n /**\n * Returns a base64 decoded web safe string\n *\n * @param string $string The string to be decoded\n *\n * @return string Decoded string\n */\n private function base64UrlDecode(string $string): string\n {\n return base64_decode(str_replace(['-', '_'], ['+', '/'], $string));\n }\n\n public function getMessage($service, $messageId): GoogleGmail\\Message\n {\n return $service->users_messages->get(config('jiminny.google_text_user'), $messageId);\n }\n\n public function trashMessage($service, $messageId)\n {\n return $service->users_messages->trash(config('jiminny.google_text_user'), $messageId);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Jobs\\Mailbox;\n\nuse Illuminate\\Support\\Str;\nuse Carbon\\Carbon;\nuse Google\\Service\\Gmail as GoogleGmail;\nuse Illuminate\\Queue\\SerializesModels;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Contracts\\Acl\\PermissionEnum;\nuse Jiminny\\Exceptions\\TextRelayException;\nuse Jiminny\\Jobs\\Job;\nuse EmailReplyParser\\Parser\\EmailParser;\nuse Jiminny\\Mail\\Activities\\SmsRelayFailed;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\TextRelay;\nuse Jiminny\\Repositories\\UserRepository;\nuse Jiminny\\Rules\\SmsMessage;\nuse Jiminny\\Services\\Mail\\TextRelayService;\nuse Jiminny\\Services\\Telephony\\TextMessagingService;\nuse Vinkla\\Hashids\\Facades\\Hashids;\nuse Validator;\n\nclass EmailTextRelay extends Job implements ShouldQueue\n{\n use InteractsWithQueue;\n use SerializesModels;\n\n private const REASON_CODE_MAILBOX_MISMATCH = 1000;\n\n private const REASON_CODE_SENDER_UNKNOWN = 2000;\n\n private const REASON_CODE_SENDER_MISMATCH = 2100;\n\n private const REASON_CODE_SENDER_FORBIDDEN = 2200;\n\n private const REASON_CODE_RECIPIENT_ORIGIN_INVALID = 3000;\n\n private const REASON_CODE_RECIPIENT_DESTINATION_INVALID = 3100;\n\n private const REASON_CODE_RECIPIENT_MISSING_INVALID = 3200;\n\n private const REASON_CODE_MESSAGE_BODY_TOO_LARGE = 4000;\n\n private const REASON_CODE_MESSAGE_BODY_EMPTY = 4100;\n\n private const REASON_CODE_INVALID_HASH = 5000;\n\n private const REASON_CODE_UNKNOWN = 10000;\n\n\n private $textRelay;\n\n private $messageId;\n\n private $to;\n\n private $text;\n\n /**\n * @var Activity\n */\n private $activityOrigin;\n\n /**\n * Create a new job instance.\n */\n public function __construct(string $messageId, TextRelay $textRelay)\n {\n $this->messageId = $messageId;\n $this->textRelay = $textRelay;\n }\n\n /**\n * Execute the job.\n */\n public function handle(\n TextMessagingService $messagingService,\n TextRelayService $relayService,\n UserRepository $userRepository\n ): void {\n if (config('jiminny.google_text_host') === null) {\n return;\n }\n\n $mailService = $relayService->getService(config('jiminny.google_text_user'));\n\n try {\n // Retrieve the message from the email server.\n $message = $this->getMessage($mailService, $this->messageId);\n\n $payload = $message->getPayload();\n\n $headers = $this->getHeaders($payload->getHeaders());\n\n $date = Carbon::createFromFormat(Carbon::RFC2822, $headers['Date']);\n\n $this->textRelay->update([\n 'email_sent_at' => $date,\n 'sender' => Str::limit($headers['From'], 191, ''),\n 'recipient' => Str::limit($headers['To'], 191, ''),\n ]);\n\n $this->checkIntegrity($payload, $userRepository);\n\n $user = $this->activityOrigin->user;\n\n // Create the activity and send the SMS.\n $messagingService->setTeam($user->team);\n\n $message = $messagingService->send(\n $user,\n $this->to,\n $this->text\n );\n\n $activity = $messagingService->buildActivity(\n $message->sid,\n Activity::TYPE_SMS_OUTBOUND,\n $user,\n $user->softphone_number,\n $this->to,\n $this->text,\n $this->activityOrigin->prospect ? $this->activityOrigin->prospect->crm_provider_id : null,\n strtolower($this->activityOrigin->prospect_type),\n null\n );\n\n $this->textRelay->update([\n 'origin_activity_id' => $this->activityOrigin->id,\n 'activity_id' => $activity->id,\n 'status' => TextRelay::STATUS_PROCESSED,\n ]);\n } catch (TextRelayException $e) {\n $this->textRelay->update([\n 'status' => TextRelay::STATUS_FAILED,\n 'code' => $e->getCode(),\n ]);\n\n $sender = $this->parseSender($headers['From']);\n\n // If we can pull the sender, tell them that it failed.\n if ($sender) {\n $responder = (new SmsRelayFailed($this->textRelay, $e->getMessage(), $e->getCode()))\n ->onQueue(Constants::QUEUE_EMAILS);\n\n \\Mail::to($sender)->queue($responder);\n }\n } catch (\\Exception $e) {\n $this->textRelay->update([\n 'status' => TextRelay::STATUS_FAILED,\n 'code' => self::REASON_CODE_UNKNOWN,\n ]);\n\n \\Sentry::captureException($e);\n } finally {\n // Delete the message from the email server (it lives for 30 days).\n $this->trashMessage($mailService, $this->messageId);\n }\n }\n\n /**\n * @param GoogleGmail\\MessagePartHeader[] $rawHeaders\n */\n private function getHeaders(array $rawHeaders): array\n {\n $headers = [];\n\n foreach ($rawHeaders as $header) {\n if (\\in_array($header->name, ['To', 'From', 'X-Gm-Original-To', 'Date'])) {\n $headers[$header->name] = $header->value;\n }\n }\n\n return $headers;\n }\n\n /**\n * @param GoogleGmail\\MessagePart[] $rawBodyParts\n */\n private function getBody(array $rawBodyParts): ?string\n {\n $body = null;\n\n foreach ($rawBodyParts as $bodyPart) {\n /* @var $bodyPart GoogleGmail\\MessagePart */\n if ($bodyPart->mimeType === 'text/plain') {\n $body = $this->base64UrlDecode($bodyPart->getBody()->getData());\n }\n }\n\n return $body;\n }\n\n private function parseSender(string $sender): ?string\n {\n preg_match_all(\n '/\\b[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\\.)+[A-Z]{2,6}\\b/i',\n $sender,\n $matches,\n PREG_PATTERN_ORDER\n );\n\n return $matches[0][0];\n }\n\n /**\n * @throws \\Exception\n */\n private function parseRecipient(string $recipient): string\n {\n preg_match(\n '/\\d+\\.\\d+\\.[a-zA-Z0-9]{10}@txt\\.(?:staging\\.)?jiminny\\.com/i',\n $recipient,\n $matches\n );\n\n if ($matches == false) {\n // This should not happen. If it does, the mailbox allowed an invalid catch-all.\n throw new TextRelayException(\n 'Recipient is missing or invalid.',\n self::REASON_CODE_RECIPIENT_MISSING_INVALID\n );\n }\n\n // Reply-To consists of digits.digits.string@mailbox\n return $matches[0];\n }\n\n /**\n * @throws \\Exception\n */\n private function parseRecipientIntoEntities(string $recipient): array\n {\n // Convert From/To/Activity into their usable types.\n [$from, $to, $encodedActivityId] = explode('.', $recipient);\n\n $from = '+' . $from;\n $to = '+' . $to;\n $activityId = Hashids::decode($encodedActivityId);\n\n return [$from, $to, $activityId];\n }\n\n /**\n * @throws \\Exception\n */\n private function checkIntegrity(GoogleGmail\\MessagePart $payload, UserRepository $userRepository)\n {\n $headers = $this->getHeaders($payload->getHeaders());\n $body = $this->getBody($payload->getParts());\n\n // Parse the email body and check it can actually be sent.\n $email = (new EmailParser())->parse($body);\n $text = $email->getVisibleText();\n\n if ($text === '') {\n throw new TextRelayException(\n 'Message body is un-readable or empty.',\n self::REASON_CODE_MESSAGE_BODY_EMPTY\n );\n }\n\n $validator = Validator::make(['data' => $text], ['data' => new SmsMessage()]);\n if ($validator->fails()) {\n throw new TextRelayException(\n 'Message body is too large to send as an SMS.',\n self::REASON_CODE_MESSAGE_BODY_TOO_LARGE\n );\n }\n\n $this->text = $text;\n\n $sender = $this->parseSender($headers['From']);\n\n // Extract the Jiminny recipient.\n $recipient = explode('@', $this->parseRecipient($headers['To']));\n\n // Check this message is intended for us.\n if ($recipient[1] !== config('jiminny.google_text_host')) {\n throw new TextRelayException(\n 'Destination Mailbox does not match.',\n self::REASON_CODE_MAILBOX_MISMATCH\n );\n }\n\n [$from, $to, $activityId] = $this->parseRecipientIntoEntities($recipient[0]);\n\n $validator = Validator::make(['data' => $from], ['data' => 'phone:INTERNATIONAL']);\n if ($validator->fails()) {\n throw new TextRelayException(\n 'Recipient origin number is not valid.',\n self::REASON_CODE_RECIPIENT_ORIGIN_INVALID\n );\n }\n\n $validator = Validator::make(['data' => $to], ['data' => 'phone:INTERNATIONAL']);\n if ($validator->fails()) {\n throw new TextRelayException(\n 'Recipient destination number is not valid.',\n self::REASON_CODE_RECIPIENT_DESTINATION_INVALID\n );\n }\n\n $user = $userRepository->findByEmail($sender);\n if ($user === null) {\n throw new TextRelayException(\n 'Sender does not exist.',\n self::REASON_CODE_SENDER_UNKNOWN\n );\n }\n\n if ($user->softphone_number === null || ! $user->hasPermission(PermissionEnum::SMS)) {\n throw new TextRelayException(\n 'Messaging is not enabled for this account.',\n self::REASON_CODE_SENDER_FORBIDDEN\n );\n }\n\n if ($activityId === null) {\n throw new TextRelayException(\n 'Origin activity has an invalid hash.',\n self::REASON_CODE_INVALID_HASH\n );\n }\n\n $activity = Activity::where('id', $activityId)->first();\n if ($activity && $activity->user_id !== $user->id) {\n throw new TextRelayException(\n 'Sender does not match the origin activity.',\n self::REASON_CODE_SENDER_MISMATCH\n );\n }\n\n $this->activityOrigin = $activity;\n $this->to = $to;\n }\n\n /**\n * Returns a base64 decoded web safe string\n *\n * @param string $string The string to be decoded\n *\n * @return string Decoded string\n */\n private function base64UrlDecode(string $string): string\n {\n return base64_decode(str_replace(['-', '_'], ['+', '/'], $string));\n }\n\n public function getMessage($service, $messageId): GoogleGmail\\Message\n {\n return $service->users_messages->get(config('jiminny.google_text_user'), $messageId);\n }\n\n public function trashMessage($service, $messageId)\n {\n return $service->users_messages->trash(config('jiminny.google_text_user'), $messageId);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"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,"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,"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,"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,"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,"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,"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,"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"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":"40","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"40","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"65","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":"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;","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;","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}]...
|
-9089659913590458937
|
2218635325254022725
|
visual_change
|
accessibility
|
NULL
|
1 file committed
JY-20891 add support for secondar 1 file committed
JY-20891 add support for secondary email
text/html
text/html
text/html
Edit Commit Message…
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
TextRelayException
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
1/12
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Sync Changes
Hide This Notification
Code changed:
Hide
29
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Jobs\Mailbox;
use Illuminate\Support\Str;
use Carbon\Carbon;
use Google\Service\Gmail as GoogleGmail;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Contracts\Acl\PermissionEnum;
use Jiminny\Exceptions\TextRelayException;
use Jiminny\Jobs\Job;
use EmailReplyParser\Parser\EmailParser;
use Jiminny\Mail\Activities\SmsRelayFailed;
use Jiminny\Models\Activity;
use Jiminny\Models\TextRelay;
use Jiminny\Repositories\UserRepository;
use Jiminny\Rules\SmsMessage;
use Jiminny\Services\Mail\TextRelayService;
use Jiminny\Services\Telephony\TextMessagingService;
use Vinkla\Hashids\Facades\Hashids;
use Validator;
class EmailTextRelay extends Job implements ShouldQueue
{
use InteractsWithQueue;
use SerializesModels;
private const REASON_CODE_MAILBOX_MISMATCH = 1000;
private const REASON_CODE_SENDER_UNKNOWN = 2000;
private const REASON_CODE_SENDER_MISMATCH = 2100;
private const REASON_CODE_SENDER_FORBIDDEN = 2200;
private const REASON_CODE_RECIPIENT_ORIGIN_INVALID = 3000;
private const REASON_CODE_RECIPIENT_DESTINATION_INVALID = 3100;
private const REASON_CODE_RECIPIENT_MISSING_INVALID = 3200;
private const REASON_CODE_MESSAGE_BODY_TOO_LARGE = 4000;
private const REASON_CODE_MESSAGE_BODY_EMPTY = 4100;
private const REASON_CODE_INVALID_HASH = 5000;
private const REASON_CODE_UNKNOWN = 10000;
private $textRelay;
private $messageId;
private $to;
private $text;
/**
* @var Activity
*/
private $activityOrigin;
/**
* Create a new job instance.
*/
public function __construct(string $messageId, TextRelay $textRelay)
{
$this->messageId = $messageId;
$this->textRelay = $textRelay;
}
/**
* Execute the job.
*/
public function handle(
TextMessagingService $messagingService,
TextRelayService $relayService,
UserRepository $userRepository
): void {
if (config('jiminny.google_text_host') === null) {
return;
}
$mailService = $relayService->getService(config('jiminny.google_text_user'));
try {
// Retrieve the message from the email server.
$message = $this->getMessage($mailService, $this->messageId);
$payload = $message->getPayload();
$headers = $this->getHeaders($payload->getHeaders());
$date = Carbon::createFromFormat(Carbon::RFC2822, $headers['Date']);
$this->textRelay->update([
'email_sent_at' => $date,
'sender' => Str::limit($headers['From'], 191, ''),
'recipient' => Str::limit($headers['To'], 191, ''),
]);
$this->checkIntegrity($payload, $userRepository);
$user = $this->activityOrigin->user;
// Create the activity and send the SMS.
$messagingService->setTeam($user->team);
$message = $messagingService->send(
$user,
$this->to,
$this->text
);
$activity = $messagingService->buildActivity(
$message->sid,
Activity::TYPE_SMS_OUTBOUND,
$user,
$user->softphone_number,
$this->to,
$this->text,
$this->activityOrigin->prospect ? $this->activityOrigin->prospect->crm_provider_id : null,
strtolower($this->activityOrigin->prospect_type),
null
);
$this->textRelay->update([
'origin_activity_id' => $this->activityOrigin->id,
'activity_id' => $activity->id,
'status' => TextRelay::STATUS_PROCESSED,
]);
} catch (TextRelayException $e) {
$this->textRelay->update([
'status' => TextRelay::STATUS_FAILED,
'code' => $e->getCode(),
]);
$sender = $this->parseSender($headers['From']);
// If we can pull the sender, tell them that it failed.
if ($sender) {
$responder = (new SmsRelayFailed($this->textRelay, $e->getMessage(), $e->getCode()))
->onQueue(Constants::QUEUE_EMAILS);
\Mail::to($sender)->queue($responder);
}
} catch (\Exception $e) {
$this->textRelay->update([
'status' => TextRelay::STATUS_FAILED,
'code' => self::REASON_CODE_UNKNOWN,
]);
\Sentry::captureException($e);
} finally {
// Delete the message from the email server (it lives for 30 days).
$this->trashMessage($mailService, $this->messageId);
}
}
/**
* @param GoogleGmail\MessagePartHeader[] $rawHeaders
*/
private function getHeaders(array $rawHeaders): array
{
$headers = [];
foreach ($rawHeaders as $header) {
if (\in_array($header->name, ['To', 'From', 'X-Gm-Original-To', 'Date'])) {
$headers[$header->name] = $header->value;
}
}
return $headers;
}
/**
* @param GoogleGmail\MessagePart[] $rawBodyParts
*/
private function getBody(array $rawBodyParts): ?string
{
$body = null;
foreach ($rawBodyParts as $bodyPart) {
/* @var $bodyPart GoogleGmail\MessagePart */
if ($bodyPart->mimeType === 'text/plain') {
$body = $this->base64UrlDecode($bodyPart->getBody()->getData());
}
}
return $body;
}
private function parseSender(string $sender): ?string
{
preg_match_all(
'/\b[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}\b/i',
$sender,
$matches,
PREG_PATTERN_ORDER
);
return $matches[0][0];
}
/**
* @throws \Exception
*/
private function parseRecipient(string $recipient): string
{
preg_match(
'/\d+\.\d+\.[a-zA-Z0-9]{10}@txt\.(?:staging\.)?jiminny\.com/i',
$recipient,
$matches
);
if ($matches == false) {
// This should not happen. If it does, the mailbox allowed an invalid catch-all.
throw new TextRelayException(
'Recipient is missing or invalid.',
self::REASON_CODE_RECIPIENT_MISSING_INVALID
);
}
// Reply-To consists of digits.digits.string@mailbox
return $matches[0];
}
/**
* @throws \Exception
*/
private function parseRecipientIntoEntities(string $recipient): array
{
// Convert From/To/Activity into their usable types.
[$from, $to, $encodedActivityId] = explode('.', $recipient);
$from = '+' . $from;
$to = '+' . $to;
$activityId = Hashids::decode($encodedActivityId);
return [$from, $to, $activityId];
}
/**
* @throws \Exception
*/
private function checkIntegrity(GoogleGmail\MessagePart $payload, UserRepository $userRepository)
{
$headers = $this->getHeaders($payload->getHeaders());
$body = $this->getBody($payload->getParts());
// Parse the email body and check it can actually be sent.
$email = (new EmailParser())->parse($body);
$text = $email->getVisibleText();
if ($text === '') {
throw new TextRelayException(
'Message body is un-readable or empty.',
self::REASON_CODE_MESSAGE_BODY_EMPTY
);
}
$validator = Validator::make(['data' => $text], ['data' => new SmsMessage()]);
if ($validator->fails()) {
throw new TextRelayException(
'Message body is too large to send as an SMS.',
self::REASON_CODE_MESSAGE_BODY_TOO_LARGE
);
}
$this->text = $text;
$sender = $this->parseSender($headers['From']);
// Extract the Jiminny recipient.
$recipient = explode('@', $this->parseRecipient($headers['To']));
// Check this message is intended for us.
if ($recipient[1] !== config('jiminny.google_text_host')) {
throw new TextRelayException(
'Destination Mailbox does not match.',
self::REASON_CODE_MAILBOX_MISMATCH
);
}
[$from, $to, $activityId] = $this->parseRecipientIntoEntities($recipient[0]);
$validator = Validator::make(['data' => $from], ['data' => 'phone:INTERNATIONAL']);
if ($validator->fails()) {
throw new TextRelayException(
'Recipient origin number is not valid.',
self::REASON_CODE_RECIPIENT_ORIGIN_INVALID
);
}
$validator = Validator::make(['data' => $to], ['data' => 'phone:INTERNATIONAL']);
if ($validator->fails()) {
throw new TextRelayException(
'Recipient destination number is not valid.',
self::REASON_CODE_RECIPIENT_DESTINATION_INVALID
);
}
$user = $userRepository->findByEmail($sender);
if ($user === null) {
throw new TextRelayException(
'Sender does not exist.',
self::REASON_CODE_SENDER_UNKNOWN
);
}
if ($user->softphone_number === null || ! $user->hasPermission(PermissionEnum::SMS)) {
throw new TextRelayException(
'Messaging is not enabled for this account.',
self::REASON_CODE_SENDER_FORBIDDEN
);
}
if ($activityId === null) {
throw new TextRelayException(
'Origin activity has an invalid hash.',
self::REASON_CODE_INVALID_HASH
);
}
$activity = Activity::where('id', $activityId)->first();
if ($activity && $activity->user_id !== $user->id) {
throw new TextRelayException(
'Sender does not match the origin activity.',
self::REASON_CODE_SENDER_MISMATCH
);
}
$this->activityOrigin = $activity;
$this->to = $to;
}
/**
* Returns a base64 decoded web safe string
*
* @param string $string The string to be decoded
*
* @return string Decoded string
*/
private function base64UrlDecode(string $string): string
{
return base64_decode(str_replace(['-', '_'], ['+', '/'], $string));
}
public function getMessage($service, $messageId): GoogleGmail\Message
{
return $service->users_messages->get(config('jiminny.google_text_user'), $messageId);
}
public function trashMessage($service, $messageId)
{
return $service->users_messages->trash(config('jiminny.google_text_user'), $messageId);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
40
1
40
65
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993
SELECT * FROM users WHERE id = 25061;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 994;
SELECT * FROM crm_profiles WHERE user_id = 25061;
select * from crm_configurations where id = 834;
SELECT * FROM teams WHERE id = 882;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 882 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 933 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;
SELECT * FROM contacts where crm_configuration_id = 834;
SELECT * FROM opportunities WHERE team_id = 933
# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');
AND id IN (8482561,18352941,19042734,19232139,19445140,19472541);
SELECT * FROM opportunity_contacts
WHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 485; #
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 933 and sa.provider = 'hubspot';
select crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id
where crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')
# and l.converted_at IS NOT NULL
;
# [PASSWORD_DOTS]
SELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')
and opportunity_id IS NULL
order by id desc;
SELECT * FROM teams WHERE id = 604; # 598
SELECT * FROM activities WHERE id = 74410828; # [EMAIL]
SELECT * FROM accounts WHERE id = 20068382;
SELECT * FROM accounts WHERE id = 35186038;
SELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 559 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;
select * from sidekick_settings where team_id = 781;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100
SELECT * FROM crm_layouts WHERE crm_configuration_id = 711;
SELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL
and is_internal = 0 and status = 'completed'
order by id desc;
SELECT * FROM crm_layout_entities
WHERE crm_layout_id IN (2352, 2353);
;
SELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 556 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;
SELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;
select * from contacts
where crm_configuration_id = 530
and crm_provider_id = 872252;
select * from activities where crm_configuration_id = 530
and user_id = 14343 and type like '%softphone%'
and created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);
SELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t
JOIN crm_configurations c ON t.id = c.team_id
WHERE t.status = 'active';
SELECT * FROM teams where id = 1091;
SELECT * FROM crm_configurations where team_id = 1091;
SELECT * FROM activity_providers where team_id = 1091;
SELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT * FROM teams WHERE name LIKE '%Leadventure%';
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1091 and sa.provider = 'salesforce';
SELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812
SELECT * FROM teams where id = 862;
SELECT * FROM crm_configurations where team_id = 862;
SELECT * FROM activity_providers where team_id = 862;
SELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT t.id, crm.id, crm.provider, ap.* FROM teams t
join crm_configurations crm on t.id = crm.team_id
join activity_providers ap on t.id = ap.team_id
where t.status = 'active' and ap.is_enabled = 1
and crm.provider = 'hubspot'
and ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',
'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');
SELECT * FROM teams where id = 1068;
SELECT * FROM crm_configurations where team_id = 1068;
SELECT * FROM activity_providers where team_id = 1068;
SELECT * FROM activities a
where crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')
and a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'
)
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by a.id desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1068 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 933 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262
SELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 882 and sa.provider = 'hubspot';
select * from crm_layouts where crm_configuration_id = 834;
select * from crm_layout_entities where crm_layout_id = 2780;
select * from crm_fields where id IN (321153,321192,321193,321194);
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1057 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8
SELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20
SELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10
SELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #
SELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;
select * from users where team_id = 51; # 7783
SELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130
select * from activity_searches where user_id = 7783;
select * from activity_search_filters where activity_search_id IN (32291, 32292);
SELECT asf.activity_search_id, asf.id, asf.value
FROM activity_search_filters asf
WHERE asf.filter = 'group_id'
AND asf.value IN (
SELECT CONCAT(
HEX(SUBSTR(uuid, 5, 4)), '-',
HEX(SUBSTR(uuid, 3, 2)), '-',
HEX(SUBSTR(uuid, 1, 2)), '-',
HEX(SUBSTR(uuid, 9, 2)), '-',
HEX(SUBSTR(uuid, 11))
)
FROM groups
WHERE deleted_at IS NOT NULL
);
SELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where provider = 'hubspot';
SELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133
SELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null
# [PASSWORD_DOTS]
select * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';
select
cp.*
# DISTINCT t.id
# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields
FROM crm_profiles cp
JOIN crm_configurations crm on crm.id = cp.crm_configuration_id
JOIN users u on u.id = cp.user_id
JOIN teams t ON t.id = crm.team_id
WHERE crm.provider = 'salesforce' and t.status = 'active'
and cp.archived_at IS NULL and u.deleted_at IS NULL
and t.id NOT IN (1093)
and t.id = 2
and cp.contact_fields IS NULL;
# and c.crm_provider_id = '003Uu00000ojD4NIAU';
SELECT * FROM users WHERE id = 26484;
SELECT * FROM crm_profiles WHERE user_id = 26484;
SELECT * FROM social_accounts WHERE sociable_id = 26484;
SELECT * FROM crm_configurations where provider = 'salesforce';
select * from users where id IN (10022, 10403);
select * from users where team_id IN (526);
select * from teams where id IN (526, 532);
select * from crm_configurations where id IN (500, 516);
select * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);
select * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 526 and sa.provider = 'salesforce';
select * from team_settings where team_id IN (526, 532);
select * from users where id IN (22824);
select * from crm_profiles where crm_configuration_id IN (1026);
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1093 and sa.provider = 'salesforce';
select * from teams where id = 1099;
select * from users where id = 29643
select * from activity_processing_states;
SELECT * FROM teams where name LIKE '%Fare%'; # 233
SELECT * FROM opportunities where crm_configuration_id = 215
# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'
;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1088 and sa.provider = 'hubspot';
SELECT * FROM teams order by updated_at DESC
SELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account
select * from crm_configurations where provider = 'pipedrive';
select * from teams where id = 957;
select * from crm_configurations where id = 957;
SELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743
SELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;
select * from users where team_id = 1; # 26726 - Gabriela Dureva
SELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific
select * from activities where user_id = 26726 order by id desc;
select * from contacts where crm_configuration_id = 1
and email IN ('[EMAIL]', '[EMAIL]'); # 2094416, 2093620
SELECT * FROM contacts WHERE id = 6284931;
SELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id
WHERE 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;
select * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);
select * from crm_configurations where id = 1;
43801692-1aeb-32ce-acba-5b80a479701a
44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b
405975c0-b3d0-7aaa-821f-09d59cae6dd1
4caf848d-4bed-2299-b248-7788d41f9fca
49bedc3f-f196-eef3-89c3-dea6a3b4aa63
43420989-a09d-b8f8-9806-c8bbf7a02aac
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1 and sa.provider = 'salesforce';
SELECT * FROM activities WHERE id = 75461988;
SELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;
select * from contacts where id = 17900517;
select * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id
where crm.provider != 'salesforce';
select * from users where id = 21047;
SELECT * FROM crm_configurations WHERE id = 892;
SELECT * FROM teams WHERE id = 942;
select * from opportunities where team_id = 942 order by updated_at desc;
select * from contacts where team_id = 942 order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 942 and sa.provider = 'hubspot';
SELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430
SELECT * FROM crm_configurations WHERE id = 1;
SELECT * FROM teams WHERE crm_id = 1;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1 and sa.provider = 'salesforce';
select id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1
SELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430
select * from teams where id = 852;
select * from groups where id = 2286;
select * from sidekick_settings where team_id = 852;
select * from default_activity_types where team_id = 852;
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1 AND u.deleted_at IS NULL
AND u.crm_required = 1
AND u.team_id = 1
ORDER BY u.team_id;
SELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (
18481
);
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1
AND u.deleted_at IS NULL
AND u.crm_required = 1
# AND u.team_id = 1
AND p.id IS NULL -- Move this condition to WHERE clause
ORDER BY u.team_id;
SELECT * FROM opportunities WHERE id = 20002609;
select * from teams where id = 1122; # Velatir, 29953 - [EMAIL]
select * from crm_configurations where id = 1060;
select * from crm_layouts where crm_configuration_id = 1060;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1122 and sa.provider = 'hubspot';
select * from opportunities where team_id = 1122 order by updated_at desc;
select * from crm_field_data where object_type = 'contact';
SELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 248 and sa.provider = 'salesforce';
SELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS
SELECT * FROM users where id = 24115;
SELECT * FROM accounts where id = 4002896;
SELECT * FROM teams WHERE name LIKE '%adswerve%';
SELECT * 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");
SELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203
SELECT u.id, u.email, ac.name, a.* FROM activities a
JOIN users u ON a.user_id = u.id
JOIN accounts ac ON a.account_id = ac.id
WHERE
uuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or
uuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or
uuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;
select * from users where id = 5825;
SELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;
select * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;
19594, 862
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 862 and sa.provider = 'salesforce';
select * from automated_reports where id = 36;
select ar.frequency, r.*, ar.* from automated_report_results r
join automated_reports ar on r.report_id = ar.id
where ar.frequency != 'one_off';
select s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;
select * from nudges n where n.activity_search_id
select * from teams where created_at > '2026-03-09';
SELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;
select * from users where team_id = 1 and name like '%Lukas%'; # 7160
SELECT * FROM teams WHERE id = 575;
select * from opportunities where team_id = 575;
SELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,
select * from opportunities where team_id = 1126;
SELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,
select * from opportunities where team_id = 1125;
select * from contacts c
where c.team_id = 882;
SELECT * FROM activities WHERE id = 76822967;
SELECT * FROM crm_profiles WHERE user_id = 15440;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 555;
SELECT * FROM crm_configurations WHERE id = 555;
SELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 581 and sa.provider = 'salesforce';
SELECT * FROM automated_report_results order by id desc;
select * from features;
select * from team_features where feature_id = 40;
select * from teams where id = 556;
select * from automated_reports;
where id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , ["pdf","podcast"]
SELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;
select * from automated_report_results order by id desc;
SELECT * FROM automated_report_results WHERE id = 1919;
select * from automated_report_results WHERE report_id = 54;
select * from opportunities where id = 7594349;
SELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067 - [EMAIL]
select * from playbooks where team_id = 711; # event 226147
SELECT * FROM playbook_categories WHERE playbook_id = 5515;
SELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event';
SELECT * FROM crm_fields WHERE id = 226147;
SELECT * FROM crm_field_values WHERE crm_field_id = 226147;
SELECT * FROM crm_configurations WHERE id = 692;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 711 and sa.provider = 'salesforce';
SELECT * FROM crm_profiles cp JOIN users u on u.id = cp.user_id WHERE u.team_id = 711;
select * from leads;
select * from calendars;
SELECT
t.id AS team_id,
t.name,
LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1)) AS calendar_domain
FROM teams t
JOIN users u ON u.team_id = t.id
JOIN calendars c ON c.user_id = u.id AND c.status = 'active' AND c.calendar_provider_id LIKE '%@%'
LEFT JOIN team_domains td
ON td.team_id = t.id
AND td.deleted_at IS NULL
AND td.domain = LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1))
GROUP BY t.id, t.name, calendar_domain
ORDER BY t.name, calendar_domain;
select * from users u join calendars c on c.user_id = u.id
where u.team_id = 882;
select * from activities where id = 74049485; # team 563 crm 537
select * from activities where id = 73272382; # team 563 crm 537
select * from activities where id = 64400389; # team 563 crm 537
select * from activities where id = 58081273; # team 563 crm 537
select * from activities where id = 54520297; # team 563 crm 537
select * 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') = uuid
or uuid_to_bin('47842446-af51-4bcb-854f-cc6560290101') = uuid;
SELECT * FROM crm_configurations WHERE provider = 'hubspot';
select * from rate_limits;
select * from automated_report_results where media_type = 'pdf' and status = 2
and 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_relays where created_at > '2026-05-01'
# and id IN (32415, 32416);
and id = 32412;
select * from users where team_id = 2 and email like '%scott%' and id = 29510;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
34799
|
NULL
|
NULL
|
NULL
|
|
32058
|
1241
|
58
|
2026-05-13T09:04:31.604653+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-13/1778 /Users/lukas/.screenpipe/data/data/2026-05-13/1778663071604_m2.jpg...
|
Firefox
|
Screenpipe — Archive — Personal
|
1
|
app.screenpipe.lakylak.xyz
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Screenpipe — Archive
Screenpipe — Archive
All docs Screenpipe — Archive
Screenpipe — Archive
All docs · AFFiNE
All docs · AFFiNE
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
New Tab
New Tab
Screenpipe — Archive
Screenpipe — Archive
Close tab
SQLite Web: archive.db
SQLite Web: archive.db
SQLite Web: db.sqlite
SQLite Web: db.sqlite
Claude
Claude
Manage extra usage for paid Claude plans | Claude Help Center
Manage extra usage for paid Claude plans | Claude Help Center
2 TB in 25 MB/s - Google Search
2 TB in 25 MB/s - Google Search
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
AI Chat settings
Close
Google Account: Lukáš Koválik ([EMAIL])
Main menu
New chat
Gemini
New chat
Share conversation
Open menu for conversation actions.
Conversation with Gemini
Conversation with Gemini
Copy prompt
You said Exaplin me: how does the ScreenPipe audio recording work? Where did it start? How is it getting transferred and transcribed? Is there some work in progress stage? How can I see from the folder structure what has been done and what hasn't been done?
You said
Exaplin me: how does the ScreenPipe audio recording work? Where did it start? How is it getting transferred and transcribed? Is there some work in progress stage? How can I see from the folder structure what has been done and what hasn't been done?
Listen
Show thinking
Gemini said
Gemini said
Here is a breakdown of how ScreenPipe handles audio recording, from the moment it captures sound to how it organizes the data on your machine.
ScreenPipe is fundamentally designed to be a
100% local, privacy-first
application, meaning the vast majority of its processing happens directly on your hardware without relying on the cloud.
View source details. Opens side panel.
1. Where It Starts: The Capture Layer
1. Where It Starts: The Capture Layer
The process begins locally by tapping directly into your operating system’s audio routing. ScreenPipe simultaneously captures two separate audio streams:
System Audio:
Everything your computer outputs (e.g., the voices of other people in a Zoom or Teams meeting, a YouTube video, or Spotify).
Microphone Input:
Everything you say into your mic.
View source details. Opens side panel.
Because ScreenPipe acts as a 24/7 background process, it continuously listens to these streams and breaks the continuous audio into manageable chunks to prepare them for the next stage.
2. How It Gets Transferred and Transcribed
2. How It Gets Transferred and Transcribed
Because ScreenPipe prioritizes local processing, the "transfer" step is incredibly short. The audio data never leaves your machine unless you explicitly set up a cloud plugin.
Here is the transcription pipeline:
The Engine:
The raw audio chunks are fed into a local speech-to-text model.
By default, ScreenPipe uses
OpenAI Whisper
running locally on your hardware. (Users can also configure it to use cloud providers like Deepgram if they need faster processing, but local Whisper is the standard).
View source details. Opens side panel.
Diarization:
As it transcribes the text, the engine also performs "diarization"—a technical term for speaker identification.
It analyzes the audio to distinguish between your voice and the voices of others, labeling who said what.
View source details. Opens side panel.
Storage:
The final transcribed text is then indexed into a local
SQLite database
. This database uses Full-Text Search (FTS5), which is what allows you to instantly search for a phrase you heard in a meeting three weeks ago.
3. The "Work in Progress" (WIP) Stage
3. The "Work in Progress" (WIP) Stage
There is a slight bottleneck between
hearing
the audio and
saving
the text, which acts as the WIP stage:
Buffering:
Audio is recorded into a temporary buffer in your system's RAM or written as temporary chunk files on your disk.
Processing Queue:
Whisper requires CPU/GPU resources to transcribe. If you are having a rapid, multi-person conversation, the raw audio chunks line up in a processing queue.
Finalization:
Once the AI finishes transcribing a chunk, the text is committed to the SQLite database alongside a timestamp, and the raw audio is compressed and saved so you can play it back later.
4. Understanding the Folder Structure
4. Understanding the Folder Structure
All of ScreenPipe's data lives locally on your machine (usually in a hidden directory like
~/.screenpipe
on Mac/Linux or in your AppData folder on Windows). You can determine what has been processed by looking at a few key areas in this directory:
The SQLite Database (
):
This is the master ledger. If text exists inside this database, it means the audio has been fully transcribed, diarized, and is "done."
The
or
folders:
This is where the compressed, raw audio files and JPEG screenshots are stored permanently. Think of this as the raw archive.
Temp Files:
If you see rapidly changing files, temporary
chunks, or locked database journals (like
), this indicates the system is actively processing a backlog of audio (the WIP stage). Once the engine catches up, these temporary chunk files are usually cleared out or moved to permanent storage, and the database updates.
Are you setting up ScreenPipe for a specific workflow (like summarizing meetings or tracking your daily tasks), or just exploring how the architecture works under the hood?
Sources
Sources
Good response
Bad response
Share and export
Copy
Show more options
Copy prompt
You said I see the files in ~/.screenpipe/data/ as LakyLak bose qc35 II (input)_2026-05-12_06-49-17.mp4 or MacBook Pro Microphone (input)_2026-05-12_12-17-23.mp4 or soundcore AeroClip (input)_2026-05-12_07-40-48.mp4 or System Audio (output)_2026-05-11_06-17-14.mp4. Are these used for anything after transcribing and storing in sqlite db?
You said
I see the files in ~/.screenpipe/data/ as LakyLak bose qc35 II (input)_2026-05-12_06-49-17.mp4 or MacBook Pro Microphone (input)_2026-05-12_12-17-23.mp4 or soundcore AeroClip (input)_2026-05-12_07-40-48.mp4 or System Audio (output)_2026-05-11_06-17-14.mp4. Are these used for anything after transcribing and storing in sqlite db?
Expand
Listen
Show thinking
Gemini said
Gemini said
Yes, those
.mp4
files are absolutely still used after the transcription process is complete. They are not temporary processing chunks; they act as your permanent media archive.
Here is exactly what those files are doing sitting in your
~/.screenpipe/data/
directory and how they interact with the rest of the system.
1. The "Time Machine" Playback
1. The "Time Machine" Playback
The primary reason ScreenPipe keeps these files is for audio playback. When Whisper transcribes your meetings or ambient audio, it writes the text into the
db.sqlite
database along with
precise timestamps and file path references
.
When you search your ScreenPipe history for a specific conversation, the UI doesn't just show you the text—it uses the file path stored in the database to pull up that specific
.mp4
file and starts playing it at the exact second the keyword was spoken. Without these files, you get the transcript, but you lose the ability to actually listen to the context, tone, or nuance of the original conversation.
2. Re-processing and Model Upgrades
2. Re-processing and Model Upgrades
Local LLMs and transcription models are improving rapidly. Right now, your system might be using a quantized Whisper model to balance CPU load with accuracy. If you later decide to run a heavier, more accurate model—or if a radically better speaker diarization algorithm is released—having the raw
.mp4
files allows you to re-process historical audio. Without the raw input files, your data is permanently locked to the quality of the transcription model you were running on May 12th.
3. The Source of Truth for Hallucinations
3. The Source of Truth for Hallucinations
Speech-to-text engines, even great ones, occasionally hallucinate or misinterpret technical jargon, overlapping voices, or background noise. If a database query returns a completely nonsensical sentence, that
.mp4
file is your fallback source of truth to verify what was actually said.
How the Database Views Them
How the Database Views Them
If you were to open up your
db.sqlite
and query the tables (e.g.,
audio_transcriptions
), you would see that the database treats the
~/.screenpipe/data/
folder essentially as a blob storage backend. The tables contain columns acting as soft foreign keys pointing to filenames like
soundcore AeroClip (input)_2026-05-12_07-40-48.mp4
.
What happens if you delete them?
If you manually
rm
those files to free up space on your Mac's SSD, ScreenPipe won't crash. The SQLite database will still hold all your searchable text. However, any attempt to play back the audio for those older transcripts will silently fail or throw a "file not found" error in the background logs.
Managing the Storage Footprint
Managing the Storage Footprint
Because ScreenPipe records continuously, this folder will inevitably grow over time.
Built-in Garbage Collection:
ScreenPipe usually handles this automatically based on retention settings (e.g., deleting media older than 30 days or when the folder hits a specific GB limit).
Archiving:
If you prefer to keep a permanent, searchable "life log" without eating up your local SSD space, you don't necessarily have to let ScreenPipe delete them. You could script a simple cron job to regularly
older
files to your massive 4-drive RAID 5 array, replacing the local files with symlinks. This would keep the database references intact while offloading the heavy media storage to your NAS.
Good response
Bad response
Share and export
Copy
Show more options
Copy prompt
Edit
You said yes I will do that. Is there a way to setup languages to transcription?
You said
yes I will do that. Is there a way to setup languages to transcription?
Listen
Show thinking
Gemini said
Gemini said
That is a solid plan. Offloading those older media files to your 4-drive RAID 5 setup (or even the secondary SSD-only Beelink NAS if you end up building it) will keep your local SSD lean while keeping your database searchable.
As for setting up languages for transcription,
yes, you can configure it
, but it helps to understand how ScreenPipe handles language natively first.
How the Transcription Engine Handles Language
How the Transcription Engine Handles Language
By default, ScreenPipe uses a
multilingual Whisper model
and sets the language configuration to
Auto-Detect
.
When a new audio chunk comes in, the model listens to the first few seconds of audio, guesses the language, and transcribes the rest of that chunk using that assumption.
The Advantage:
If you are watching a tutorial in English, and then an hour later you are in a Zoom call speaking Bulgarian, ScreenPipe will automatically switch gears without you having to touch the settings.
The Drawback:
Auto-detect can sometimes guess wrong, especially if there is heavy background noise, if someone starts a sentence with a foreign loanword, or if you are rapidly code-switching between Slovak and English in the same conversation. When it guesses wrong, it might try to force Slovak words into English spelling, resulting in gibberish.
How to Force a Specific Language
How to Force a Specific Language
If you want to ensure maximum accuracy and you know you will primarily be recording in one language for a specific period, you can explicitly set the language. This also slightly speeds up the processing because the model skips the detection phase.
If you are using the Desktop App UI:
Open the ScreenPipe settings.
Navigate to the
Audio
or
AI/Transcription
section.
Look for the
Whisper Language
or
Transcription Language
dropdown.
Change it from "Auto" to your specific language (e.g., English, Bulgarian, or Slovak).
If you are running ScreenPipe via CLI/Config:
You can modify your underlying configuration (usually found in
~/.screenpipe/pipe.json
or by passing a flag when starting the daemon) to force a specific language code. You would use the standard ISO language codes:
(Slovak)
(Bulgarian)
(English)
If you find yourself frequently jumping between those three languages, your best bet is to leave it on Auto-Detect and perhaps run a slightly larger, more accurate Whisper model (like Whisper 'Small' or 'Base' instead of 'Tiny') if your hardware has the headroom for it.
Good response
Bad response
Redo
Share and export
Copy
Show more options
Ask Gemini
Ask Gemini
Open upload file menu
Tools
Open mode picker
Pro
Microphone
Send message
Gemini is AI and can make mistakes, including about people.
Your privacy and Gemini Opens in a new window
Your privacy and Gemini
Opens in a new window
Summarize page
Summarize page
Screenpipe [archive.db · 763MB]
Screenpipe
[archive.db · 763MB]
Activity
Search
Audio
Work Report
Timetable
AI Summary
Date
12
/
05
/
2026
Calendar
AUDIO TRANSCRIPTIONS
09:28
MIC
That's it.
09:32
MIC
And now...
09:32
MIC...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Screenpipe — Archive","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Screenpipe — Archive","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.037898935,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"All docs · AFFiNE","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"All docs · AFFiNE","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.029587766,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"DXP4800PLUS-B5F8","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"DXP4800PLUS-B5F8","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.036901597,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.014960106,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Screenpipe — Archive","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Screenpipe — Archive","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.037898935,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.05651596,"top":0.18994413,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"SQLite Web: archive.db","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SQLite Web: archive.db","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.040724736,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SQLite Web: db.sqlite","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SQLite Web: db.sqlite","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.03756649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Claude","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Claude","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.012134309,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Manage extra usage for paid Claude plans | Claude Help Center","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Manage extra usage for paid Claude plans | Claude Help Center","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.1100399,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"2 TB in 25 MB/s - Google Search","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2 TB in 25 MB/s - Google Search","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.05668218,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.38068634,"width":0.06333112,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bitwarden","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.29321808,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.30518618,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Google Account: Lukáš Koválik (kovaliklukas@gmail.com)","depth":12,"bounds":{"left":0.3025266,"top":0.103751,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Main menu","depth":12,"bounds":{"left":0.07280585,"top":0.103751,"width":0.013297873,"height":0.031923383},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.08610372,"top":0.10454908,"width":0.028590426,"height":0.030327214},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"bounds":{"left":0.0887633,"top":0.10973663,"width":0.021941489,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New chat","depth":12,"bounds":{"left":0.2613032,"top":0.103751,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share conversation","depth":11,"bounds":{"left":0.27460107,"top":0.103751,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":12,"bounds":{"left":0.28789893,"top":0.103751,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.068484046,"top":0.14764565,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.068484046,"top":0.15003991,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":21,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said Exaplin me: how does the ScreenPipe audio recording work? Where did it start? How is it getting transferred and transcribed? Is there some work in progress stage? How can I see from the folder structure what has been done and what hasn't been done?","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Exaplin me: how does the ScreenPipe audio recording work? Where did it start? How is it getting transferred and transcribed? Is there some work in progress stage? How can I see from the folder structure what has been done and what hasn't been done?","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Listen","depth":24,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Show thinking","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Gemini said","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a breakdown of how ScreenPipe handles audio recording, from the moment it captures sound to how it organizes the data on your machine.","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ScreenPipe is fundamentally designed to be a","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"100% local, privacy-first","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"application, meaning the vast majority of its processing happens directly on your hardware without relying on the cloud.","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"View source details. Opens side panel.","depth":27,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"1. Where It Starts: The Capture Layer","depth":26,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Where It Starts: The Capture Layer","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The process begins locally by tapping directly into your operating system’s audio routing. ScreenPipe simultaneously captures two separate audio streams:","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"System Audio:","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Everything your computer outputs (e.g., the voices of other people in a Zoom or Teams meeting, a YouTube video, or Spotify).","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Microphone Input:","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Everything you say into your mic.","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"View source details. Opens side panel.","depth":29,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Because ScreenPipe acts as a 24/7 background process, it continuously listens to these streams and breaks the continuous audio into manageable chunks to prepare them for the next stage.","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. How It Gets Transferred and Transcribed","depth":26,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. How It Gets Transferred and Transcribed","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Because ScreenPipe prioritizes local processing, the \"transfer\" step is incredibly short. The audio data never leaves your machine unless you explicitly set up a cloud plugin.","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is the transcription pipeline:","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Engine:","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The raw audio chunks are fed into a local speech-to-text model.","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"By default, ScreenPipe uses","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"OpenAI Whisper","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"running locally on your hardware. (Users can also configure it to use cloud providers like Deepgram if they need faster processing, but local Whisper is the standard).","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"View source details. Opens side panel.","depth":29,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Diarization:","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"As it transcribes the text, the engine also performs \"diarization\"—a technical term for speaker identification.","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"It analyzes the audio to distinguish between your voice and the voices of others, labeling who said what.","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"View source details. Opens side panel.","depth":29,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Storage:","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The final transcribed text is then indexed into a local","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SQLite database","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". This database uses Full-Text Search (FTS5), which is what allows you to instantly search for a phrase you heard in a meeting three weeks ago.","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. The \"Work in Progress\" (WIP) Stage","depth":26,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. The \"Work in Progress\" (WIP) Stage","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"There is a slight bottleneck between","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"hearing","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"the audio and","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"saving","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"the text, which acts as the WIP stage:","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Buffering:","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Audio is recorded into a temporary buffer in your system's RAM or written as temporary chunk files on your disk.","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Processing Queue:","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Whisper requires CPU/GPU resources to transcribe. If you are having a rapid, multi-person conversation, the raw audio chunks line up in a processing queue.","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Finalization:","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Once the AI finishes transcribing a chunk, the text is committed to the SQLite database alongside a timestamp, and the raw audio is compressed and saved so you can play it back later.","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Understanding the Folder Structure","depth":26,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Understanding the Folder Structure","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"All of ScreenPipe's data lives locally on your machine (usually in a hidden directory like","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"~/.screenpipe","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on Mac/Linux or in your AppData folder on Windows). You can determine what has been processed by looking at a few key areas in this directory:","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The SQLite Database (","depth":29,"bounds":{"left":0.09142287,"top":0.0,"width":0.05817819,"height":0.016360734},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"):","depth":29,"bounds":{"left":0.17885639,"top":0.0,"width":0.0034906915,"height":0.016360734},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This is the master ledger. If text exists inside this database, it means the audio has been fully transcribed, diarized, and is \"done.\"","depth":29,"bounds":{"left":0.09142287,"top":0.0,"width":0.21010639,"height":0.037110932},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":29,"bounds":{"left":0.09142287,"top":0.0,"width":0.011303191,"height":0.016360734},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":29,"bounds":{"left":0.120678194,"top":0.0,"width":0.008144947,"height":0.016360734},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"folders:","depth":29,"bounds":{"left":0.14960106,"top":0.0,"width":0.020944148,"height":0.016360734},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This is where the compressed, raw audio files and JPEG screenshots are stored permanently. Think of this as the raw archive.","depth":29,"bounds":{"left":0.09142287,"top":0.0,"width":0.21542554,"height":0.037110932},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Temp Files:","depth":29,"bounds":{"left":0.09142287,"top":0.029928172,"width":0.028922873,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If you see rapidly changing files, temporary","depth":29,"bounds":{"left":0.12034574,"top":0.029928172,"width":0.106715426,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"chunks, or locked database journals (like","depth":29,"bounds":{"left":0.09142287,"top":0.029928172,"width":0.21991356,"height":0.037110932},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"), this indicates the system is actively processing a backlog of audio (the WIP stage). Once the engine catches up, these temporary chunk files are usually cleared out or moved to permanent storage, and the database updates.","depth":29,"bounds":{"left":0.09142287,"top":0.050678372,"width":0.22174202,"height":0.057861134},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Are you setting up ScreenPipe for a specific workflow (like summarizing meetings or tracking your daily tasks), or just exploring how the architecture works under the hood?","depth":27,"bounds":{"left":0.0787899,"top":0.13288109,"width":0.22456782,"height":0.037110932},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Sources","depth":26,"bounds":{"left":0.0787899,"top":0.18715084,"width":0.036402926,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sources","depth":28,"bounds":{"left":0.091755316,"top":0.19592977,"width":0.017785905,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":24,"bounds":{"left":0.075465426,"top":0.23184358,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":24,"bounds":{"left":0.08610372,"top":0.23184358,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share and export","depth":23,"bounds":{"left":0.09674202,"top":0.23184358,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":24,"bounds":{"left":0.107380316,"top":0.23184358,"width":0.010638298,"height":0.025538707},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":23,"bounds":{"left":0.11801862,"top":0.23184358,"width":0.010638298,"height":0.025538707},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":21,"bounds":{"left":0.14029256,"top":0.292498,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said I see the files in ~/.screenpipe/data/ as LakyLak bose qc35 II (input)_2026-05-12_06-49-17.mp4 or MacBook Pro Microphone (input)_2026-05-12_12-17-23.mp4 or soundcore AeroClip (input)_2026-05-12_07-40-48.mp4 or System Audio (output)_2026-05-11_06-17-14.mp4. Are these used for anything after transcribing and storing in sqlite db?","depth":21,"bounds":{"left":0.16023937,"top":0.30207503,"width":0.13696809,"height":0.09577015},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"bounds":{"left":0.068484046,"top":0.3028731,"width":0.019946808,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I see the files in ~/.screenpipe/data/ as LakyLak bose qc35 II (input)_2026-05-12_06-49-17.mp4 or MacBook Pro Microphone (input)_2026-05-12_12-17-23.mp4 or soundcore AeroClip (input)_2026-05-12_07-40-48.mp4 or System Audio (output)_2026-05-11_06-17-14.mp4. Are these used for anything after transcribing and storing in sqlite db?","depth":23,"bounds":{"left":0.16023937,"top":0.30367118,"width":0.13663563,"height":0.13128492},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"bounds":{"left":0.29720744,"top":0.30207503,"width":0.013297873,"height":0.031923383},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Listen","depth":24,"bounds":{"left":0.3025266,"top":0.42976856,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Show thinking","depth":28,"bounds":{"left":0.09208777,"top":0.43216282,"width":0.030917553,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Gemini said","depth":23,"bounds":{"left":0.08976064,"top":0.4736632,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":24,"bounds":{"left":0.08976064,"top":0.47565842,"width":0.04105718,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Yes, those","depth":27,"bounds":{"left":0.0787899,"top":0.47685555,"width":0.025930852,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".mp4","depth":28,"bounds":{"left":0.106715426,"top":0.47805268,"width":0.011136968,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"files are absolutely still used after the transcription process is complete. They are not temporary processing chunks; they act as your permanent media archive.","depth":27,"bounds":{"left":0.0787899,"top":0.47685555,"width":0.23005319,"height":0.037110932},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is exactly what those files are doing sitting in your","depth":27,"bounds":{"left":0.0787899,"top":0.5271349,"width":0.1341423,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"~/.screenpipe/data/","depth":28,"bounds":{"left":0.21492687,"top":0.528332,"width":0.053025264,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"directory and how they interact with the rest of the system.","depth":27,"bounds":{"left":0.0787899,"top":0.5271349,"width":0.22623006,"height":0.037110932},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. The \"Time Machine\" Playback","depth":26,"bounds":{"left":0.0787899,"top":0.5885874,"width":0.234375,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. The \"Time Machine\" Playback","depth":27,"bounds":{"left":0.0787899,"top":0.59018356,"width":0.08194814,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The primary reason ScreenPipe keeps these files is for audio playback. When Whisper transcribes your meetings or ambient audio, it writes the text into the","depth":27,"bounds":{"left":0.0787899,"top":0.61652035,"width":0.20761304,"height":0.037110932},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"db.sqlite","depth":28,"bounds":{"left":0.24883644,"top":0.63846767,"width":0.025099734,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"database along with","depth":27,"bounds":{"left":0.0787899,"top":0.63727057,"width":0.22240691,"height":0.037110932},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"precise timestamps and file path references","depth":27,"bounds":{"left":0.105053194,"top":0.65802073,"width":0.11419548,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":27,"bounds":{"left":0.21924867,"top":0.65802073,"width":0.0013297872,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"When you search your ScreenPipe history for a specific conversation, the UI doesn't just show you the text—it uses the file path stored in the database to pull up that specific","depth":27,"bounds":{"left":0.0787899,"top":0.6875499,"width":0.22755983,"height":0.037110932},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".mp4","depth":28,"bounds":{"left":0.27293882,"top":0.7094972,"width":0.011136968,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"file and starts playing it at the exact second the keyword was spoken. Without these files, you get the transcript, but you lose the ability to actually listen to the context, tone, or nuance of the original conversation.","depth":27,"bounds":{"left":0.0787899,"top":0.70830005,"width":0.23354389,"height":0.07861133},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Re-processing and Model Upgrades","depth":26,"bounds":{"left":0.0787899,"top":0.811253,"width":0.234375,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Re-processing and Model Upgrades","depth":27,"bounds":{"left":0.0787899,"top":0.81284916,"width":0.09840426,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Local LLMs and transcription models are improving rapidly. Right now, your system might be using a quantized Whisper model to balance CPU load with accuracy. If you later decide to run a heavier, more accurate model—or if a radically better speaker diarization algorithm is released—having the raw","depth":27,"bounds":{"left":0.0787899,"top":0.83918595,"width":0.23254654,"height":0.07861133},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".mp4","depth":28,"bounds":{"left":0.117519945,"top":0.90263367,"width":0.011136968,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"files allows you to re-process historical audio. Without the raw input files, your data is permanently locked to the quality of the transcription model you were running on May 12th.","depth":27,"bounds":{"left":0.0787899,"top":0.90143657,"width":0.23038563,"height":0.057861134},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. The Source of Truth for Hallucinations","depth":26,"bounds":{"left":0.0787899,"top":0.98363924,"width":0.234375,"height":0.01636076},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. The Source of Truth for Hallucinations","depth":27,"bounds":{"left":0.0787899,"top":0.98523545,"width":0.10405585,"height":0.014764547},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Speech-to-text engines, even great ones, occasionally hallucinate or misinterpret technical jargon, overlapping voices, or background noise. If a database query returns a completely nonsensical sentence, that","depth":27,"bounds":{"left":0.0787899,"top":1.0,"width":0.2209109,"height":-0.011572242},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".mp4","depth":28,"bounds":{"left":0.14660904,"top":1.0,"width":0.011303191,"height":-0.05426979},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"file is your fallback source of truth to verify what was actually said.","depth":27,"bounds":{"left":0.0787899,"top":1.0,"width":0.23121676,"height":-0.05307257},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"How the Database Views Them","depth":26,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"How the Database Views Them","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If you were to open up your","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"db.sqlite","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and query the tables (e.g.,","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"audio_transcriptions","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"), you would see that the database treats the","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"~/.screenpipe/data/","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"folder essentially as a blob storage backend. The tables contain columns acting as soft foreign keys pointing to filenames like","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"soundcore AeroClip (input)_2026-05-12_07-40-48.mp4","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"What happens if you delete them?","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If you manually","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rm","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"those files to free up space on your Mac's SSD, ScreenPipe won't crash. The SQLite database will still hold all your searchable text. However, any attempt to play back the audio for those older transcripts will silently fail or throw a \"file not found\" error in the background logs.","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Managing the Storage Footprint","depth":26,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Managing the Storage Footprint","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Because ScreenPipe records continuously, this folder will inevitably grow over time.","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Built-in Garbage Collection:","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ScreenPipe usually handles this automatically based on retention settings (e.g., deleting media older than 30 days or when the folder hits a specific GB limit).","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Archiving:","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If you prefer to keep a permanent, searchable \"life log\" without eating up your local SSD space, you don't necessarily have to let ScreenPipe delete them. You could script a simple cron job to regularly","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"older","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"files to your massive 4-drive RAID 5 array, replacing the local files with symlinks. This would keep the database references intact while offloading the heavy media storage to your NAS.","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":24,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":24,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share and export","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":24,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":23,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":21,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said yes I will do that. Is there a way to setup languages to transcription?","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"yes I will do that. Is there a way to setup languages to transcription?","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Listen","depth":24,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Show thinking","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Gemini said","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"That is a solid plan. Offloading those older media files to your 4-drive RAID 5 setup (or even the secondary SSD-only Beelink NAS if you end up building it) will keep your local SSD lean while keeping your database searchable.","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"As for setting up languages for transcription,","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"yes, you can configure it","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", but it helps to understand how ScreenPipe handles language natively first.","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"How the Transcription Engine Handles Language","depth":26,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"How the Transcription Engine Handles Language","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"By default, ScreenPipe uses a","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"multilingual Whisper model","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and sets the language configuration to","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Auto-Detect","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"When a new audio chunk comes in, the model listens to the first few seconds of audio, guesses the language, and transcribes the rest of that chunk using that assumption.","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Advantage:","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If you are watching a tutorial in English, and then an hour later you are in a Zoom call speaking Bulgarian, ScreenPipe will automatically switch gears without you having to touch the settings.","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Drawback:","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Auto-detect can sometimes guess wrong, especially if there is heavy background noise, if someone starts a sentence with a foreign loanword, or if you are rapidly code-switching between Slovak and English in the same conversation. When it guesses wrong, it might try to force Slovak words into English spelling, resulting in gibberish.","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"How to Force a Specific Language","depth":26,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"How to Force a Specific Language","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If you want to ensure maximum accuracy and you know you will primarily be recording in one language for a specific period, you can explicitly set the language. This also slightly speeds up the processing because the model skips the detection phase.","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If you are using the Desktop App UI:","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open the ScreenPipe settings.","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Navigate to the","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Audio","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AI/Transcription","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"section.","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Look for the","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Whisper Language","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Transcription Language","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"dropdown.","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Change it from \"Auto\" to your specific language (e.g., English, Bulgarian, or Slovak).","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If you are running ScreenPipe via CLI/Config:","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You can modify your underlying configuration (usually found in","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"~/.screenpipe/pipe.json","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or by passing a flag when starting the daemon) to force a specific language code. You would use the standard ISO language codes:","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Slovak)","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(Bulgarian)","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(English)","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If you find yourself frequently jumping between those three languages, your best bet is to leave it on Auto-Detect and perhaps run a slightly larger, more accurate Whisper model (like Whisper 'Small' or 'Base' instead of 'Tiny') if your hardware has the headroom for it.","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":23,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Redo","depth":23,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Share and export","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":23,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":22,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXTextArea","text":"Ask Gemini","depth":20,"bounds":{"left":0.08211436,"top":0.83439744,"width":0.22573139,"height":0.01915403},"on_screen":true,"value":"Ask Gemini","help_text":"","role_description":"text entry area","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Gemini","depth":21,"bounds":{"left":0.08211436,"top":0.8347965,"width":0.030086435,"height":0.018355945},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open upload file menu","depth":20,"bounds":{"left":0.078125,"top":0.87031126,"width":0.013297873,"height":0.031923383},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tools","depth":18,"bounds":{"left":0.094082445,"top":0.87031126,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open mode picker","depth":20,"bounds":{"left":0.27044547,"top":0.867917,"width":0.026097074,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pro","depth":23,"bounds":{"left":0.2757646,"top":0.87669593,"width":0.007480053,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Microphone","depth":19,"bounds":{"left":0.29853722,"top":0.867917,"width":0.013297873,"height":0.031923383},"on_screen":true,"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Send message","depth":19,"bounds":{"left":0.30485374,"top":0.8671189,"width":0.013962766,"height":0.033519555},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini is AI and can make mistakes, including about people.","depth":17,"bounds":{"left":0.11702128,"top":0.92178774,"width":0.11170213,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Your privacy and Gemini Opens in a new window","depth":17,"bounds":{"left":0.2287234,"top":0.92178774,"width":0.044215426,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Your privacy and Gemini","depth":18,"bounds":{"left":0.2287234,"top":0.92178774,"width":0.044215426,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Opens in a new window","depth":19,"bounds":{"left":0.068484046,"top":0.92098963,"width":0.043218084,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Summarize page","depth":7,"bounds":{"left":0.07413564,"top":0.95730245,"width":0.053523935,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Summarize page","depth":9,"bounds":{"left":0.07978723,"top":0.96249,"width":0.042220745,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Screenpipe [archive.db · 763MB]","depth":7,"bounds":{"left":0.33061835,"top":0.061452515,"width":0.06067154,"height":0.017956903},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Screenpipe","depth":8,"bounds":{"left":0.33061835,"top":0.06304868,"width":0.027759308,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[archive.db · 763MB]","depth":9,"bounds":{"left":0.35970744,"top":0.06703911,"width":0.03158245,"height":0.009976057},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Activity","depth":7,"bounds":{"left":0.39594415,"top":0.059856344,"width":0.024933511,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Search","depth":7,"bounds":{"left":0.42154256,"top":0.059856344,"width":0.023603724,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Audio","depth":7,"bounds":{"left":0.44581118,"top":0.059856344,"width":0.020944148,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false},{"role":"AXButton","text":"Work Report","depth":7,"bounds":{"left":0.46742022,"top":0.059856344,"width":0.03507314,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Timetable","depth":7,"bounds":{"left":0.5031583,"top":0.059856344,"width":0.029753989,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Summary","depth":7,"bounds":{"left":0.53357714,"top":0.059856344,"width":0.034075797,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Date","depth":8,"bounds":{"left":0.93866354,"top":0.0650439,"width":0.008144947,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12","depth":9,"bounds":{"left":0.95545214,"top":0.06464485,"width":0.0048204786,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":8,"bounds":{"left":0.96127,"top":0.06464485,"width":0.0023271276,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"05","depth":9,"bounds":{"left":0.9645944,"top":0.06464485,"width":0.0048204786,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":8,"bounds":{"left":0.97041225,"top":0.06464485,"width":0.002493351,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2026","depth":9,"bounds":{"left":0.97390294,"top":0.06464485,"width":0.009474734,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Calendar","depth":8,"bounds":{"left":0.9847075,"top":0.0650439,"width":0.0051529254,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"AUDIO TRANSCRIPTIONS","depth":9,"bounds":{"left":0.45761302,"top":0.11532322,"width":0.04637633,"height":0.009976057},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"09:28","depth":10,"bounds":{"left":0.4582779,"top":0.14086193,"width":0.010472074,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"MIC","depth":10,"bounds":{"left":0.4737367,"top":0.14405426,"width":0.0066489363,"height":0.009976057},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"That's it.","depth":10,"bounds":{"left":0.48537233,"top":0.14126097,"width":0.018783245,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"09:32","depth":10,"bounds":{"left":0.4582779,"top":0.16959298,"width":0.010305851,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"MIC","depth":10,"bounds":{"left":0.47357047,"top":0.17278531,"width":0.0066489363,"height":0.009976057},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"And now...","depth":10,"bounds":{"left":0.48520613,"top":0.16999201,"width":0.02244016,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"09:32","depth":10,"bounds":{"left":0.4582779,"top":0.19832402,"width":0.010305851,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"MIC","depth":10,"bounds":{"left":0.47357047,"top":0.20151636,"width":0.0066489363,"height":0.009976057},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-9089111667168672852
|
8632612938731629533
|
click
|
accessibility
|
NULL
|
Screenpipe — Archive
Screenpipe — Archive
All docs Screenpipe — Archive
Screenpipe — Archive
All docs · AFFiNE
All docs · AFFiNE
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
New Tab
New Tab
Screenpipe — Archive
Screenpipe — Archive
Close tab
SQLite Web: archive.db
SQLite Web: archive.db
SQLite Web: db.sqlite
SQLite Web: db.sqlite
Claude
Claude
Manage extra usage for paid Claude plans | Claude Help Center
Manage extra usage for paid Claude plans | Claude Help Center
2 TB in 25 MB/s - Google Search
2 TB in 25 MB/s - Google Search
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
AI Chat settings
Close
Google Account: Lukáš Koválik ([EMAIL])
Main menu
New chat
Gemini
New chat
Share conversation
Open menu for conversation actions.
Conversation with Gemini
Conversation with Gemini
Copy prompt
You said Exaplin me: how does the ScreenPipe audio recording work? Where did it start? How is it getting transferred and transcribed? Is there some work in progress stage? How can I see from the folder structure what has been done and what hasn't been done?
You said
Exaplin me: how does the ScreenPipe audio recording work? Where did it start? How is it getting transferred and transcribed? Is there some work in progress stage? How can I see from the folder structure what has been done and what hasn't been done?
Listen
Show thinking
Gemini said
Gemini said
Here is a breakdown of how ScreenPipe handles audio recording, from the moment it captures sound to how it organizes the data on your machine.
ScreenPipe is fundamentally designed to be a
100% local, privacy-first
application, meaning the vast majority of its processing happens directly on your hardware without relying on the cloud.
View source details. Opens side panel.
1. Where It Starts: The Capture Layer
1. Where It Starts: The Capture Layer
The process begins locally by tapping directly into your operating system’s audio routing. ScreenPipe simultaneously captures two separate audio streams:
System Audio:
Everything your computer outputs (e.g., the voices of other people in a Zoom or Teams meeting, a YouTube video, or Spotify).
Microphone Input:
Everything you say into your mic.
View source details. Opens side panel.
Because ScreenPipe acts as a 24/7 background process, it continuously listens to these streams and breaks the continuous audio into manageable chunks to prepare them for the next stage.
2. How It Gets Transferred and Transcribed
2. How It Gets Transferred and Transcribed
Because ScreenPipe prioritizes local processing, the "transfer" step is incredibly short. The audio data never leaves your machine unless you explicitly set up a cloud plugin.
Here is the transcription pipeline:
The Engine:
The raw audio chunks are fed into a local speech-to-text model.
By default, ScreenPipe uses
OpenAI Whisper
running locally on your hardware. (Users can also configure it to use cloud providers like Deepgram if they need faster processing, but local Whisper is the standard).
View source details. Opens side panel.
Diarization:
As it transcribes the text, the engine also performs "diarization"—a technical term for speaker identification.
It analyzes the audio to distinguish between your voice and the voices of others, labeling who said what.
View source details. Opens side panel.
Storage:
The final transcribed text is then indexed into a local
SQLite database
. This database uses Full-Text Search (FTS5), which is what allows you to instantly search for a phrase you heard in a meeting three weeks ago.
3. The "Work in Progress" (WIP) Stage
3. The "Work in Progress" (WIP) Stage
There is a slight bottleneck between
hearing
the audio and
saving
the text, which acts as the WIP stage:
Buffering:
Audio is recorded into a temporary buffer in your system's RAM or written as temporary chunk files on your disk.
Processing Queue:
Whisper requires CPU/GPU resources to transcribe. If you are having a rapid, multi-person conversation, the raw audio chunks line up in a processing queue.
Finalization:
Once the AI finishes transcribing a chunk, the text is committed to the SQLite database alongside a timestamp, and the raw audio is compressed and saved so you can play it back later.
4. Understanding the Folder Structure
4. Understanding the Folder Structure
All of ScreenPipe's data lives locally on your machine (usually in a hidden directory like
~/.screenpipe
on Mac/Linux or in your AppData folder on Windows). You can determine what has been processed by looking at a few key areas in this directory:
The SQLite Database (
):
This is the master ledger. If text exists inside this database, it means the audio has been fully transcribed, diarized, and is "done."
The
or
folders:
This is where the compressed, raw audio files and JPEG screenshots are stored permanently. Think of this as the raw archive.
Temp Files:
If you see rapidly changing files, temporary
chunks, or locked database journals (like
), this indicates the system is actively processing a backlog of audio (the WIP stage). Once the engine catches up, these temporary chunk files are usually cleared out or moved to permanent storage, and the database updates.
Are you setting up ScreenPipe for a specific workflow (like summarizing meetings or tracking your daily tasks), or just exploring how the architecture works under the hood?
Sources
Sources
Good response
Bad response
Share and export
Copy
Show more options
Copy prompt
You said I see the files in ~/.screenpipe/data/ as LakyLak bose qc35 II (input)_2026-05-12_06-49-17.mp4 or MacBook Pro Microphone (input)_2026-05-12_12-17-23.mp4 or soundcore AeroClip (input)_2026-05-12_07-40-48.mp4 or System Audio (output)_2026-05-11_06-17-14.mp4. Are these used for anything after transcribing and storing in sqlite db?
You said
I see the files in ~/.screenpipe/data/ as LakyLak bose qc35 II (input)_2026-05-12_06-49-17.mp4 or MacBook Pro Microphone (input)_2026-05-12_12-17-23.mp4 or soundcore AeroClip (input)_2026-05-12_07-40-48.mp4 or System Audio (output)_2026-05-11_06-17-14.mp4. Are these used for anything after transcribing and storing in sqlite db?
Expand
Listen
Show thinking
Gemini said
Gemini said
Yes, those
.mp4
files are absolutely still used after the transcription process is complete. They are not temporary processing chunks; they act as your permanent media archive.
Here is exactly what those files are doing sitting in your
~/.screenpipe/data/
directory and how they interact with the rest of the system.
1. The "Time Machine" Playback
1. The "Time Machine" Playback
The primary reason ScreenPipe keeps these files is for audio playback. When Whisper transcribes your meetings or ambient audio, it writes the text into the
db.sqlite
database along with
precise timestamps and file path references
.
When you search your ScreenPipe history for a specific conversation, the UI doesn't just show you the text—it uses the file path stored in the database to pull up that specific
.mp4
file and starts playing it at the exact second the keyword was spoken. Without these files, you get the transcript, but you lose the ability to actually listen to the context, tone, or nuance of the original conversation.
2. Re-processing and Model Upgrades
2. Re-processing and Model Upgrades
Local LLMs and transcription models are improving rapidly. Right now, your system might be using a quantized Whisper model to balance CPU load with accuracy. If you later decide to run a heavier, more accurate model—or if a radically better speaker diarization algorithm is released—having the raw
.mp4
files allows you to re-process historical audio. Without the raw input files, your data is permanently locked to the quality of the transcription model you were running on May 12th.
3. The Source of Truth for Hallucinations
3. The Source of Truth for Hallucinations
Speech-to-text engines, even great ones, occasionally hallucinate or misinterpret technical jargon, overlapping voices, or background noise. If a database query returns a completely nonsensical sentence, that
.mp4
file is your fallback source of truth to verify what was actually said.
How the Database Views Them
How the Database Views Them
If you were to open up your
db.sqlite
and query the tables (e.g.,
audio_transcriptions
), you would see that the database treats the
~/.screenpipe/data/
folder essentially as a blob storage backend. The tables contain columns acting as soft foreign keys pointing to filenames like
soundcore AeroClip (input)_2026-05-12_07-40-48.mp4
.
What happens if you delete them?
If you manually
rm
those files to free up space on your Mac's SSD, ScreenPipe won't crash. The SQLite database will still hold all your searchable text. However, any attempt to play back the audio for those older transcripts will silently fail or throw a "file not found" error in the background logs.
Managing the Storage Footprint
Managing the Storage Footprint
Because ScreenPipe records continuously, this folder will inevitably grow over time.
Built-in Garbage Collection:
ScreenPipe usually handles this automatically based on retention settings (e.g., deleting media older than 30 days or when the folder hits a specific GB limit).
Archiving:
If you prefer to keep a permanent, searchable "life log" without eating up your local SSD space, you don't necessarily have to let ScreenPipe delete them. You could script a simple cron job to regularly
older
files to your massive 4-drive RAID 5 array, replacing the local files with symlinks. This would keep the database references intact while offloading the heavy media storage to your NAS.
Good response
Bad response
Share and export
Copy
Show more options
Copy prompt
Edit
You said yes I will do that. Is there a way to setup languages to transcription?
You said
yes I will do that. Is there a way to setup languages to transcription?
Listen
Show thinking
Gemini said
Gemini said
That is a solid plan. Offloading those older media files to your 4-drive RAID 5 setup (or even the secondary SSD-only Beelink NAS if you end up building it) will keep your local SSD lean while keeping your database searchable.
As for setting up languages for transcription,
yes, you can configure it
, but it helps to understand how ScreenPipe handles language natively first.
How the Transcription Engine Handles Language
How the Transcription Engine Handles Language
By default, ScreenPipe uses a
multilingual Whisper model
and sets the language configuration to
Auto-Detect
.
When a new audio chunk comes in, the model listens to the first few seconds of audio, guesses the language, and transcribes the rest of that chunk using that assumption.
The Advantage:
If you are watching a tutorial in English, and then an hour later you are in a Zoom call speaking Bulgarian, ScreenPipe will automatically switch gears without you having to touch the settings.
The Drawback:
Auto-detect can sometimes guess wrong, especially if there is heavy background noise, if someone starts a sentence with a foreign loanword, or if you are rapidly code-switching between Slovak and English in the same conversation. When it guesses wrong, it might try to force Slovak words into English spelling, resulting in gibberish.
How to Force a Specific Language
How to Force a Specific Language
If you want to ensure maximum accuracy and you know you will primarily be recording in one language for a specific period, you can explicitly set the language. This also slightly speeds up the processing because the model skips the detection phase.
If you are using the Desktop App UI:
Open the ScreenPipe settings.
Navigate to the
Audio
or
AI/Transcription
section.
Look for the
Whisper Language
or
Transcription Language
dropdown.
Change it from "Auto" to your specific language (e.g., English, Bulgarian, or Slovak).
If you are running ScreenPipe via CLI/Config:
You can modify your underlying configuration (usually found in
~/.screenpipe/pipe.json
or by passing a flag when starting the daemon) to force a specific language code. You would use the standard ISO language codes:
(Slovak)
(Bulgarian)
(English)
If you find yourself frequently jumping between those three languages, your best bet is to leave it on Auto-Detect and perhaps run a slightly larger, more accurate Whisper model (like Whisper 'Small' or 'Base' instead of 'Tiny') if your hardware has the headroom for it.
Good response
Bad response
Redo
Share and export
Copy
Show more options
Ask Gemini
Ask Gemini
Open upload file menu
Tools
Open mode picker
Pro
Microphone
Send message
Gemini is AI and can make mistakes, including about people.
Your privacy and Gemini Opens in a new window
Your privacy and Gemini
Opens in a new window
Summarize page
Summarize page
Screenpipe [archive.db · 763MB]
Screenpipe
[archive.db · 763MB]
Activity
Search
Audio
Work Report
Timetable
AI Summary
Date
12
/
05
/
2026
Calendar
AUDIO TRANSCRIPTIONS
09:28
MIC
That's it.
09:32
MIC
And now...
09:32
MIC...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
82286
|
2853
|
22
|
2026-05-28T09:24:23.172781+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779960263172_m2.jpg...
|
Firefox
|
Iliyana Netseva at 27/05/2026, 18:48:18 - Session Iliyana Netseva at 27/05/2026, 18:48:18 - Session Replay - LogRocket — Work...
|
1
|
app.logrocket.com/ponxaf/platform-eu/s/6-019e6a1f- app.logrocket.com/ponxaf/platform-eu/s/6-019e6a1f-49d8-7739-a3ff-19380383e3ed/0?dashboardID=NaN&filterIntent=%257B%2522type%2522%253A%2522all%2522%252C%2522children%2522%253A%255B%257B%2522level%2522%253A%2522session%2522%252C%2522type%2522%253A%2522email%2522%252C%2522where%2522%253A%257B%2522type%2522%253A%2522all%2522%252C%2522children%2522%253A%255B%257B%2522type%2522%253A%2522email%2522%252C%2522operator%2522%253A%2522IS%2522%252C%2522values%2522%253A%255B%2522iliyana.netseva%2540jiminny.com%2522%255D%257D%255D%257D%257D%255D%257D&fromTab=&network_id=12-xhr-9&network_root_tab_id=06da3165-4fdd-4000-acdd-961a62018343&network_tab_id=06da3165-4fdd-4000-acdd-961a62018343&persistForm=true&t=1779897946748.365...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix missing header by LakyLak · Pull Request #12136 · jiminny/app
JY-20915 fix missing header by LakyLak · Pull Request #12136 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny Mail
Jiminny Mail
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Unnamed Group
Jiminny
Jiminny
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Sona Subramanian at 27/05/2026, 17:46:08 - Session Replay - LogRocket
Sona Subramanian at 27/05/2026, 17:46:08 - Session Replay - LogRocket
Iliyana Netseva at 27/05/2026, 18:48:18 - Session Replay - LogRocket
Iliyana Netseva at 27/05/2026, 18:48:18 - Session Replay - LogRocket
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
https://app.jiminny.eu/ondemand?user_id[]=714a31fa-4838-4c2c-ae0b-ac9f1f5fa600&sequence_number=32
https://app.jiminny.eu/ondemand?user_id[]=714a31fa-4838-4c2c-ae0b-ac9f1f5fa600&sequence_number=32
https://app.jiminny.eu
/ondemand
?user_id[]=714a31fa-4838-4c2c-ae0b-ac9f1f5fa600&sequence_number=32
Network
Network
Performance
Performance
Filter network entries (e.g. status>=400 method:POST /user/)
17:49min
18:12min
18:35min
18:58min
19:22min
19:45min
20:08min
20:31min
20:54min
21:17min
21:40min
22:03min
22:26min
Status
Status
Method
Method
URL
URL
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/component-css-class-Du_nf4Gy.js
https://app.jiminny.eu/vue-assets/assets/component-css-class-Du_nf4Gy.js
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/vue-mq-bh4L87Tr.css
https://app.jiminny.eu/vue-assets/assets/vue-mq-bh4L87Tr.css
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/BuildInfo-CxNQv_OV.css
https://app.jiminny.eu/vue-assets/assets/BuildInfo-CxNQv_OV.css
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/BuildInfo-BqXdxd90.js
https://app.jiminny.eu/vue-assets/assets/BuildInfo-BqXdxd90.js
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/vue-mq-BT4SrCVV.js
https://app.jiminny.eu/vue-assets/assets/vue-mq-BT4SrCVV.js
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/PrimaryButton-CJj2FzKS.css
https://app.jiminny.eu/vue-assets/assets/PrimaryButton-CJj2FzKS.css
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/KioskBanner-Dj4CHp1p.js
https://app.jiminny.eu/vue-assets/assets/KioskBanner-Dj4CHp1p.js
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/PrimaryButton-CQICoCWJ.js
https://app.jiminny.eu/vue-assets/assets/PrimaryButton-CQICoCWJ.js
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/KioskBanner-xU3FRd8g.css
https://app.jiminny.eu/vue-assets/assets/KioskBanner-xU3FRd8g.css
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/LogoLong100-ByQTvUY3.css
https://app.jiminny.eu/vue-assets/assets/LogoLong100-ByQTvUY3.css
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/lastFilters-C6MahU6o.js
https://app.jiminny.eu/vue-assets/assets/lastFilters-C6MahU6o.js
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/LogoShort100-lnFxbZVL.js
https://app.jiminny.eu/vue-assets/assets/LogoShort100-lnFxbZVL.js
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/LogoLong100-C4rZ7qiD.js
https://app.jiminny.eu/vue-assets/assets/LogoLong100-C4rZ7qiD.js
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/snackbar-CS_iqvrv.css
https://app.jiminny.eu/vue-assets/assets/snackbar-CS_iqvrv.css
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/logged-in-layout-BjhUQA_l.css
https://app.jiminny.eu/vue-assets/assets/logged-in-layout-BjhUQA_l.css
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/liquor-tree-CB5oh8v1.css
https://app.jiminny.eu/vue-assets/assets/liquor-tree-CB5oh8v1.css
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/snackbar-D-kpc2T1.js
https://app.jiminny.eu/vue-assets/assets/snackbar-D-kpc2T1.js
GET
GET
https://app.jiminny.eu/vue-assets/assets/url-messenger-DzeACBZw.js
https://app.jiminny.eu/vue-assets/assets/url-messenger-DzeACBZw.js
GET
GET
https://app.jiminny.eu/api/v1/activity/search?status%5B%5D=completed&sort_by=dateHeld&sort_direction=desc&exclude%5B%5D=stats&only_recorded=1&user_id%5B%5D=714a31fa-4838-4c2c-ae0b-ac9f1f5fa600
https://app.jiminny.eu/api/v1/activity/search?status%5B%5D=completed&sort_by=dateHeld&sort_direction=desc&exclude%5B%5D=stats&only_recorded=1&user_id%5B%5D=714a31fa-4838-4c2c-ae0b-ac9f1f5fa600
GET
GET
https://app.jiminny.eu/api/v1/notifications/recent
https://app.jiminny.eu/api/v1/notifications/recent
GET
GET
https://app.jiminny.eu/api/v1/activity/saved-search
https://app.jiminny.eu/api/v1/activity/saved-search
GET
GET
https://app.jiminny.eu/api/v1/integrations
https://app.jiminny.eu/api/v1/integrations
GET
GET
https://app.jiminny.eu/api/v1/page/on-demand?page=1
https://app.jiminny.eu/api/v1/page/on-demand?page=1
Network Request
Network Request
Legend
17:28.690 / 22:26.618
17:28.690 / 22:26.618
Play
10
Jump back 10 seconds
0.5x
0.5
x
1x
1
x
2x
2
x
4x
4
x
8x
8
x
Skipping Inactivity
Skipping Inactivity
Tools
Tools
Share
Share
Fullscreen
Ask Galileo
Ask Galileo...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0018284575,"top":0.0518755,"width":0.038065158,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.039893616,"top":0.0518755,"width":0.037898935,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.09497207,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.10614525,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"BE upgrade libraries","depth":4,"bounds":{"left":0.0028257978,"top":0.13288109,"width":0.03939495,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":4,"bounds":{"left":0.0,"top":0.15203512,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.1632083,"width":0.12799202,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Text relay","depth":4,"bounds":{"left":0.0028257978,"top":0.18994413,"width":0.020279255,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.0028257978,"top":0.21348763,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"bounds":{"left":0.015957447,"top":0.22466081,"width":0.04288564,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.2462091,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.25738227,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.27893057,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.29010376,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.31165203,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.32282522,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 fix missing header by LakyLak · Pull Request #12136 · jiminny/app","depth":4,"bounds":{"left":0.0028257978,"top":0.3443735,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 fix missing header by LakyLak · Pull Request #12136 · jiminny/app","depth":5,"bounds":{"left":0.015957447,"top":0.35554668,"width":0.1314827,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.37709498,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.38826814,"width":0.14245346,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Login | Salesforce","depth":4,"bounds":{"left":0.0,"top":0.40981644,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Login | Salesforce","depth":5,"bounds":{"left":0.013297873,"top":0.42098963,"width":0.030917553,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":4,"bounds":{"left":0.0,"top":0.4425379,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":5,"bounds":{"left":0.013297873,"top":0.4537111,"width":0.24301861,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.47525936,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny Mail","depth":5,"bounds":{"left":0.013297873,"top":0.48643255,"width":0.02144282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":4,"bounds":{"left":0.0,"top":0.5079808,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.519154,"width":0.08543883,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.54070234,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.5518755,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.5734238,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.584597,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.0028257978,"top":0.6113328,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0028257978,"top":0.6348763,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.015957447,"top":0.6460495,"width":0.013297873,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":4,"bounds":{"left":0.0028257978,"top":0.6675978,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":5,"bounds":{"left":0.015957447,"top":0.67877096,"width":0.1271609,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Sona Subramanian at 27/05/2026, 17:46:08 - Session Replay - LogRocket","depth":4,"bounds":{"left":0.0028257978,"top":0.70031923,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sona Subramanian at 27/05/2026, 17:46:08 - Session Replay - LogRocket","depth":5,"bounds":{"left":0.015957447,"top":0.7114924,"width":0.12799202,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Iliyana Netseva at 27/05/2026, 18:48:18 - Session Replay - LogRocket","depth":4,"bounds":{"left":0.0028257978,"top":0.7330407,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Iliyana Netseva at 27/05/2026, 18:48:18 - Session Replay - LogRocket","depth":5,"bounds":{"left":0.015957447,"top":0.7442139,"width":0.12134308,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.74022347,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.76735836,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"https://app.jiminny.eu/ondemand?user_id[]=714a31fa-4838-4c2c-ae0b-ac9f1f5fa600&sequence_number=32","depth":4,"bounds":{"left":0.094913565,"top":0.060654428,"width":0.17586437,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"https://app.jiminny.eu/ondemand?user_id[]=714a31fa-4838-4c2c-ae0b-ac9f1f5fa600&sequence_number=32","depth":5,"bounds":{"left":0.094913565,"top":0.061053474,"width":0.17586437,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://app.jiminny.eu","depth":6,"bounds":{"left":0.094913565,"top":0.061053474,"width":0.03374335,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/ondemand","depth":6,"bounds":{"left":0.12865691,"top":0.061053474,"width":0.018118352,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"?user_id[]=714a31fa-4838-4c2c-ae0b-ac9f1f5fa600&sequence_number=32","depth":6,"bounds":{"left":0.14677526,"top":0.061053474,"width":0.12400266,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Network","depth":4,"bounds":{"left":0.07962101,"top":0.08938547,"width":0.022606382,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Network","depth":6,"bounds":{"left":0.08361037,"top":0.09297685,"width":0.01462766,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Performance","depth":4,"bounds":{"left":0.1022274,"top":0.08938547,"width":0.03025266,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Performance","depth":5,"bounds":{"left":0.10621676,"top":0.09297685,"width":0.022273935,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"Filter network entries (e.g. status>=400 method:POST /user/)","depth":5,"bounds":{"left":0.72323805,"top":0.08978452,"width":0.11668883,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"17:49min","depth":7,"bounds":{"left":0.3382646,"top":0.11292897,"width":0.015458777,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"18:12min","depth":7,"bounds":{"left":0.3728391,"top":0.11292897,"width":0.015292553,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"18:35min","depth":7,"bounds":{"left":0.40724733,"top":0.11292897,"width":0.015458777,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"18:58min","depth":7,"bounds":{"left":0.4418218,"top":0.11292897,"width":0.015458777,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"19:22min","depth":7,"bounds":{"left":0.47639626,"top":0.11292897,"width":0.015292553,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"19:45min","depth":7,"bounds":{"left":0.51080453,"top":0.11292897,"width":0.015458777,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"20:08min","depth":7,"bounds":{"left":0.545379,"top":0.11292897,"width":0.015292553,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"20:31min","depth":7,"bounds":{"left":0.57978725,"top":0.11292897,"width":0.015458777,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"20:54min","depth":7,"bounds":{"left":0.6143617,"top":0.11292897,"width":0.015458777,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"21:17min","depth":7,"bounds":{"left":0.64893615,"top":0.11292897,"width":0.015292553,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"21:40min","depth":7,"bounds":{"left":0.6833444,"top":0.11292897,"width":0.015458777,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"22:03min","depth":7,"bounds":{"left":0.7179189,"top":0.11292897,"width":0.015292553,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"22:26min","depth":7,"bounds":{"left":0.75232714,"top":0.11292897,"width":0.015458777,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"Status","depth":7,"bounds":{"left":0.07962101,"top":0.10853951,"width":0.023271276,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Status","depth":8,"bounds":{"left":0.0859375,"top":0.11332801,"width":0.010305851,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"Method","depth":7,"bounds":{"left":0.10289229,"top":0.10853951,"width":0.023271276,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Method","depth":8,"bounds":{"left":0.10804521,"top":0.11332801,"width":0.012632979,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"URL","depth":7,"bounds":{"left":0.12583111,"top":0.10853951,"width":0.19498006,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"URL","depth":8,"bounds":{"left":0.21991356,"top":0.11332801,"width":0.0068151597,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"200","depth":10,"bounds":{"left":0.07962101,"top":0.10694334,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":13,"bounds":{"left":0.09059176,"top":0.110135674,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"GET","depth":10,"bounds":{"left":0.10289229,"top":0.10694334,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":12,"bounds":{"left":0.11186835,"top":0.110135674,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"https://app.jiminny.eu/vue-assets/assets/component-css-class-Du_nf4Gy.js","depth":10,"bounds":{"left":0.12616356,"top":0.10694334,"width":0.19498006,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"https://app.jiminny.eu/vue-assets/assets/component-css-class-Du_nf4Gy.js","depth":11,"bounds":{"left":0.1278258,"top":0.110135674,"width":0.15791224,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"200","depth":10,"bounds":{"left":0.07962101,"top":0.1245012,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":13,"bounds":{"left":0.09059176,"top":0.12769353,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"GET","depth":10,"bounds":{"left":0.10289229,"top":0.1245012,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":12,"bounds":{"left":0.11186835,"top":0.12769353,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"https://app.jiminny.eu/vue-assets/assets/vue-mq-bh4L87Tr.css","depth":10,"bounds":{"left":0.12616356,"top":0.1245012,"width":0.19498006,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"https://app.jiminny.eu/vue-assets/assets/vue-mq-bh4L87Tr.css","depth":11,"bounds":{"left":0.1278258,"top":0.12769353,"width":0.13164894,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"200","depth":10,"bounds":{"left":0.07962101,"top":0.14205906,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":13,"bounds":{"left":0.09059176,"top":0.1452514,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"GET","depth":10,"bounds":{"left":0.10289229,"top":0.14205906,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":12,"bounds":{"left":0.11186835,"top":0.1452514,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"https://app.jiminny.eu/vue-assets/assets/BuildInfo-CxNQv_OV.css","depth":10,"bounds":{"left":0.12616356,"top":0.14205906,"width":0.19498006,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"https://app.jiminny.eu/vue-assets/assets/BuildInfo-CxNQv_OV.css","depth":11,"bounds":{"left":0.1278258,"top":0.1452514,"width":0.13829787,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"200","depth":10,"bounds":{"left":0.07962101,"top":0.15961692,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":13,"bounds":{"left":0.09059176,"top":0.16280925,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"GET","depth":10,"bounds":{"left":0.10289229,"top":0.15961692,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":12,"bounds":{"left":0.11186835,"top":0.16280925,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"https://app.jiminny.eu/vue-assets/assets/BuildInfo-BqXdxd90.js","depth":10,"bounds":{"left":0.12616356,"top":0.15961692,"width":0.19498006,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"https://app.jiminny.eu/vue-assets/assets/BuildInfo-BqXdxd90.js","depth":11,"bounds":{"left":0.1278258,"top":0.16280925,"width":0.13597074,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"200","depth":10,"bounds":{"left":0.07962101,"top":0.17717478,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":13,"bounds":{"left":0.09059176,"top":0.18036711,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"GET","depth":10,"bounds":{"left":0.10289229,"top":0.17717478,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":12,"bounds":{"left":0.11186835,"top":0.18036711,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"https://app.jiminny.eu/vue-assets/assets/vue-mq-BT4SrCVV.js","depth":10,"bounds":{"left":0.12616356,"top":0.17717478,"width":0.19498006,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"https://app.jiminny.eu/vue-assets/assets/vue-mq-BT4SrCVV.js","depth":11,"bounds":{"left":0.1278258,"top":0.18036711,"width":0.12948804,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"200","depth":10,"bounds":{"left":0.07962101,"top":0.19473264,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":13,"bounds":{"left":0.09059176,"top":0.19792499,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"GET","depth":10,"bounds":{"left":0.10289229,"top":0.19473264,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":12,"bounds":{"left":0.11186835,"top":0.19792499,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"https://app.jiminny.eu/vue-assets/assets/PrimaryButton-CJj2FzKS.css","depth":10,"bounds":{"left":0.12616356,"top":0.19473264,"width":0.19498006,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"https://app.jiminny.eu/vue-assets/assets/PrimaryButton-CJj2FzKS.css","depth":11,"bounds":{"left":0.1278258,"top":0.19792499,"width":0.14694148,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"200","depth":10,"bounds":{"left":0.07962101,"top":0.2122905,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":13,"bounds":{"left":0.09059176,"top":0.21548285,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"GET","depth":10,"bounds":{"left":0.10289229,"top":0.2122905,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":12,"bounds":{"left":0.11186835,"top":0.21548285,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"https://app.jiminny.eu/vue-assets/assets/KioskBanner-Dj4CHp1p.js","depth":10,"bounds":{"left":0.12616356,"top":0.2122905,"width":0.19498006,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"https://app.jiminny.eu/vue-assets/assets/KioskBanner-Dj4CHp1p.js","depth":11,"bounds":{"left":0.1278258,"top":0.21548285,"width":0.14045878,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"200","depth":10,"bounds":{"left":0.07962101,"top":0.22984837,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":13,"bounds":{"left":0.09059176,"top":0.2330407,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"GET","depth":10,"bounds":{"left":0.10289229,"top":0.22984837,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":12,"bounds":{"left":0.11186835,"top":0.2330407,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"https://app.jiminny.eu/vue-assets/assets/PrimaryButton-CQICoCWJ.js","depth":10,"bounds":{"left":0.12616356,"top":0.22984837,"width":0.19498006,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"https://app.jiminny.eu/vue-assets/assets/PrimaryButton-CQICoCWJ.js","depth":11,"bounds":{"left":0.1278258,"top":0.2330407,"width":0.14478059,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"200","depth":10,"bounds":{"left":0.07962101,"top":0.24740623,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":13,"bounds":{"left":0.09059176,"top":0.25059855,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"GET","depth":10,"bounds":{"left":0.10289229,"top":0.24740623,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":12,"bounds":{"left":0.11186835,"top":0.25059855,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"https://app.jiminny.eu/vue-assets/assets/KioskBanner-xU3FRd8g.css","depth":10,"bounds":{"left":0.12616356,"top":0.24740623,"width":0.19498006,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"https://app.jiminny.eu/vue-assets/assets/KioskBanner-xU3FRd8g.css","depth":11,"bounds":{"left":0.1278258,"top":0.25059855,"width":0.14261968,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"200","depth":10,"bounds":{"left":0.07962101,"top":0.26496407,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":13,"bounds":{"left":0.09059176,"top":0.26815644,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"GET","depth":10,"bounds":{"left":0.10289229,"top":0.26496407,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":12,"bounds":{"left":0.11186835,"top":0.26815644,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"https://app.jiminny.eu/vue-assets/assets/LogoLong100-ByQTvUY3.css","depth":10,"bounds":{"left":0.12616356,"top":0.26496407,"width":0.19498006,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"https://app.jiminny.eu/vue-assets/assets/LogoLong100-ByQTvUY3.css","depth":11,"bounds":{"left":0.1278258,"top":0.26815644,"width":0.14261968,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"200","depth":10,"bounds":{"left":0.07962101,"top":0.28252193,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":13,"bounds":{"left":0.09059176,"top":0.2857143,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"GET","depth":10,"bounds":{"left":0.10289229,"top":0.28252193,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":12,"bounds":{"left":0.11186835,"top":0.2857143,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"https://app.jiminny.eu/vue-assets/assets/lastFilters-C6MahU6o.js","depth":10,"bounds":{"left":0.12616356,"top":0.28252193,"width":0.19498006,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"https://app.jiminny.eu/vue-assets/assets/lastFilters-C6MahU6o.js","depth":11,"bounds":{"left":0.1278258,"top":0.2857143,"width":0.14045878,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"200","depth":10,"bounds":{"left":0.07962101,"top":0.30007982,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":13,"bounds":{"left":0.09059176,"top":0.30327216,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"GET","depth":10,"bounds":{"left":0.10289229,"top":0.30007982,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":12,"bounds":{"left":0.11186835,"top":0.30327216,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"https://app.jiminny.eu/vue-assets/assets/LogoShort100-lnFxbZVL.js","depth":10,"bounds":{"left":0.12616356,"top":0.30007982,"width":0.19498006,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"https://app.jiminny.eu/vue-assets/assets/LogoShort100-lnFxbZVL.js","depth":11,"bounds":{"left":0.1278258,"top":0.30327216,"width":0.14261968,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"200","depth":10,"bounds":{"left":0.07962101,"top":0.31763768,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":13,"bounds":{"left":0.09059176,"top":0.32083002,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"GET","depth":10,"bounds":{"left":0.10289229,"top":0.31763768,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":12,"bounds":{"left":0.11186835,"top":0.32083002,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"https://app.jiminny.eu/vue-assets/assets/LogoLong100-C4rZ7qiD.js","depth":10,"bounds":{"left":0.12616356,"top":0.31763768,"width":0.19498006,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"https://app.jiminny.eu/vue-assets/assets/LogoLong100-C4rZ7qiD.js","depth":11,"bounds":{"left":0.1278258,"top":0.32083002,"width":0.14045878,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"200","depth":10,"bounds":{"left":0.07962101,"top":0.33519554,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":13,"bounds":{"left":0.09059176,"top":0.33838788,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"GET","depth":10,"bounds":{"left":0.10289229,"top":0.33519554,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":12,"bounds":{"left":0.11186835,"top":0.33838788,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"https://app.jiminny.eu/vue-assets/assets/snackbar-CS_iqvrv.css","depth":10,"bounds":{"left":0.12616356,"top":0.33519554,"width":0.19498006,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"https://app.jiminny.eu/vue-assets/assets/snackbar-CS_iqvrv.css","depth":11,"bounds":{"left":0.1278258,"top":0.33838788,"width":0.13597074,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"200","depth":10,"bounds":{"left":0.07962101,"top":0.3527534,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":13,"bounds":{"left":0.09059176,"top":0.35594574,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"GET","depth":10,"bounds":{"left":0.10289229,"top":0.3527534,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":12,"bounds":{"left":0.11186835,"top":0.35594574,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"https://app.jiminny.eu/vue-assets/assets/logged-in-layout-BjhUQA_l.css","depth":10,"bounds":{"left":0.12616356,"top":0.3527534,"width":0.19498006,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"https://app.jiminny.eu/vue-assets/assets/logged-in-layout-BjhUQA_l.css","depth":11,"bounds":{"left":0.1278258,"top":0.35594574,"width":0.15359043,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"200","depth":10,"bounds":{"left":0.07962101,"top":0.37031126,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":13,"bounds":{"left":0.09059176,"top":0.3735036,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"GET","depth":10,"bounds":{"left":0.10289229,"top":0.37031126,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":12,"bounds":{"left":0.11186835,"top":0.3735036,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"https://app.jiminny.eu/vue-assets/assets/liquor-tree-CB5oh8v1.css","depth":10,"bounds":{"left":0.12616356,"top":0.37031126,"width":0.19498006,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"https://app.jiminny.eu/vue-assets/assets/liquor-tree-CB5oh8v1.css","depth":11,"bounds":{"left":0.1278258,"top":0.3735036,"width":0.14261968,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"200","depth":10,"bounds":{"left":0.07962101,"top":0.38786912,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200","depth":13,"bounds":{"left":0.09059176,"top":0.39106146,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"GET","depth":10,"bounds":{"left":0.10289229,"top":0.38786912,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":12,"bounds":{"left":0.11186835,"top":0.39106146,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"https://app.jiminny.eu/vue-assets/assets/snackbar-D-kpc2T1.js","depth":10,"bounds":{"left":0.12616356,"top":0.38786912,"width":0.19498006,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"https://app.jiminny.eu/vue-assets/assets/snackbar-D-kpc2T1.js","depth":11,"bounds":{"left":0.1278258,"top":0.39106146,"width":0.13380983,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"GET","depth":10,"bounds":{"left":0.10289229,"top":0.40542698,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":12,"bounds":{"left":0.11186835,"top":0.4086193,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"https://app.jiminny.eu/vue-assets/assets/url-messenger-DzeACBZw.js","depth":10,"bounds":{"left":0.12616356,"top":0.40542698,"width":0.19498006,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"https://app.jiminny.eu/vue-assets/assets/url-messenger-DzeACBZw.js","depth":11,"bounds":{"left":0.1278258,"top":0.4086193,"width":0.14478059,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"GET","depth":10,"bounds":{"left":0.10289229,"top":0.42298484,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":12,"bounds":{"left":0.11186835,"top":0.42617717,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"https://app.jiminny.eu/api/v1/activity/search?status%5B%5D=completed&sort_by=dateHeld&sort_direction=desc&exclude%5B%5D=stats&only_recorded=1&user_id%5B%5D=714a31fa-4838-4c2c-ae0b-ac9f1f5fa600","depth":10,"bounds":{"left":0.12616356,"top":0.42298484,"width":0.19498006,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"https://app.jiminny.eu/api/v1/activity/search?status%5B%5D=completed&sort_by=dateHeld&sort_direction=desc&exclude%5B%5D=stats&only_recorded=1&user_id%5B%5D=714a31fa-4838-4c2c-ae0b-ac9f1f5fa600","depth":11,"bounds":{"left":0.1278258,"top":0.42617717,"width":0.4212101,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"GET","depth":10,"bounds":{"left":0.10289229,"top":0.4405427,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":12,"bounds":{"left":0.11186835,"top":0.44373503,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"https://app.jiminny.eu/api/v1/notifications/recent","depth":10,"bounds":{"left":0.12616356,"top":0.4405427,"width":0.19498006,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"https://app.jiminny.eu/api/v1/notifications/recent","depth":11,"bounds":{"left":0.1278258,"top":0.44373503,"width":0.109707445,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"GET","depth":10,"bounds":{"left":0.10289229,"top":0.45810056,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":12,"bounds":{"left":0.11186835,"top":0.4612929,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"https://app.jiminny.eu/api/v1/activity/saved-search","depth":10,"bounds":{"left":0.12616356,"top":0.45810056,"width":0.19498006,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"https://app.jiminny.eu/api/v1/activity/saved-search","depth":11,"bounds":{"left":0.1278258,"top":0.4612929,"width":0.11186835,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"GET","depth":10,"bounds":{"left":0.10289229,"top":0.47565842,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":12,"bounds":{"left":0.11186835,"top":0.47885075,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"https://app.jiminny.eu/api/v1/integrations","depth":10,"bounds":{"left":0.12616356,"top":0.47565842,"width":0.19498006,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"https://app.jiminny.eu/api/v1/integrations","depth":11,"bounds":{"left":0.1278258,"top":0.47885075,"width":0.09208777,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"GET","depth":10,"bounds":{"left":0.10289229,"top":0.49321628,"width":0.023271276,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET","depth":12,"bounds":{"left":0.11186835,"top":0.4964086,"width":0.0066489363,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCell","text":"https://app.jiminny.eu/api/v1/page/on-demand?page=1","depth":10,"bounds":{"left":0.12616356,"top":0.49321628,"width":0.19498006,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"cell","subrole":"AXUnknown"},{"role":"AXStaticText","text":"https://app.jiminny.eu/api/v1/page/on-demand?page=1","depth":11,"bounds":{"left":0.1278258,"top":0.4964086,"width":0.11186835,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Network Request","depth":6,"bounds":{"left":0.7699468,"top":0.11412609,"width":0.037898935,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Network Request","depth":8,"bounds":{"left":0.77393615,"top":0.117717475,"width":0.029920213,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Legend","depth":3,"bounds":{"left":0.9900266,"top":0.95011973,"width":0.00731383,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"17:28.690 / 22:26.618","depth":3,"bounds":{"left":0.08228058,"top":0.9724661,"width":0.05119681,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"17:28.690 / 22:26.618","depth":5,"bounds":{"left":0.0852726,"top":0.9768556,"width":0.040724736,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Play","depth":2,"bounds":{"left":0.13796543,"top":0.9736632,"width":0.00731383,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"10","depth":3,"bounds":{"left":0.1505984,"top":0.9788508,"width":0.0031582448,"height":0.0075818035},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Jump back 10 seconds","depth":2,"bounds":{"left":0.14793883,"top":0.971668,"width":0.009142287,"height":0.021947326},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"0.5x","depth":2,"bounds":{"left":0.15791224,"top":0.97406226,"width":0.014295213,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"0.5","depth":5,"bounds":{"left":0.1612367,"top":0.9764565,"width":0.005817819,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"x","depth":5,"bounds":{"left":0.16705452,"top":0.97725457,"width":0.0018284575,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"1x","depth":2,"bounds":{"left":0.17220744,"top":0.97406226,"width":0.010804521,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"1","depth":5,"bounds":{"left":0.17553191,"top":0.9764565,"width":0.0023271276,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"x","depth":5,"bounds":{"left":0.17785904,"top":0.97725457,"width":0.0018284575,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"2x","depth":2,"bounds":{"left":0.18301196,"top":0.97406226,"width":0.011136968,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2","depth":5,"bounds":{"left":0.18633644,"top":0.9764565,"width":0.002493351,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"x","depth":5,"bounds":{"left":0.1888298,"top":0.97725457,"width":0.0018284575,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"4x","depth":2,"bounds":{"left":0.19414894,"top":0.97406226,"width":0.010804521,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"4","depth":5,"bounds":{"left":0.1974734,"top":0.9764565,"width":0.0023271276,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"x","depth":5,"bounds":{"left":0.19980054,"top":0.97725457,"width":0.0018284575,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"8x","depth":2,"bounds":{"left":0.20495346,"top":0.97406226,"width":0.010804521,"height":0.01715882},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"8","depth":5,"bounds":{"left":0.20827793,"top":0.9764565,"width":0.0023271276,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"x","depth":5,"bounds":{"left":0.21060506,"top":0.97725457,"width":0.0018284575,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Skipping Inactivity","depth":3,"bounds":{"left":0.2165891,"top":0.9724661,"width":0.03956117,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skipping Inactivity","depth":4,"bounds":{"left":0.2237367,"top":0.97725457,"width":0.029421542,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Tools","depth":2,"bounds":{"left":0.91289896,"top":0.9724661,"width":0.01662234,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Tools","depth":3,"bounds":{"left":0.91805184,"top":0.97725457,"width":0.008477394,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Share","depth":2,"bounds":{"left":0.93351066,"top":0.9724661,"width":0.019946808,"height":0.020351157},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Share","depth":3,"bounds":{"left":0.9411569,"top":0.97725457,"width":0.00930851,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Fullscreen","depth":2,"bounds":{"left":0.9574468,"top":0.9736632,"width":0.00731383,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Ask Galileo","depth":2,"bounds":{"left":0.96875,"top":0.97326416,"width":0.028590426,"height":0.018754989},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Galileo","depth":3,"bounds":{"left":0.97639626,"top":0.97725457,"width":0.018284574,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-9088797448295232146
|
8184022540594246796
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix missing header by LakyLak · Pull Request #12136 · jiminny/app
JY-20915 fix missing header by LakyLak · Pull Request #12136 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny Mail
Jiminny Mail
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Unnamed Group
Jiminny
Jiminny
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Sona Subramanian at 27/05/2026, 17:46:08 - Session Replay - LogRocket
Sona Subramanian at 27/05/2026, 17:46:08 - Session Replay - LogRocket
Iliyana Netseva at 27/05/2026, 18:48:18 - Session Replay - LogRocket
Iliyana Netseva at 27/05/2026, 18:48:18 - Session Replay - LogRocket
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
https://app.jiminny.eu/ondemand?user_id[]=714a31fa-4838-4c2c-ae0b-ac9f1f5fa600&sequence_number=32
https://app.jiminny.eu/ondemand?user_id[]=714a31fa-4838-4c2c-ae0b-ac9f1f5fa600&sequence_number=32
https://app.jiminny.eu
/ondemand
?user_id[]=714a31fa-4838-4c2c-ae0b-ac9f1f5fa600&sequence_number=32
Network
Network
Performance
Performance
Filter network entries (e.g. status>=400 method:POST /user/)
17:49min
18:12min
18:35min
18:58min
19:22min
19:45min
20:08min
20:31min
20:54min
21:17min
21:40min
22:03min
22:26min
Status
Status
Method
Method
URL
URL
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/component-css-class-Du_nf4Gy.js
https://app.jiminny.eu/vue-assets/assets/component-css-class-Du_nf4Gy.js
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/vue-mq-bh4L87Tr.css
https://app.jiminny.eu/vue-assets/assets/vue-mq-bh4L87Tr.css
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/BuildInfo-CxNQv_OV.css
https://app.jiminny.eu/vue-assets/assets/BuildInfo-CxNQv_OV.css
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/BuildInfo-BqXdxd90.js
https://app.jiminny.eu/vue-assets/assets/BuildInfo-BqXdxd90.js
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/vue-mq-BT4SrCVV.js
https://app.jiminny.eu/vue-assets/assets/vue-mq-BT4SrCVV.js
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/PrimaryButton-CJj2FzKS.css
https://app.jiminny.eu/vue-assets/assets/PrimaryButton-CJj2FzKS.css
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/KioskBanner-Dj4CHp1p.js
https://app.jiminny.eu/vue-assets/assets/KioskBanner-Dj4CHp1p.js
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/PrimaryButton-CQICoCWJ.js
https://app.jiminny.eu/vue-assets/assets/PrimaryButton-CQICoCWJ.js
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/KioskBanner-xU3FRd8g.css
https://app.jiminny.eu/vue-assets/assets/KioskBanner-xU3FRd8g.css
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/LogoLong100-ByQTvUY3.css
https://app.jiminny.eu/vue-assets/assets/LogoLong100-ByQTvUY3.css
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/lastFilters-C6MahU6o.js
https://app.jiminny.eu/vue-assets/assets/lastFilters-C6MahU6o.js
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/LogoShort100-lnFxbZVL.js
https://app.jiminny.eu/vue-assets/assets/LogoShort100-lnFxbZVL.js
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/LogoLong100-C4rZ7qiD.js
https://app.jiminny.eu/vue-assets/assets/LogoLong100-C4rZ7qiD.js
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/snackbar-CS_iqvrv.css
https://app.jiminny.eu/vue-assets/assets/snackbar-CS_iqvrv.css
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/logged-in-layout-BjhUQA_l.css
https://app.jiminny.eu/vue-assets/assets/logged-in-layout-BjhUQA_l.css
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/liquor-tree-CB5oh8v1.css
https://app.jiminny.eu/vue-assets/assets/liquor-tree-CB5oh8v1.css
200
200
GET
GET
https://app.jiminny.eu/vue-assets/assets/snackbar-D-kpc2T1.js
https://app.jiminny.eu/vue-assets/assets/snackbar-D-kpc2T1.js
GET
GET
https://app.jiminny.eu/vue-assets/assets/url-messenger-DzeACBZw.js
https://app.jiminny.eu/vue-assets/assets/url-messenger-DzeACBZw.js
GET
GET
https://app.jiminny.eu/api/v1/activity/search?status%5B%5D=completed&sort_by=dateHeld&sort_direction=desc&exclude%5B%5D=stats&only_recorded=1&user_id%5B%5D=714a31fa-4838-4c2c-ae0b-ac9f1f5fa600
https://app.jiminny.eu/api/v1/activity/search?status%5B%5D=completed&sort_by=dateHeld&sort_direction=desc&exclude%5B%5D=stats&only_recorded=1&user_id%5B%5D=714a31fa-4838-4c2c-ae0b-ac9f1f5fa600
GET
GET
https://app.jiminny.eu/api/v1/notifications/recent
https://app.jiminny.eu/api/v1/notifications/recent
GET
GET
https://app.jiminny.eu/api/v1/activity/saved-search
https://app.jiminny.eu/api/v1/activity/saved-search
GET
GET
https://app.jiminny.eu/api/v1/integrations
https://app.jiminny.eu/api/v1/integrations
GET
GET
https://app.jiminny.eu/api/v1/page/on-demand?page=1
https://app.jiminny.eu/api/v1/page/on-demand?page=1
Network Request
Network Request
Legend
17:28.690 / 22:26.618
17:28.690 / 22:26.618
Play
10
Jump back 10 seconds
0.5x
0.5
x
1x
1
x
2x
2
x
4x
4
x
8x
8
x
Skipping Inactivity
Skipping Inactivity
Tools
Tools
Share
Share
Fullscreen
Ask Galileo
Ask Galileo...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
36939
|
1381
|
9
|
2026-05-13T15:26:32.893974+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-13/1778 /Users/lukas/.screenpipe/data/data/2026-05-13/1778685992893_m1.jpg...
|
PhpStorm
|
faVsco.js – SF [jiminny@localhost]
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
iTerm2ShellEditViewSessionScriptsProfilesWindowHel iTerm2ShellEditViewSessionScriptsProfilesWindowHelp‹ >0 (lADEV (docker)-zshDOCKERO 881DEV (docker)*2Start pushingin list for update entities(32212)> yesAPP (-zsh)83Lyes] :Scheduled 4612 entities for processing..32212/32212 [=-------==-7 s/7-=--=======] 100%62.0 MiBRemaining activities entities for processing (normal: 4412, priority: 0)screenpipe"• ₴5ec2-user@ip-10-30-129-...X6100% <-8• Wed 13 May 18:26:32181-zsh87DEVMonitor scheduled entities depletion...Entities remaining 504412/4412 [===100%Entities remaining 04412/4412 [========100%9 min, 25 s/9 min,25 s 62.0 MiBData reset completed.root@docker_lamp_1:/home/jiminny#What's next:Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug 007d5da3af66Learn more at [URL_WITH_CREDENTIALS] artisan activity:update:es f43cf158-e60d-46e5-92f8-c4e0594a3219Found activity ID: 16, UUID: 988920ba-cdf8-43bc-a869-1f6d85e55fa0Sending activity for ES update...Done.root@docker_lamp_1:/home/jiminny# php artisan activity:update:es f43cf158-e60d-46e5-92f8-c4e0594a3219Searching for activity with: f43cf158-e60d-46e5-92f8-c4e0594a3219Found activity ID: 422003, UUID: f43cf158-e60d-46e5-92f8-c4e0594a3219Sending activity for ES update...Done.root@docker_lamp_1:/home/jiminny# php artisan activity:update:es f43cf158-e60d-46e5-92f8-c4e0594a3219Searching for activity with: f43cf158-e60d-46e5-92f8-c4e0594a3219Found activity ID: 422003, UUID: f43cf158-e60d-46e5-92f8-c4e0594a3219Sending activity for ES update...Done.root@docker_lamp_1:/home/jiminny# php artisan activity:update:es f43cf158-e60d-46e5-92f8-c4e0594a3219Searching for activity with: f43cf158-e60d-46e5-92f8-c4e0594a3219Found activity ID: 422003, UUID: f43cf158-e60d-46e5-92f8-c4e0594a3219Sending activity for ES update...Done.root@docker_lamp_1:/home/jiminny# ]...
|
NULL
|
-9088694757391434467
|
NULL
|
click
|
ocr
|
NULL
|
iTerm2ShellEditViewSessionScriptsProfilesWindowHel iTerm2ShellEditViewSessionScriptsProfilesWindowHelp‹ >0 (lADEV (docker)-zshDOCKERO 881DEV (docker)*2Start pushingin list for update entities(32212)> yesAPP (-zsh)83Lyes] :Scheduled 4612 entities for processing..32212/32212 [=-------==-7 s/7-=--=======] 100%62.0 MiBRemaining activities entities for processing (normal: 4412, priority: 0)screenpipe"• ₴5ec2-user@ip-10-30-129-...X6100% <-8• Wed 13 May 18:26:32181-zsh87DEVMonitor scheduled entities depletion...Entities remaining 504412/4412 [===100%Entities remaining 04412/4412 [========100%9 min, 25 s/9 min,25 s 62.0 MiBData reset completed.root@docker_lamp_1:/home/jiminny#What's next:Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug 007d5da3af66Learn more at [URL_WITH_CREDENTIALS] artisan activity:update:es f43cf158-e60d-46e5-92f8-c4e0594a3219Found activity ID: 16, UUID: 988920ba-cdf8-43bc-a869-1f6d85e55fa0Sending activity for ES update...Done.root@docker_lamp_1:/home/jiminny# php artisan activity:update:es f43cf158-e60d-46e5-92f8-c4e0594a3219Searching for activity with: f43cf158-e60d-46e5-92f8-c4e0594a3219Found activity ID: 422003, UUID: f43cf158-e60d-46e5-92f8-c4e0594a3219Sending activity for ES update...Done.root@docker_lamp_1:/home/jiminny# php artisan activity:update:es f43cf158-e60d-46e5-92f8-c4e0594a3219Searching for activity with: f43cf158-e60d-46e5-92f8-c4e0594a3219Found activity ID: 422003, UUID: f43cf158-e60d-46e5-92f8-c4e0594a3219Sending activity for ES update...Done.root@docker_lamp_1:/home/jiminny# php artisan activity:update:es f43cf158-e60d-46e5-92f8-c4e0594a3219Searching for activity with: f43cf158-e60d-46e5-92f8-c4e0594a3219Found activity ID: 422003, UUID: f43cf158-e60d-46e5-92f8-c4e0594a3219Sending activity for ES update...Done.root@docker_lamp_1:/home/jiminny# ]...
|
36936
|
NULL
|
NULL
|
NULL
|
|
76205
|
2686
|
26
|
2026-05-27T09:33:47.808756+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-27/1779 /Users/lukas/.screenpipe/data/data/2026-05-27/1779874427808_m1.jpg...
|
PhpStorm
|
faVsco.js – Kernel.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
+SlackFileEditHistoryWindow HelpQViewGoKioskOrgani +SlackFileEditHistoryWindow HelpQViewGoKioskOrganizationsSetup AccountUsersActivities- jupiter.staging.jiminny.com/kiosk/onboard-listAutomated ReportsMobile version• Search |NAMEAudi MotorsHomeDMsActivityFilesLater•.•MoreED→Jiminny ...Ab External connections# Starred& jiminny-x-integrati...8 platform-backend-...8 platform-inner-teamE Channels# ai-chapter#alerts# backend# bugs# confusion-clinic# donut_time# engineering# general# happy_birthday# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of_jimi...° Direct messagesStalivan Coorciov• Unread mentions100% <78• Wed 27 May 12:33:47Describe what you are looking forSteliyan Georgiev6 0• Messagest* Add canvasO Files+Allow oToday~to be selected whe...Story JY-2U613 in Jira CloudStatusDeployedPriority= MediumAssigneeLukas Koval...As of today at 11:21 AMOpen in Jira+ Summarizeсамо да видя къде може да се покажеd 1Lukas Kovalik :11:35 AMhttps://jupiter.staging.jiminny.com/kiosk/onboard-listако искаш да се чуем на бързоSteliyan Georgiev 11:36 AMдай ми 3 мин и ще ти звъннаLukas Kovalik ®! 11:36 AMOKA huddle happened 11:39 AMYou and Steliyan Georgiev were in the huddle for4m.Message Steliyan Georgiev...Lộ3JY-20855-mcp-ES-mappings = 892937...
|
NULL
|
-9088623283515103210
|
NULL
|
click
|
ocr
|
NULL
|
+SlackFileEditHistoryWindow HelpQViewGoKioskOrgani +SlackFileEditHistoryWindow HelpQViewGoKioskOrganizationsSetup AccountUsersActivities- jupiter.staging.jiminny.com/kiosk/onboard-listAutomated ReportsMobile version• Search |NAMEAudi MotorsHomeDMsActivityFilesLater•.•MoreED→Jiminny ...Ab External connections# Starred& jiminny-x-integrati...8 platform-backend-...8 platform-inner-teamE Channels# ai-chapter#alerts# backend# bugs# confusion-clinic# donut_time# engineering# general# happy_birthday# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of_jimi...° Direct messagesStalivan Coorciov• Unread mentions100% <78• Wed 27 May 12:33:47Describe what you are looking forSteliyan Georgiev6 0• Messagest* Add canvasO Files+Allow oToday~to be selected whe...Story JY-2U613 in Jira CloudStatusDeployedPriority= MediumAssigneeLukas Koval...As of today at 11:21 AMOpen in Jira+ Summarizeсамо да видя къде може да се покажеd 1Lukas Kovalik :11:35 AMhttps://jupiter.staging.jiminny.com/kiosk/onboard-listако искаш да се чуем на бързоSteliyan Georgiev 11:36 AMдай ми 3 мин и ще ти звъннаLukas Kovalik ®! 11:36 AMOKA huddle happened 11:39 AMYou and Steliyan Georgiev were in the huddle for4m.Message Steliyan Georgiev...Lộ3JY-20855-mcp-ES-mappings = 892937...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
42959
|
1572
|
30
|
2026-05-14T12:13:24.306722+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778760804306_m2.jpg...
|
Firefox
|
jiminny/app at JY-20903-update_activity-stage-on-o jiminny/app at JY-20903-update_activity-stage-on-opportunity-change — Work...
|
1
|
github.com/jiminny/app/tree/JY-20903-update_activi github.com/jiminny/app/tree/JY-20903-update_activity-stage-on-opportunity-change...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Unnamed Group
Platform Sprint 4 Q2 - Platform Team Unnamed Group
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
jiminny/app at JY-20903-update_activity-stage-on-opportunity-change
jiminny/app at JY-20903-update_activity-stage-on-opportunity-change
Close tab
[SRD-6849] Recorded call does not appear on the dashboard - Jira
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Usage | Windsurf
Usage | Windsurf
Unnamed Group
[SRD-6848] Sidekick SMS issue - Jira
[SRD-6848] Sidekick SMS issue - Jira
[JY-20891] Sidekick SMS issue - Jira
[JY-20891] Sidekick SMS issue - Jira
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
Unnamed Group
[SRD-6849] Recorded call does not appear on the dashboard - Jira
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Jiminny
Jiminny
Jiminny
Jiminny
Jiminny
Jiminny
Unnamed Group
[SRD-6853] Moxso - Potential deal stages bug - Jira
[SRD-6853] Moxso - Potential deal stages bug - Jira
[JY-20903] Recorded call does not appear on the dashboard - Jira
[JY-20903] Recorded call does not appear on the dashboard - Jira
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
Useful commands - Engineering - Confluence
Useful commands - Engineering - Confluence
Dev Tools - Elastic
Dev Tools - Elastic
Jiminny
Jiminny
CloudWatch | eu-west-1
CloudWatch | eu-west-1
CloudWatch | eu-west-1
CloudWatch | eu-west-1
CloudWatch | eu-west-1
CloudWatch | eu-west-1
Unnamed Group
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
Pipedrive Developers Corner | Pipedrive API sandbox
Pipedrive Developers Corner | Pipedrive API sandbox
daniti/pipedrive-laravel: A Pipedrive App boilerplate with Laravel
daniti/pipedrive-laravel: A Pipedrive App boilerplate with Laravel
pipedrive/client-php: Pipedrive API client for PHP
pipedrive/client-php: Pipedrive API client for PHP
IsraelOrtuno/pipedrive: Complete Pipedrive API client for PHP
IsraelOrtuno/pipedrive: Complete Pipedrive API client for PHP
Code search results
Code search results
Jiminny
Jiminny
[JY-20903] Recorded call does not appear on the dashboard - Jira
[JY-20903] Recorded call does not appear on the dashboard - Jira
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Tabs from other devices...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.0028257978,"top":0.057063047,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0028257978,"top":0.08060654,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.015957447,"top":0.09217877,"width":0.101230055,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"jiminny/app at JY-20903-update_activity-stage-on-opportunity-change","depth":4,"bounds":{"left":0.0028257978,"top":0.11332801,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"jiminny/app at JY-20903-update_activity-stage-on-opportunity-change","depth":5,"bounds":{"left":0.015957447,"top":0.12490024,"width":0.12516622,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.12051077,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":4,"bounds":{"left":0.0028257978,"top":0.14604948,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":5,"bounds":{"left":0.015957447,"top":0.15762171,"width":0.11735372,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Usage | Windsurf","depth":4,"bounds":{"left":0.0,"top":0.17877094,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Usage | Windsurf","depth":5,"bounds":{"left":0.013297873,"top":0.19034317,"width":0.029920213,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.0028257978,"top":0.21667998,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6848] Sidekick SMS issue - Jira","depth":4,"bounds":{"left":0.0028257978,"top":0.24022347,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6848] Sidekick SMS issue - Jira","depth":5,"bounds":{"left":0.015957447,"top":0.25179568,"width":0.06632314,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20891] Sidekick SMS issue - Jira","depth":4,"bounds":{"left":0.0028257978,"top":0.27294493,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20891] Sidekick SMS issue - Jira","depth":5,"bounds":{"left":0.015957447,"top":0.28451717,"width":0.064494684,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app","depth":4,"bounds":{"left":0.0028257978,"top":0.3056664,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app","depth":5,"bounds":{"left":0.015957447,"top":0.31723863,"width":0.15658244,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.0028257978,"top":0.34357542,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":4,"bounds":{"left":0.0028257978,"top":0.36711892,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":5,"bounds":{"left":0.015957447,"top":0.37869114,"width":0.11735372,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0028257978,"top":0.39984038,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.015957447,"top":0.4114126,"width":0.013297873,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0028257978,"top":0.43256184,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.015957447,"top":0.4441341,"width":0.013297873,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0028257978,"top":0.46528333,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.015957447,"top":0.47685555,"width":0.013297873,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.0028257978,"top":0.50319237,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6853] Moxso - Potential deal stages bug - Jira","depth":4,"bounds":{"left":0.0028257978,"top":0.52673584,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6853] Moxso - Potential deal stages bug - Jira","depth":5,"bounds":{"left":0.015957447,"top":0.5383081,"width":0.091755316,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20903] Recorded call does not appear on the dashboard - Jira","depth":4,"bounds":{"left":0.0028257978,"top":0.5594573,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20903] Recorded call does not appear on the dashboard - Jira","depth":5,"bounds":{"left":0.015957447,"top":0.57102954,"width":0.11619016,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app","depth":4,"bounds":{"left":0.0028257978,"top":0.59217876,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app","depth":5,"bounds":{"left":0.015957447,"top":0.603751,"width":0.18932846,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.6249002,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":5,"bounds":{"left":0.013297873,"top":0.63647246,"width":0.12898937,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Useful commands - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.6576217,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Useful commands - Engineering - Confluence","depth":5,"bounds":{"left":0.013297873,"top":0.6691939,"width":0.079288565,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Dev Tools - Elastic","depth":4,"bounds":{"left":0.0,"top":0.6903432,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Dev Tools - Elastic","depth":5,"bounds":{"left":0.013297873,"top":0.7019154,"width":0.032247342,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.72306466,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.73463684,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | eu-west-1","depth":4,"bounds":{"left":0.0,"top":0.7557861,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | eu-west-1","depth":5,"bounds":{"left":0.013297873,"top":0.76735836,"width":0.041722074,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | eu-west-1","depth":4,"bounds":{"left":0.0,"top":0.7885076,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | eu-west-1","depth":5,"bounds":{"left":0.013297873,"top":0.8000798,"width":0.041722074,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | eu-west-1","depth":4,"bounds":{"left":0.0,"top":0.82122904,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | eu-west-1","depth":5,"bounds":{"left":0.013297873,"top":0.8328013,"width":0.041722074,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.0028257978,"top":0.8591381,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app","depth":4,"bounds":{"left":0.0028257978,"top":0.88268155,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app","depth":5,"bounds":{"left":0.015957447,"top":0.8942538,"width":0.21575798,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira","depth":4,"bounds":{"left":0.0028257978,"top":0.915403,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira","depth":5,"bounds":{"left":0.015957447,"top":0.92697525,"width":0.122340426,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipedrive Developers Corner | Pipedrive API sandbox","depth":4,"bounds":{"left":0.0,"top":0.9481245,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipedrive Developers Corner | Pipedrive API sandbox","depth":5,"bounds":{"left":0.013297873,"top":0.9596967,"width":0.091755316,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"daniti/pipedrive-laravel: A Pipedrive App boilerplate with Laravel","depth":4,"bounds":{"left":0.0,"top":0.980846,"width":0.07962101,"height":0.019154012},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"daniti/pipedrive-laravel: A Pipedrive App boilerplate with Laravel","depth":5,"bounds":{"left":0.013297873,"top":0.99241817,"width":0.11153591,"height":0.00758183},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"pipedrive/client-php: Pipedrive API client for PHP","depth":4,"bounds":{"left":0.0,"top":1.0,"width":0.07962101,"height":-0.013567448},"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"pipedrive/client-php: Pipedrive API client for PHP","depth":5,"bounds":{"left":0.013297873,"top":1.0,"width":0.08543883,"height":-0.02513969},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"IsraelOrtuno/pipedrive: Complete Pipedrive API client for PHP","depth":4,"bounds":{"left":0.0,"top":1.0,"width":0.07962101,"height":-0.046288848},"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"IsraelOrtuno/pipedrive: Complete Pipedrive API client for PHP","depth":5,"bounds":{"left":0.013297873,"top":1.0,"width":0.10688165,"height":-0.05786109},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Code search results","depth":4,"bounds":{"left":0.0,"top":1.0,"width":0.07962101,"height":-0.07901037},"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code search results","depth":5,"bounds":{"left":0.013297873,"top":1.0,"width":0.034574468,"height":-0.09058261},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20903] Recorded call does not appear on the dashboard - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20903] Recorded call does not appear on the dashboard - Jira","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.92897046,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-9088584343829090486
|
1739601462541697636
|
visual_change
|
accessibility
|
NULL
|
Unnamed Group
Platform Sprint 4 Q2 - Platform Team Unnamed Group
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
jiminny/app at JY-20903-update_activity-stage-on-opportunity-change
jiminny/app at JY-20903-update_activity-stage-on-opportunity-change
Close tab
[SRD-6849] Recorded call does not appear on the dashboard - Jira
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Usage | Windsurf
Usage | Windsurf
Unnamed Group
[SRD-6848] Sidekick SMS issue - Jira
[SRD-6848] Sidekick SMS issue - Jira
[JY-20891] Sidekick SMS issue - Jira
[JY-20891] Sidekick SMS issue - Jira
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
Unnamed Group
[SRD-6849] Recorded call does not appear on the dashboard - Jira
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Jiminny
Jiminny
Jiminny
Jiminny
Jiminny
Jiminny
Unnamed Group
[SRD-6853] Moxso - Potential deal stages bug - Jira
[SRD-6853] Moxso - Potential deal stages bug - Jira
[JY-20903] Recorded call does not appear on the dashboard - Jira
[JY-20903] Recorded call does not appear on the dashboard - Jira
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
Useful commands - Engineering - Confluence
Useful commands - Engineering - Confluence
Dev Tools - Elastic
Dev Tools - Elastic
Jiminny
Jiminny
CloudWatch | eu-west-1
CloudWatch | eu-west-1
CloudWatch | eu-west-1
CloudWatch | eu-west-1
CloudWatch | eu-west-1
CloudWatch | eu-west-1
Unnamed Group
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
Pipedrive Developers Corner | Pipedrive API sandbox
Pipedrive Developers Corner | Pipedrive API sandbox
daniti/pipedrive-laravel: A Pipedrive App boilerplate with Laravel
daniti/pipedrive-laravel: A Pipedrive App boilerplate with Laravel
pipedrive/client-php: Pipedrive API client for PHP
pipedrive/client-php: Pipedrive API client for PHP
IsraelOrtuno/pipedrive: Complete Pipedrive API client for PHP
IsraelOrtuno/pipedrive: Complete Pipedrive API client for PHP
Code search results
Code search results
Jiminny
Jiminny
[JY-20903] Recorded call does not appear on the dashboard - Jira
[JY-20903] Recorded call does not appear on the dashboard - Jira
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Tabs from other devices...
|
42958
|
NULL
|
NULL
|
NULL
|
|
30496
|
1210
|
47
|
2026-05-13T07:47:16.410470+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-13/1778 /Users/lukas/.screenpipe/data/data/2026-05-13/1778658436410_m2.jpg...
|
Firefox
|
DXP4800PLUS-B5F8 — Personal
|
1
|
nas.lakylak.xyz/desktop/#/
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Screenpipe — Archive
Screenpipe — Archive
All docs Screenpipe — Archive
Screenpipe — Archive
All docs · AFFiNE
All docs · AFFiNE
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
Close tab
New Tab
New Tab
Screenpipe — Archive
Screenpipe — Archive
SQLite Web: archive.db
SQLite Web: archive.db
SQLite Web: db.sqlite
SQLite Web: db.sqlite
Claude
Claude
Manage extra usage for paid Claude plans | Claude Help Center
Manage extra usage for paid Claude plans | Claude Help Center
New Tab
New Tab
2 TB in 25 MB/s - Google Search
2 TB in 25 MB/s - Google Search
New Tab
New Tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
1
KB/s
198
B/s
Files
Control Panel
Storage
App Center
Logs
Support
Task Manager
Music
Cloud Drives
Theater
Photos
Online Office
TextEdit
Virtual Machine
Downloads
DLNA
File Version Explorer
Security
Jellyfin-HT
SAN Manager
Vault
Snapshot
Comics
Sync & Backup
UGREEN AI
Recycle Bin
Control Panel
Search
Connection & Access
User Management
File Service
Device Connection
Domain/LDAP
Terminal
General
Hardware & Power
Time & Language
Network
Security...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Screenpipe — Archive","depth":4,"bounds":{"left":0.5,"top":0.0518755,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Screenpipe — Archive","depth":5,"bounds":{"left":0.51329786,"top":0.06304868,"width":0.037898935,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"All docs · AFFiNE","depth":4,"bounds":{"left":0.5,"top":0.08459697,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"All docs · AFFiNE","depth":5,"bounds":{"left":0.51329786,"top":0.09577015,"width":0.029587766,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"DXP4800PLUS-B5F8","depth":4,"bounds":{"left":0.5,"top":0.11731844,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"DXP4800PLUS-B5F8","depth":5,"bounds":{"left":0.51329786,"top":0.12849163,"width":0.036901597,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.55651593,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.5,"top":0.15003991,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.51329786,"top":0.16121309,"width":0.014960106,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Screenpipe — Archive","depth":4,"bounds":{"left":0.5,"top":0.18276137,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Screenpipe — Archive","depth":5,"bounds":{"left":0.51329786,"top":0.19393456,"width":0.037898935,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SQLite Web: archive.db","depth":4,"bounds":{"left":0.5,"top":0.21548285,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SQLite Web: archive.db","depth":5,"bounds":{"left":0.51329786,"top":0.22665602,"width":0.040724736,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SQLite Web: db.sqlite","depth":4,"bounds":{"left":0.5,"top":0.2482043,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SQLite Web: db.sqlite","depth":5,"bounds":{"left":0.51329786,"top":0.25937748,"width":0.03756649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Claude","depth":4,"bounds":{"left":0.5,"top":0.28092578,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Claude","depth":5,"bounds":{"left":0.51329786,"top":0.29209897,"width":0.012134309,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Manage extra usage for paid Claude plans | Claude Help Center","depth":4,"bounds":{"left":0.5,"top":0.31364724,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Manage extra usage for paid Claude plans | Claude Help Center","depth":5,"bounds":{"left":0.51329786,"top":0.32482043,"width":0.1100399,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.5,"top":0.3463687,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.51329786,"top":0.3575419,"width":0.014960106,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"2 TB in 25 MB/s - Google Search","depth":4,"bounds":{"left":0.5,"top":0.3790902,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2 TB in 25 MB/s - Google Search","depth":5,"bounds":{"left":0.51329786,"top":0.39026338,"width":0.05668218,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.5,"top":0.41181165,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.51329786,"top":0.42298484,"width":0.014960106,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.5028258,"top":0.4461293,"width":0.06333112,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.5028258,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.51379657,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.5249335,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.53607047,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bitwarden","depth":6,"bounds":{"left":0.5472075,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":18,"bounds":{"left":0.97706115,"top":0.06304868,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":16,"bounds":{"left":0.92769283,"top":0.06264964,"width":0.0019946808,"height":0.008379889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"KB/s","depth":16,"bounds":{"left":0.9296875,"top":0.06304868,"width":0.006150266,"height":0.0075818035},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"198","depth":16,"bounds":{"left":0.92769283,"top":0.07222666,"width":0.005984043,"height":0.008379889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"B/s","depth":16,"bounds":{"left":0.93367684,"top":0.0726257,"width":0.004155585,"height":0.0075818035},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Files","depth":13,"bounds":{"left":0.59175533,"top":0.1707901,"width":0.009973404,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Control Panel","depth":13,"bounds":{"left":0.58261305,"top":0.2697526,"width":0.02825798,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Storage","depth":13,"bounds":{"left":0.58859706,"top":0.36871508,"width":0.016289894,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"App Center","depth":13,"bounds":{"left":0.58494014,"top":0.46767756,"width":0.023603724,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Logs","depth":13,"bounds":{"left":0.59175533,"top":0.5666401,"width":0.009973404,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Support","depth":13,"bounds":{"left":0.58859706,"top":0.66560256,"width":0.016289894,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Task Manager","depth":13,"bounds":{"left":0.58211434,"top":0.76456505,"width":0.02925532,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Music","depth":13,"bounds":{"left":0.5905917,"top":0.86352754,"width":0.012300532,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Cloud Drives","depth":13,"bounds":{"left":0.6313165,"top":0.1707901,"width":0.026595745,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Theater","depth":13,"bounds":{"left":0.63663566,"top":0.2697526,"width":0.015957447,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Photos","depth":13,"bounds":{"left":0.63730055,"top":0.36871508,"width":0.01462766,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Online Office","depth":13,"bounds":{"left":0.63115025,"top":0.46767756,"width":0.026928192,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"TextEdit","depth":13,"bounds":{"left":0.6363032,"top":0.5666401,"width":0.01662234,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Virtual Machine","depth":13,"bounds":{"left":0.6286569,"top":0.66560256,"width":0.031914894,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Downloads","depth":13,"bounds":{"left":0.633145,"top":0.76456505,"width":0.022938829,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"DLNA","depth":13,"bounds":{"left":0.6384641,"top":0.86352754,"width":0.012300532,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"File Version Explorer","depth":13,"bounds":{"left":0.6710439,"top":0.1707901,"width":0.04288564,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Security","depth":13,"bounds":{"left":0.6840093,"top":0.2697526,"width":0.016954787,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Jellyfin-HT","depth":13,"bounds":{"left":0.68151593,"top":0.36871508,"width":0.021941489,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SAN Manager","depth":13,"bounds":{"left":0.67785907,"top":0.46767756,"width":0.02925532,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Vault","depth":13,"bounds":{"left":0.68733376,"top":0.5666401,"width":0.010305851,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Snapshot","depth":13,"bounds":{"left":0.68267953,"top":0.66560256,"width":0.019614361,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Comics","depth":13,"bounds":{"left":0.6846742,"top":0.76456505,"width":0.015625,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Sync & Backup","depth":13,"bounds":{"left":0.67669547,"top":0.86352754,"width":0.03158245,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"UGREEN AI","depth":13,"bounds":{"left":0.72755986,"top":0.1707901,"width":0.025598405,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Recycle Bin","depth":13,"bounds":{"left":0.7280585,"top":0.2697526,"width":0.024601065,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Control Panel","depth":13,"bounds":{"left":0.84142286,"top":0.1348763,"width":0.025930852,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"","depth":13,"bounds":{"left":0.98670214,"top":0.13088587,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":14,"bounds":{"left":0.9880319,"top":0.13407822,"width":0.005319149,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":19,"bounds":{"left":0.7224069,"top":0.1707901,"width":0.004654255,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"Search","depth":18,"bounds":{"left":0.7297208,"top":0.16360734,"width":0.028922873,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Connection & Access","depth":19,"bounds":{"left":0.7087766,"top":0.21468475,"width":0.037898935,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"User Management","depth":21,"bounds":{"left":0.71875,"top":0.2490024,"width":0.040059842,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"File Service","depth":21,"bounds":{"left":0.71875,"top":0.28731045,"width":0.025930852,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Device Connection","depth":21,"bounds":{"left":0.71875,"top":0.3256185,"width":0.025598405,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Domain/LDAP","depth":21,"bounds":{"left":0.71875,"top":0.3830806,"width":0.031083776,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Terminal","depth":21,"bounds":{"left":0.71875,"top":0.42138866,"width":0.019115692,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"General","depth":19,"bounds":{"left":0.7087766,"top":0.46049482,"width":0.01412899,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hardware & Power","depth":21,"bounds":{"left":0.71875,"top":0.49481246,"width":0.04105718,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Time & Language","depth":21,"bounds":{"left":0.71875,"top":0.5331205,"width":0.03873005,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Network","depth":21,"bounds":{"left":0.71875,"top":0.5714286,"width":0.018284574,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Security","depth":21,"bounds":{"left":0.71875,"top":0.6097366,"width":0.018284574,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-9088389786595429164
|
7452788025262721421
|
click
|
accessibility
|
NULL
|
Screenpipe — Archive
Screenpipe — Archive
All docs Screenpipe — Archive
Screenpipe — Archive
All docs · AFFiNE
All docs · AFFiNE
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
Close tab
New Tab
New Tab
Screenpipe — Archive
Screenpipe — Archive
SQLite Web: archive.db
SQLite Web: archive.db
SQLite Web: db.sqlite
SQLite Web: db.sqlite
Claude
Claude
Manage extra usage for paid Claude plans | Claude Help Center
Manage extra usage for paid Claude plans | Claude Help Center
New Tab
New Tab
2 TB in 25 MB/s - Google Search
2 TB in 25 MB/s - Google Search
New Tab
New Tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
1
KB/s
198
B/s
Files
Control Panel
Storage
App Center
Logs
Support
Task Manager
Music
Cloud Drives
Theater
Photos
Online Office
TextEdit
Virtual Machine
Downloads
DLNA
File Version Explorer
Security
Jellyfin-HT
SAN Manager
Vault
Snapshot
Comics
Sync & Backup
UGREEN AI
Recycle Bin
Control Panel
Search
Connection & Access
User Management
File Service
Device Connection
Domain/LDAP
Terminal
General
Hardware & Power
Time & Language
Network
Security...
|
30494
|
NULL
|
NULL
|
NULL
|
|
48891
|
1747
|
11
|
2026-05-15T10:58:27.784442+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-15/1778 /Users/lukas/.screenpipe/data/data/2026-05-15/1778842707784_m2.jpg...
|
Firefox
|
[JY-20891] Sidekick SMS issue - Jira — Work
|
1
|
jiminny.atlassian.net/browse/JY-20891
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
[JY-20891] Sidekick SMS issue - Jira
[JY-20891] Sidekick SMS issue - Jira
[JY-20891] Sidekick SMS issue - Jira
[JY-20891] Sidekick SMS issue - Jira
Close tab
Usage | Windsurf
Usage | Windsurf
[SRD-6853] Moxso - Potential deal stages bug - Jira
[SRD-6853] Moxso - Potential deal stages bug - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
Feed — jiminny — Sentry
Feed — jiminny — Sentry
[JY-20906] Review of Pipedrive SDK - Jira
[JY-20906] Review of Pipedrive SDK - Jira
Pipedrive API Reference and Documentation
Pipedrive API Reference and Documentation
pipedrive/client-php: Pipedrive API client for PHP
pipedrive/client-php: Pipedrive API client for PHP
Fwd: Your text message failed to send - [EMAIL] - Jiminny Mail
Fwd: Your text message failed to send - [EMAIL] - Jiminny Mail
Pull requests · jiminny/app
Pull requests · jiminny/app
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to:
Sidebar
Sidebar
Top Bar
Top Bar
Main Content
Main Content
Collapse sidebar [
Collapse sidebar [
Switch sites or apps
Switch sites or apps
Go to your Jira homepage
Search, press enter to navigate to advanced search with your text query
Create
Create
Rovo Ask Rovo
Ask Rovo
Notifications
Notifications
Help
Help
Settings
Settings
[EMAIL]
[EMAIL]
For you
For you
Recent
Recent
Starred
Starred
Apps
Apps
More actions for Apps
More actions for Apps
Spaces
Spaces
Create space
Create space
More actions for spaces
More actions for spaces
Recent
Jiminny (New)
Jiminny (New)
Jiminny (New)
Create board
Create board
More actions for Jiminny (New)
More actions for Jiminny (New)
Platform Team
Platform Team
Board actions
Board actions
Capture Team
Capture Team
Board actions
Board actions
Enterprise Stability Issues 🤕
Enterprise Stability Issues 🤕
Board actions
Board actions
Processing Team
Processing Team
Board actions
Board actions
SE Kanban
SE Kanban
Board actions...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.22240691,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.2357048,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app","depth":4,"bounds":{"left":0.22240691,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app","depth":5,"bounds":{"left":0.2357048,"top":0.09577015,"width":0.15658244,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20891] Sidekick SMS issue - Jira","depth":4,"bounds":{"left":0.22240691,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20891] Sidekick SMS issue - Jira","depth":5,"bounds":{"left":0.2357048,"top":0.12849163,"width":0.064494684,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20891] Sidekick SMS issue - Jira","depth":4,"bounds":{"left":0.22240691,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"[JY-20891] Sidekick SMS issue - Jira","depth":5,"bounds":{"left":0.2357048,"top":0.16121309,"width":0.064494684,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.2897274,"top":0.15722266,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Usage | Windsurf","depth":4,"bounds":{"left":0.22240691,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Usage | Windsurf","depth":5,"bounds":{"left":0.2357048,"top":0.19393456,"width":0.029920213,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6853] Moxso - Potential deal stages bug - Jira","depth":4,"bounds":{"left":0.22240691,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6853] Moxso - Potential deal stages bug - Jira","depth":5,"bounds":{"left":0.2357048,"top":0.22665602,"width":0.09158909,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.22240691,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.2357048,"top":0.25937748,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.22240691,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"bounds":{"left":0.2357048,"top":0.29209897,"width":0.042719416,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20906] Review of Pipedrive SDK - Jira","depth":4,"bounds":{"left":0.22240691,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20906] Review of Pipedrive SDK - Jira","depth":5,"bounds":{"left":0.2357048,"top":0.32482043,"width":0.07413564,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipedrive API Reference and Documentation","depth":4,"bounds":{"left":0.22240691,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipedrive API Reference and Documentation","depth":5,"bounds":{"left":0.2357048,"top":0.3575419,"width":0.07679521,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"pipedrive/client-php: Pipedrive API client for PHP","depth":4,"bounds":{"left":0.22240691,"top":0.3790902,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"pipedrive/client-php: Pipedrive API client for PHP","depth":5,"bounds":{"left":0.2357048,"top":0.39026338,"width":0.08543883,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Fwd: Your text message failed to send - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.22240691,"top":0.41181165,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Fwd: Your text message failed to send - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"bounds":{"left":0.2357048,"top":0.42298484,"width":0.14095744,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pull requests · jiminny/app","depth":4,"bounds":{"left":0.22240691,"top":0.4445331,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests · jiminny/app","depth":5,"bounds":{"left":0.2357048,"top":0.4557063,"width":0.04537899,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":4,"bounds":{"left":0.22240691,"top":0.4772546,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":5,"bounds":{"left":0.2357048,"top":0.4884278,"width":0.15259309,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.2252327,"top":0.51157224,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.2252327,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.23620346,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.24734043,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.2584774,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.26961437,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to:","depth":9,"bounds":{"left":0.31266624,"top":0.07861133,"width":0.016954787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sidebar","depth":10,"bounds":{"left":0.31266624,"top":0.097765364,"width":0.016954787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sidebar","depth":11,"bounds":{"left":0.31266624,"top":0.097765364,"width":0.016954787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Top Bar","depth":10,"bounds":{"left":0.31266624,"top":0.11691939,"width":0.016954787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Top Bar","depth":11,"bounds":{"left":0.31266624,"top":0.11691939,"width":0.016954787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Main Content","depth":10,"bounds":{"left":0.31266624,"top":0.13607343,"width":0.029421542,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Main Content","depth":11,"bounds":{"left":0.31266624,"top":0.13607343,"width":0.029421542,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Collapse sidebar [","depth":9,"bounds":{"left":0.30601728,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Collapse sidebar [","depth":11,"bounds":{"left":0.31117022,"top":0.06344773,"width":0.039727394,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Switch sites or apps","depth":10,"bounds":{"left":0.3179854,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Switch sites or apps","depth":12,"bounds":{"left":0.3231383,"top":0.06344773,"width":0.044215426,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Go to your Jira homepage","depth":9,"bounds":{"left":0.33128324,"top":0.057861134,"width":0.029421542,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXComboBox","text":"Search, press enter to navigate to advanced search with your text query","depth":11,"bounds":{"left":0.5159575,"top":0.06264964,"width":0.24268617,"height":0.015961692},"on_screen":true,"help_text":"","placeholder":"Search","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Create","depth":10,"bounds":{"left":0.7669548,"top":0.057861134,"width":0.030086435,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create","depth":12,"bounds":{"left":0.77825797,"top":0.06384677,"width":0.014793883,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Rovo Ask Rovo","depth":12,"bounds":{"left":0.91223407,"top":0.057861134,"width":0.035904255,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Rovo","depth":14,"bounds":{"left":0.92353725,"top":0.06384677,"width":0.020611702,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Notifications","depth":12,"bounds":{"left":0.9494681,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notifications","depth":14,"bounds":{"left":0.954621,"top":0.06344773,"width":0.027759308,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Help","depth":12,"bounds":{"left":0.96143615,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Help","depth":14,"bounds":{"left":0.9665891,"top":0.06344773,"width":0.010139627,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Settings","depth":12,"bounds":{"left":0.9734042,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Settings","depth":14,"bounds":{"left":0.97855717,"top":0.06344773,"width":0.017952127,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"lukas.kovalik@jiminny.com","depth":12,"bounds":{"left":0.98537236,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"lukas.kovalik@jiminny.com","depth":14,"bounds":{"left":0.99052525,"top":0.06344773,"width":0.009474754,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"For you","depth":12,"bounds":{"left":0.30601728,"top":0.09976058,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"For you","depth":15,"bounds":{"left":0.31665558,"top":0.10574621,"width":0.01662234,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Recent","depth":12,"bounds":{"left":0.30601728,"top":0.12529927,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Recent","depth":15,"bounds":{"left":0.31665558,"top":0.13128492,"width":0.015458777,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Starred","depth":12,"bounds":{"left":0.30601728,"top":0.15083799,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Starred","depth":15,"bounds":{"left":0.31665558,"top":0.15682362,"width":0.016456118,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Apps","depth":12,"bounds":{"left":0.30601728,"top":0.1763767,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Apps","depth":15,"bounds":{"left":0.31665558,"top":0.18236233,"width":0.011635638,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Apps","depth":13,"bounds":{"left":0.37549868,"top":0.17956904,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Apps","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Spaces","depth":12,"bounds":{"left":0.30601728,"top":0.2019154,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"Spaces","depth":15,"bounds":{"left":0.31665558,"top":0.20790103,"width":0.016456118,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create space","depth":13,"bounds":{"left":0.35887632,"top":0.20510775,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create space","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for spaces","depth":13,"bounds":{"left":0.36818483,"top":0.20510775,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for spaces","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Recent","depth":16,"bounds":{"left":0.31200132,"top":0.23423783,"width":0.013464096,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Jiminny (New)","depth":17,"bounds":{"left":0.31000665,"top":0.2529928,"width":0.0674867,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny (New)","depth":20,"bounds":{"left":0.32064494,"top":0.25897846,"width":0.032081116,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Jiminny (New)","depth":18,"bounds":{"left":0.31133643,"top":0.25618514,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXMenuButton","text":"Create board","depth":18,"bounds":{"left":0.35887632,"top":0.25618514,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Create board","depth":20,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Jiminny (New)","depth":18,"bounds":{"left":0.36818483,"top":0.25618514,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Jiminny (New)","depth":20,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Platform Team","depth":19,"bounds":{"left":0.31399602,"top":0.27853152,"width":0.06349734,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Team","depth":22,"bounds":{"left":0.3246343,"top":0.28451717,"width":0.032247342,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"bounds":{"left":0.37549868,"top":0.28172386,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Capture Team","depth":19,"bounds":{"left":0.31399602,"top":0.30407023,"width":0.06349734,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Capture Team","depth":22,"bounds":{"left":0.3246343,"top":0.31005585,"width":0.03125,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"bounds":{"left":0.37549868,"top":0.30726257,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Enterprise Stability Issues 🤕","depth":19,"bounds":{"left":0.31399602,"top":0.32960895,"width":0.06349734,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Enterprise Stability Issues 🤕","depth":22,"bounds":{"left":0.3246343,"top":0.33559456,"width":0.050531916,"height":0.030726258},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"bounds":{"left":0.37549868,"top":0.33280128,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Processing Team","depth":19,"bounds":{"left":0.31399602,"top":0.35514766,"width":0.06349734,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Processing Team","depth":22,"bounds":{"left":0.3246343,"top":0.36113328,"width":0.038231384,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"bounds":{"left":0.37549868,"top":0.35834,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"SE Kanban","depth":19,"bounds":{"left":0.31399602,"top":0.38068634,"width":0.06349734,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SE Kanban","depth":22,"bounds":{"left":0.3246343,"top":0.386672,"width":0.024102394,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"bounds":{"left":0.37549868,"top":0.38387868,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-9088368306191267242
|
1176952070663491695
|
click
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
[JY-20891] Sidekick SMS issue - Jira
[JY-20891] Sidekick SMS issue - Jira
[JY-20891] Sidekick SMS issue - Jira
[JY-20891] Sidekick SMS issue - Jira
Close tab
Usage | Windsurf
Usage | Windsurf
[SRD-6853] Moxso - Potential deal stages bug - Jira
[SRD-6853] Moxso - Potential deal stages bug - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
Feed — jiminny — Sentry
Feed — jiminny — Sentry
[JY-20906] Review of Pipedrive SDK - Jira
[JY-20906] Review of Pipedrive SDK - Jira
Pipedrive API Reference and Documentation
Pipedrive API Reference and Documentation
pipedrive/client-php: Pipedrive API client for PHP
pipedrive/client-php: Pipedrive API client for PHP
Fwd: Your text message failed to send - [EMAIL] - Jiminny Mail
Fwd: Your text message failed to send - [EMAIL] - Jiminny Mail
Pull requests · jiminny/app
Pull requests · jiminny/app
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to:
Sidebar
Sidebar
Top Bar
Top Bar
Main Content
Main Content
Collapse sidebar [
Collapse sidebar [
Switch sites or apps
Switch sites or apps
Go to your Jira homepage
Search, press enter to navigate to advanced search with your text query
Create
Create
Rovo Ask Rovo
Ask Rovo
Notifications
Notifications
Help
Help
Settings
Settings
[EMAIL]
[EMAIL]
For you
For you
Recent
Recent
Starred
Starred
Apps
Apps
More actions for Apps
More actions for Apps
Spaces
Spaces
Create space
Create space
More actions for spaces
More actions for spaces
Recent
Jiminny (New)
Jiminny (New)
Jiminny (New)
Create board
Create board
More actions for Jiminny (New)
More actions for Jiminny (New)
Platform Team
Platform Team
Board actions
Board actions
Capture Team
Capture Team
Board actions
Board actions
Enterprise Stability Issues 🤕
Enterprise Stability Issues 🤕
Board actions
Board actions
Processing Team
Processing Team
Board actions
Board actions
SE Kanban
SE Kanban
Board actions...
|
48889
|
NULL
|
NULL
|
NULL
|
|
49689
|
1766
|
78
|
2026-05-18T06:52:31.080746+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779087151080_m1.jpg...
|
PhpStorm
|
faVsco.js – TestPipedriveOfficialSdkCommand.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Notion CalendarEditViewWindowHelp> 0.Daily - Pl Notion CalendarEditViewWindowHelp> 0.Daily - Platform • nowmeet.google.com/agt-teir-cwt?authuser=lukas.kovalik%40jiminny.comNikolay Yankov (Presenting)100% C8• Mon 18 May 9:52:30006+*04008• Mon 18 May 9:52* Prо0 Alten:E3 MCP x [ Sche x Curso x | * Ciauo x Costi x Jmin x Q MCP3 Pipel@ Smin|© Calen|[URL_WITH_CREDENTIALS] aulo "gerereieo as Teotecoand by Peny, ea vaetean rcectoal or Peany e bo ed dete a le ele unmeied an rond cel remCabguege string b0/ 47langaage code of the can, eo. "engoo, es,es. Nui & langurge devection was not avaiaolouretto sesonos meet toa ourtnos onee cotu Besono"Steliyan GeorgievNikolay YankovNikolay IvanovAneliya AngelovaLukas Kovalik9:52 AM | Daily - Platform6:05...
|
NULL
|
-9088235303638905982
|
NULL
|
visual_change
|
ocr
|
NULL
|
Notion CalendarEditViewWindowHelp> 0.Daily - Pl Notion CalendarEditViewWindowHelp> 0.Daily - Platform • nowmeet.google.com/agt-teir-cwt?authuser=lukas.kovalik%40jiminny.comNikolay Yankov (Presenting)100% C8• Mon 18 May 9:52:30006+*04008• Mon 18 May 9:52* Prо0 Alten:E3 MCP x [ Sche x Curso x | * Ciauo x Costi x Jmin x Q MCP3 Pipel@ Smin|© Calen|[URL_WITH_CREDENTIALS] aulo "gerereieo as Teotecoand by Peny, ea vaetean rcectoal or Peany e bo ed dete a le ele unmeied an rond cel remCabguege string b0/ 47langaage code of the can, eo. "engoo, es,es. Nui & langurge devection was not avaiaolouretto sesonos meet toa ourtnos onee cotu Besono"Steliyan GeorgievNikolay YankovNikolay IvanovAneliya AngelovaLukas Kovalik9:52 AM | Daily - Platform6:05...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
8257
|
366
|
3
|
2026-05-08T10:15:46.955796+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-08/1778 /Users/lukas/.screenpipe/data/data/2026-05-08/1778235346955_m2.jpg...
|
PhpStorm
|
faVsco.js – Client.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
It's the only usage in Project Files
Find Opti It's the only usage in Project Files
Find Options… (⌃⌥⌘N)
text/html
text/html
text/html
text/html
text/html
Project: faVsco.js, menu
master, 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
3
69
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
public function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
public function parseRetryAfter(Throwable $e): int
{
// First try to get Retry-After from response headers
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
// For search APIs, headers are often missing - check response body for policyName
if (method_exists($e, 'getResponseBody')) {
$body = $e->getResponseBody();
if (is_string($body)) {
$body = json_decode($body, true) ?? [];
}
$policyName = $body['policyName'] ?? $body['policy'] ?? null;
// Map policy names to retry delays
if ($policyName === 'TEN_SECONDLY_ROLLING' || $policyName === 'ten_secondly_rolling') {
return 10;
}
if ($policyName === 'SECONDLY' || $policyName === 'secondly') {
return 1;
}
}
// Also check nested context object if present
if (method_exists($e, 'getResponseBody')) {
$body = $e->getResponseBody();
if (is_string($body)) {
$body = json_decode($body, true) ?? [];
}
$context = $body['context'] ?? [];
$policyName = $context['policyName'] ?? null;
if ($policyName === 'TEN_SECONDLY_ROLLING' || $policyName === 'ten_secondly_rolling') {
return 10;
}
if ($policyName === 'SECONDLY' || $policyName === 'secondly') {
return 1;
}
}
$this->log->debug('[Hubspot] No retry-after header or policy name found, using default', [
'exception_class' => get_class($e),
]);
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* Execute a search request against HubSpot CRM objects with rate limiting.
*
* @param string $objectType The object type ('deals', 'companies', 'contacts', 'calls')
* @param array<string, mixed> $payload The search payload with filters, sorts, properties, etc.
* @return array The search response with 'results', 'total', 'paging' keys
* @throws RateLimitException When rate limit is hit
* @throws HubspotException On API errors
*/
public function search(string $objectType, array $payload): array
{
$endpoint = self::BASE_URL . "/crm/v3/objects/{$objectType}/search";
return $this->executeRequest(function () use ($endpoint, $payload) {
$response = $this->getInstance()->getClient()->request('POST', $endpoint, ['json' => $payload]);
return $response->toArray();
});
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
// $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$deal = $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
);
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"It's the only usage in Project Files\nFind Options… (⌃⌥⌘N)","depth":2,"bounds":{"left":0.17885639,"top":0.38467678,"width":0.06815159,"height":0.027134877},"on_screen":true,"value":"It's the only usage in Project Files\nFind Options… (⌃⌥⌘N)","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":3,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":3,"bounds":{"left":0.17885639,"top":0.38467678,"width":0.06815159,"height":0.013567438},"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":3,"bounds":{"left":0.17885639,"top":0.3982442,"width":0.029587766,"height":0.013567438},"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":3,"bounds":{"left":0.20844415,"top":0.3982442,"width":0.019946808,"height":0.013567438},"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":3,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"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":"master, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.040226065,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master<br/>Some incoming commits are not fetched<br/>","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":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.33776596,"top":0.12529927,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"69","depth":4,"bounds":{"left":0.34773937,"top":0.12529927,"width":0.010305851,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.3600399,"top":0.12529927,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.36901596,"top":0.123703115,"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.37632978,"top":0.123703115,"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\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n public function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n public function parseRetryAfter(Throwable $e): int\n {\n // First try to get Retry-After from response headers\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n // For search APIs, headers are often missing - check response body for policyName\n if (method_exists($e, 'getResponseBody')) {\n $body = $e->getResponseBody();\n if (is_string($body)) {\n $body = json_decode($body, true) ?? [];\n }\n $policyName = $body['policyName'] ?? $body['policy'] ?? null;\n\n // Map policy names to retry delays\n if ($policyName === 'TEN_SECONDLY_ROLLING' || $policyName === 'ten_secondly_rolling') {\n return 10;\n }\n if ($policyName === 'SECONDLY' || $policyName === 'secondly') {\n return 1;\n }\n }\n\n // Also check nested context object if present\n if (method_exists($e, 'getResponseBody')) {\n $body = $e->getResponseBody();\n if (is_string($body)) {\n $body = json_decode($body, true) ?? [];\n }\n $context = $body['context'] ?? [];\n $policyName = $context['policyName'] ?? null;\n\n if ($policyName === 'TEN_SECONDLY_ROLLING' || $policyName === 'ten_secondly_rolling') {\n return 10;\n }\n if ($policyName === 'SECONDLY' || $policyName === 'secondly') {\n return 1;\n }\n }\n\n $this->log->debug('[Hubspot] No retry-after header or policy name found, using default', [\n 'exception_class' => get_class($e),\n ]);\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * Execute a search request against HubSpot CRM objects with rate limiting.\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts', 'calls')\n * @param array<string, mixed> $payload The search payload with filters, sorts, properties, etc.\n * @return array The search response with 'results', 'total', 'paging' keys\n * @throws RateLimitException When rate limit is hit\n * @throws HubspotException On API errors\n */\n public function search(string $objectType, array $payload): array\n {\n $endpoint = self::BASE_URL . \"/crm/v3/objects/{$objectType}/search\";\n\n return $this->executeRequest(function () use ($endpoint, $payload) {\n $response = $this->getInstance()->getClient()->request('POST', $endpoint, ['json' => $payload]);\n\n return $response->toArray();\n });\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n// $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $deal = $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n );\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n public function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n public function parseRetryAfter(Throwable $e): int\n {\n // First try to get Retry-After from response headers\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n // For search APIs, headers are often missing - check response body for policyName\n if (method_exists($e, 'getResponseBody')) {\n $body = $e->getResponseBody();\n if (is_string($body)) {\n $body = json_decode($body, true) ?? [];\n }\n $policyName = $body['policyName'] ?? $body['policy'] ?? null;\n\n // Map policy names to retry delays\n if ($policyName === 'TEN_SECONDLY_ROLLING' || $policyName === 'ten_secondly_rolling') {\n return 10;\n }\n if ($policyName === 'SECONDLY' || $policyName === 'secondly') {\n return 1;\n }\n }\n\n // Also check nested context object if present\n if (method_exists($e, 'getResponseBody')) {\n $body = $e->getResponseBody();\n if (is_string($body)) {\n $body = json_decode($body, true) ?? [];\n }\n $context = $body['context'] ?? [];\n $policyName = $context['policyName'] ?? null;\n\n if ($policyName === 'TEN_SECONDLY_ROLLING' || $policyName === 'ten_secondly_rolling') {\n return 10;\n }\n if ($policyName === 'SECONDLY' || $policyName === 'secondly') {\n return 1;\n }\n }\n\n $this->log->debug('[Hubspot] No retry-after header or policy name found, using default', [\n 'exception_class' => get_class($e),\n ]);\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * Execute a search request against HubSpot CRM objects with rate limiting.\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts', 'calls')\n * @param array<string, mixed> $payload The search payload with filters, sorts, properties, etc.\n * @return array The search response with 'results', 'total', 'paging' keys\n * @throws RateLimitException When rate limit is hit\n * @throws HubspotException On API errors\n */\n public function search(string $objectType, array $payload): array\n {\n $endpoint = self::BASE_URL . \"/crm/v3/objects/{$objectType}/search\";\n\n return $this->executeRequest(function () use ($endpoint, $payload) {\n $response = $this->getInstance()->getClient()->request('POST', $endpoint, ['json' => $payload]);\n\n return $response->toArray();\n });\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n// $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $deal = $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n );\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\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.3849734,"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.39361703,"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.40458778,"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.41323137,"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.421875,"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}]...
|
-9087375101361107975
|
5225837878612723812
|
click
|
accessibility
|
NULL
|
It's the only usage in Project Files
Find Opti It's the only usage in Project Files
Find Options… (⌃⌥⌘N)
text/html
text/html
text/html
text/html
text/html
Project: faVsco.js, menu
master, 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
3
69
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
public function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
public function parseRetryAfter(Throwable $e): int
{
// First try to get Retry-After from response headers
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
// For search APIs, headers are often missing - check response body for policyName
if (method_exists($e, 'getResponseBody')) {
$body = $e->getResponseBody();
if (is_string($body)) {
$body = json_decode($body, true) ?? [];
}
$policyName = $body['policyName'] ?? $body['policy'] ?? null;
// Map policy names to retry delays
if ($policyName === 'TEN_SECONDLY_ROLLING' || $policyName === 'ten_secondly_rolling') {
return 10;
}
if ($policyName === 'SECONDLY' || $policyName === 'secondly') {
return 1;
}
}
// Also check nested context object if present
if (method_exists($e, 'getResponseBody')) {
$body = $e->getResponseBody();
if (is_string($body)) {
$body = json_decode($body, true) ?? [];
}
$context = $body['context'] ?? [];
$policyName = $context['policyName'] ?? null;
if ($policyName === 'TEN_SECONDLY_ROLLING' || $policyName === 'ten_secondly_rolling') {
return 10;
}
if ($policyName === 'SECONDLY' || $policyName === 'secondly') {
return 1;
}
}
$this->log->debug('[Hubspot] No retry-after header or policy name found, using default', [
'exception_class' => get_class($e),
]);
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* Execute a search request against HubSpot CRM objects with rate limiting.
*
* @param string $objectType The object type ('deals', 'companies', 'contacts', 'calls')
* @param array<string, mixed> $payload The search payload with filters, sorts, properties, etc.
* @return array The search response with 'results', 'total', 'paging' keys
* @throws RateLimitException When rate limit is hit
* @throws HubspotException On API errors
*/
public function search(string $objectType, array $payload): array
{
$endpoint = self::BASE_URL . "/crm/v3/objects/{$objectType}/search";
return $this->executeRequest(function () use ($endpoint, $payload) {
$response = $this->getInstance()->getClient()->request('POST', $endpoint, ['json' => $payload]);
return $response->toArray();
});
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
// $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$deal = $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
);
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
19445
|
830
|
21
|
2026-05-11T12:51:25.403236+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778503885403_m1.jpg...
|
Firefox
|
Pipelines - jiminny/app — Work
|
1
|
app.circleci.com/pipelines/github/jiminny/app
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
New Tab
New Tab
Jy 20820 es reindex stream model h New Tab
New Tab
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Close bookmarks (⌘B)
Bookmarks
Bookmarks
Close sidebar
Search bookmarks
Go to home page
Auto theme
Open notifications
Open support menu
Open user menu
org avatar Current organization: jiminny
Home
Home
Pipelines
Pipelines
Projects
Projects
Deploys
Deploys
Insights
Insights
Runners
Runners
Org
Org
Plan
Plan
Chunk sidecars
Chunk sidecars
PREVIEW
Chunk
Chunk
Dashboard All Pipelines
All Pipelines
Project Outline app
app
app
app
Overview
Overview
Settings
Settings
Deploys
Deploys
Lightning Manage triggers
Manage triggers
Trigger Pipeline
Pipelines All pipelines my-pipelines-filter...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.18715277,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.21006945,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.23333333,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.25659722,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close bookmarks (⌘B)","depth":6,"bounds":{"left":0.27986112,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Bookmarks","depth":5,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Bookmarks","depth":6,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXTextField","text":"Search bookmarks","depth":7,"on_screen":true,"help_text":"","role_description":"search text field","subrole":"AXSearchField","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Go to home page","depth":9,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Auto theme","depth":9,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open notifications","depth":9,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Open support menu","depth":9,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Open user menu","depth":9,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"org avatar Current organization: jiminny","depth":9,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Home","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Home","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pipelines","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Projects","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Projects","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Deploys","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Deploys","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Runners","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Runners","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Org","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Org","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Plan","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Plan","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chunk sidecars","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Chunk sidecars","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PREVIEW","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chunk","depth":10,"bounds":{"left":0.5708333,"top":0.0,"width":0.06111111,"height":0.064444445},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Chunk","depth":12,"bounds":{"left":0.5868056,"top":0.0,"width":0.029166667,"height":0.019444445},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Dashboard All Pipelines","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"All Pipelines","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Project Outline app","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"app","depth":13,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Overview","depth":13,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Overview","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":13,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Deploys","depth":13,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Deploys","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Lightning Manage triggers","depth":13,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Manage triggers","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Trigger Pipeline","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Pipelines All pipelines my-pipelines-filter","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-9087326056384412723
|
-2912693701577240446
|
idle
|
accessibility
|
NULL
|
New Tab
New Tab
Jy 20820 es reindex stream model h New Tab
New Tab
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Close bookmarks (⌘B)
Bookmarks
Bookmarks
Close sidebar
Search bookmarks
Go to home page
Auto theme
Open notifications
Open support menu
Open user menu
org avatar Current organization: jiminny
Home
Home
Pipelines
Pipelines
Projects
Projects
Deploys
Deploys
Insights
Insights
Runners
Runners
Org
Org
Plan
Plan
Chunk sidecars
Chunk sidecars
PREVIEW
Chunk
Chunk
Dashboard All Pipelines
All Pipelines
Project Outline app
app
app
app
Overview
Overview
Settings
Settings
Deploys
Deploys
Lightning Manage triggers
Manage triggers
Trigger Pipeline
Pipelines All pipelines my-pipelines-filter...
|
19443
|
NULL
|
NULL
|
NULL
|
|
25411
|
1067
|
43
|
2026-05-12T11:18:06.776490+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-12/1778 /Users/lukas/.screenpipe/data/data/2026-05-12/1778584686776_m2.jpg...
|
Firefox
|
[JY-20773] User Pilot not receiving events on repo [JY-20773] User Pilot not receiving events on report generated - Jira — Work...
|
1
|
jiminny.atlassian.net/browse/JY-20773
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Unnamed Group
TypeError: League\Flysystem\Filesyst Unnamed Group
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
CloudWatch | us-east-2
CloudWatch | us-east-2
Pipelines - jiminny/app
Pipelines - jiminny/app
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
[JY-20773] User Pilot not receiving events on report generated - Jira
[JY-20773] User Pilot not receiving events on report generated - Jira
JY-20773 fix user pilot tracking for automated report generated by LakyLak · Pull Request #12024 · jiminny/app
JY-20773 fix user pilot tracking for automated report generated by LakyLak · Pull Request #12024 · jiminny/app
[JY-20776] Automated report - sentry - Jira
[JY-20776] Automated report - sentry - Jira
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
Platform Team - Backlog - Jira
Platform Team - Backlog - Jira
[JY-20773] User Pilot not receiving events on report generated - Jira
[JY-20773] User Pilot not receiving events on report generated - Jira
Close tab
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to:
Sidebar
Sidebar
Top Bar
Top Bar
Main Content
Main Content
Collapse sidebar [
Collapse sidebar [
Switch sites or apps
Switch sites or apps
Go to your Jira homepage
Search, press enter to navigate to advanced search with your text query
Create
Create
Rovo Ask Rovo
Ask Rovo
Notifications
Notifications
Help
Help
Settings
Settings
[EMAIL]
[EMAIL]
For you
For you
Recent
Recent
Starred
Starred
Apps
Apps
More actions for Apps
More actions for Apps
Spaces
Spaces
Create space
Create space
More actions for spaces
More actions for spaces
Recent
Jiminny (New)
Jiminny (New)
Jiminny (New)
Create board
Create board
More actions for Jiminny (New)
More actions for Jiminny (New)
Platform Team
Platform Team
Board actions
Board actions
Capture Team
Capture Team
Board actions
Board actions
Enterprise Stability Issues 🤕
Enterprise Stability Issues 🤕
Board actions
Board actions
Processing Team
Processing Team
Board actions
Board actions
SE Kanban
SE Kanban
Board actions
Board actions
Service-Desk
Service-Desk
More actions for Service-Desk
More actions for Service-Desk
More spaces
More spaces
Filters
Filters
More actions for Filters
More actions for Filters
Dashboards
Dashboards
Create dashboard
Create dashboard
More actions for Dashboards
More actions for Dashboards
Operations
Operations
More actions for Operations
More actions for Operations
Confluence , (opens new window)
Confluence
, (opens new window)
Teams , (opens new window)
Teams
, (opens new window)
open menu
open menu
Customise sidebar
Customise sidebar
Resize side navigation panel
Spaces
Spaces
/
Jiminny (New) Jiminny (New)
Jiminny (New)
/
Epic - Change parent
JY-19240
JY-19240
/
Bug - Change work type
JY-20773
JY-20773
Copy link
User Pilot not receiving events on report generated- Summary, edit
User Pilot not receiving events on report generated
User Pilot not receiving events on report generated
Add or create work related to this Bug
Add or create work related to this Bug
View app actions
View app actions
Collapse Key details Key details
Collapse Key details
Collapse Key details
Key details
Description
Description
Edit Description, edit
When new report is generated User Pilot should receive tracking event. The event is still in pending meaning no new event has arrived.
Steps to reproduce
Steps to reproduce
More information about
Edit Steps to reproduce, edit
None
Actual outcome
More information about
Edit Actual outcome
Add text
Expected outcome
More information about
Edit Expected outcome
Add text
Subtasks
Subtasks
Add subtask
Add subtask
Linked work items
Linked work items
Add linked work item
Add linked work item
Collapse Activity Activity
Collapse Activity
Collapse Activity
Activity
All
All
Comments
Comments
History
History
Work log
Work log
Atlassian Intelligence Summarise
Summarise
Newest first Newest first
Newest first
Add a comment…
Suggest a reply...
Suggest a reply...
Status update...
Status update...
Thanks...
Thanks...
Pro tip:
press
M
to comment
More information about Lukas Kovalik
More information about Lukas Kovalik
Lukas Kovalik
Copy link to comment
4 hours ago
The issue is most probably AutomatedReportGenerated event and not listener sending data to UP. The PR should address it.
Reply
Add thumbs up reaction
Add reaction
Edit
More actions
Resize work item view side panel
Watch options: You are watching this issue, 1 person watching
1
Share
Share
Actions
Actions
Backlog - Change status
Backlog
Automation
Automation
Improve Bug
Improve Bug
Details
Details
Details
Assignee
Assignee Pin to top. Only you can see pinned fields.
Lukas Kovalik- edit Assignee
More information about Lukas Kovalik
Lukas Kovalik
Reporter
Reporter Pin to top. Only you can see pinned fields.
Lukas Kovalik- edit Reporter
More information about Lukas Kovalik
Lukas Kovalik
Development
Development Pin to top. Only you can see pinned fields.
Open with VS Code Link to external website in new tab
Open with VS Code
1 branch
1
branch
Open branch
Create new branch
1 commit 14 days ago
1
commit
14 days ago
Open commit
Create new commit
1 pull request OPEN
1
pull request
OPEN
Open pull request
Create new pull request
Labels
Labels Pin to top. Only you can see pinned fields.
Edit Labels
None
Sub-Product
Sub-Product Pin to top. Only you can see pinned fields.
Edit Sub-Product, Add options selected, edit
Add options
Story Points
More information about
Story Points Pin to top. Only you can see pinned fields.
Edit Story Points
2
Organisations
More information about Organisations
Organisations Pin to top. Only you can see pinned fields.
Edit Organisations, None selected, edit
None
Components
Components Pin to top. Only you can see pinned fields.
Edit Components, edit
Platform
Platform
Fix versions
Fix versions Pin to top. Only you can see pinned fields.
Edit Fix versions, None selected, edit
None
Parent
More information about
Parent Pin to top. Only you can see pinned fields.
Edit Parent
Click to visit JY-19240 AJ Reports
JY-19240 AJ Reports
Sprint
More information about
Sprint Pin to top. Only you can see pinned fields.
Edit Sprint
Platform Sprint 4 Q2
Platform Sprint 4 Q2
Priority
Priority Pin to top. Only you can see pinned fields.
Edit Priority
Medium...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.0028257978,"top":0.057063047,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"TypeError: League\\Flysystem\\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app","depth":4,"bounds":{"left":0.0028257978,"top":0.08060654,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"TypeError: League\\Flysystem\\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app","depth":5,"bounds":{"left":0.015957447,"top":0.09217877,"width":0.40492022,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.11332801,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.12490024,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.14604948,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.15762171,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira","depth":4,"bounds":{"left":0.0,"top":0.17877094,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.19034317,"width":0.16140293,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.21149242,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.22306465,"width":0.18816489,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20773] User Pilot not receiving events on report generated - Jira","depth":4,"bounds":{"left":0.0,"top":0.2442139,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20773] User Pilot not receiving events on report generated - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25578612,"width":0.1200133,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20773 fix user pilot tracking for automated report generated by LakyLak · Pull Request #12024 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.27693537,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20773 fix user pilot tracking for automated report generated by LakyLak · Pull Request #12024 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.28850758,"width":0.1931516,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20776] Automated report - sentry - Jira","depth":4,"bounds":{"left":0.0,"top":0.30965683,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20776] Automated report - sentry - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.32122904,"width":0.07646277,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3423783,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.35395053,"width":0.18816489,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":4,"bounds":{"left":0.0,"top":0.37509975,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.386672,"width":0.09524601,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Team - Backlog - Jira","depth":4,"bounds":{"left":0.0,"top":0.40782124,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Team - Backlog - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.41939345,"width":0.053025264,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20773] User Pilot not receiving events on report generated - Jira","depth":4,"bounds":{"left":0.0,"top":0.4405427,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"[JY-20773] User Pilot not receiving events on report generated - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.4521149,"width":0.1200133,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.44772545,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.47326416,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.4848364,"width":0.18816489,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.50758183,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to:","depth":9,"bounds":{"left":0.090259306,"top":0.07861133,"width":0.016954787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sidebar","depth":10,"bounds":{"left":0.090259306,"top":0.097765364,"width":0.016954787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sidebar","depth":11,"bounds":{"left":0.090259306,"top":0.097765364,"width":0.016954787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Top Bar","depth":10,"bounds":{"left":0.090259306,"top":0.11691939,"width":0.016954787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Top Bar","depth":11,"bounds":{"left":0.090259306,"top":0.11691939,"width":0.016954787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Main Content","depth":10,"bounds":{"left":0.090259306,"top":0.13607343,"width":0.029421542,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Main Content","depth":11,"bounds":{"left":0.090259306,"top":0.13607343,"width":0.029421542,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Collapse sidebar [","depth":9,"bounds":{"left":0.08361037,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Collapse sidebar [","depth":11,"bounds":{"left":0.0887633,"top":0.06344773,"width":0.039727394,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Switch sites or apps","depth":10,"bounds":{"left":0.095578454,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Switch sites or apps","depth":12,"bounds":{"left":0.10073138,"top":0.06344773,"width":0.044215426,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Go to your Jira homepage","depth":9,"bounds":{"left":0.10887633,"top":0.057861134,"width":0.029421542,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXComboBox","text":"Search, press enter to navigate to advanced search with your text query","depth":11,"bounds":{"left":0.20694813,"top":0.06264964,"width":0.13829787,"height":0.015961692},"on_screen":true,"help_text":"","placeholder":"Search","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Create","depth":10,"bounds":{"left":0.35355717,"top":0.057861134,"width":0.030086435,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create","depth":12,"bounds":{"left":0.3648604,"top":0.06384677,"width":0.014793883,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Rovo Ask Rovo","depth":12,"bounds":{"left":0.41206783,"top":0.057861134,"width":0.036070477,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Rovo","depth":14,"bounds":{"left":0.42337102,"top":0.06384677,"width":0.020777926,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Notifications","depth":12,"bounds":{"left":0.44946808,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notifications","depth":14,"bounds":{"left":0.45462102,"top":0.06344773,"width":0.027759308,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Help","depth":12,"bounds":{"left":0.46143618,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Help","depth":14,"bounds":{"left":0.4665891,"top":0.06344773,"width":0.010139627,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Settings","depth":12,"bounds":{"left":0.47340426,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Settings","depth":14,"bounds":{"left":0.47855717,"top":0.06344773,"width":0.017952127,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"lukas.kovalik@jiminny.com","depth":12,"bounds":{"left":0.48537233,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"lukas.kovalik@jiminny.com","depth":14,"bounds":{"left":0.49052528,"top":0.06344773,"width":0.05867686,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"For you","depth":12,"bounds":{"left":0.08361037,"top":0.09976058,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"For you","depth":15,"bounds":{"left":0.09424867,"top":0.10574621,"width":0.01662234,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Recent","depth":12,"bounds":{"left":0.08361037,"top":0.12529927,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Recent","depth":15,"bounds":{"left":0.09424867,"top":0.13128492,"width":0.015458777,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Starred","depth":12,"bounds":{"left":0.08361037,"top":0.15083799,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Starred","depth":15,"bounds":{"left":0.09424867,"top":0.15682362,"width":0.016456118,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Apps","depth":12,"bounds":{"left":0.08361037,"top":0.1763767,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Apps","depth":15,"bounds":{"left":0.09424867,"top":0.18236233,"width":0.011635638,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Apps","depth":13,"bounds":{"left":0.15309176,"top":0.17956904,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Apps","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Spaces","depth":12,"bounds":{"left":0.08361037,"top":0.2019154,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"Spaces","depth":15,"bounds":{"left":0.09424867,"top":0.20790103,"width":0.016456118,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create space","depth":13,"bounds":{"left":0.13646941,"top":0.20510775,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create space","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for spaces","depth":13,"bounds":{"left":0.14577793,"top":0.20510775,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for spaces","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Recent","depth":16,"bounds":{"left":0.08959442,"top":0.23423783,"width":0.013464096,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Jiminny (New)","depth":17,"bounds":{"left":0.08759973,"top":0.2529928,"width":0.0674867,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny (New)","depth":20,"bounds":{"left":0.09823803,"top":0.25897846,"width":0.032081116,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Jiminny (New)","depth":18,"bounds":{"left":0.08892952,"top":0.25618514,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXMenuButton","text":"Create board","depth":18,"bounds":{"left":0.13646941,"top":0.25618514,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Create board","depth":20,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Jiminny (New)","depth":18,"bounds":{"left":0.14577793,"top":0.25618514,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Jiminny (New)","depth":20,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Platform Team","depth":19,"bounds":{"left":0.09158909,"top":0.27853152,"width":0.06349734,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Team","depth":22,"bounds":{"left":0.1022274,"top":0.28451717,"width":0.032247342,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"bounds":{"left":0.15309176,"top":0.28172386,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Capture Team","depth":19,"bounds":{"left":0.09158909,"top":0.30407023,"width":0.06349734,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Capture Team","depth":22,"bounds":{"left":0.1022274,"top":0.31005585,"width":0.03125,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"bounds":{"left":0.15309176,"top":0.30726257,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Enterprise Stability Issues 🤕","depth":19,"bounds":{"left":0.09158909,"top":0.32960895,"width":0.06349734,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Enterprise Stability Issues 🤕","depth":22,"bounds":{"left":0.1022274,"top":0.33559456,"width":0.050531916,"height":0.030726258},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"bounds":{"left":0.15309176,"top":0.33280128,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Processing Team","depth":19,"bounds":{"left":0.09158909,"top":0.35514766,"width":0.06349734,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Processing Team","depth":22,"bounds":{"left":0.1022274,"top":0.36113328,"width":0.038231384,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"bounds":{"left":0.15309176,"top":0.35834,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"SE Kanban","depth":19,"bounds":{"left":0.09158909,"top":0.38068634,"width":0.06349734,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SE Kanban","depth":22,"bounds":{"left":0.1022274,"top":0.386672,"width":0.024102394,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"bounds":{"left":0.15309176,"top":0.38387868,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Service-Desk","depth":17,"bounds":{"left":0.08759973,"top":0.40622506,"width":0.0674867,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Service-Desk","depth":20,"bounds":{"left":0.09823803,"top":0.4122107,"width":0.03025266,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Service-Desk","depth":18,"bounds":{"left":0.15442154,"top":0.4094174,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Service-Desk","depth":20,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"More spaces","depth":17,"bounds":{"left":0.08759973,"top":0.43176377,"width":0.0674867,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More spaces","depth":20,"bounds":{"left":0.09823803,"top":0.43774942,"width":0.028756648,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Filters","depth":12,"bounds":{"left":0.08361037,"top":0.45730248,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Filters","depth":15,"bounds":{"left":0.09424867,"top":0.4632881,"width":0.013796543,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Filters","depth":13,"bounds":{"left":0.15309176,"top":0.46049482,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Filters","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Dashboards","depth":12,"bounds":{"left":0.08361037,"top":0.4828412,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Dashboards","depth":15,"bounds":{"left":0.09424867,"top":0.4888268,"width":0.026761968,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create dashboard","depth":13,"bounds":{"left":0.15508644,"top":0.48603353,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create dashboard","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Dashboards","depth":13,"bounds":{"left":0.16240026,"top":0.48603353,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Dashboards","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Operations","depth":12,"bounds":{"left":0.08361037,"top":0.5083799,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Operations","depth":15,"bounds":{"left":0.09424867,"top":0.5143655,"width":0.02443484,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Operations","depth":13,"bounds":{"left":0.15309176,"top":0.51157224,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Operations","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Confluence , (opens new window)","depth":13,"bounds":{"left":0.08361037,"top":0.5434956,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Confluence","depth":17,"bounds":{"left":0.09424867,"top":0.5494813,"width":0.025764627,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", (opens new window)","depth":15,"bounds":{"left":0.08361037,"top":0.55706304,"width":0.04837101,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Teams , (opens new window)","depth":13,"bounds":{"left":0.08361037,"top":0.56903434,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Teams","depth":17,"bounds":{"left":0.09424867,"top":0.57501996,"width":0.014793883,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", (opens new window)","depth":15,"bounds":{"left":0.08361037,"top":0.5826017,"width":0.04837101,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"open menu","depth":14,"bounds":{"left":0.14378324,"top":0.57222664,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"open menu","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Customise sidebar","depth":12,"bounds":{"left":0.08361037,"top":0.60415006,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Customise sidebar","depth":15,"bounds":{"left":0.09424867,"top":0.6101357,"width":0.04155585,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Resize side navigation panel","depth":13,"bounds":{"left":0.2109375,"top":0.0981644,"width":0.062333778,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Spaces","depth":15,"bounds":{"left":0.17137633,"top":0.10933759,"width":0.013962766,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Spaces","depth":17,"bounds":{"left":0.17137633,"top":0.11292897,"width":0.013962766,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":15,"bounds":{"left":0.18716756,"top":0.11173184,"width":0.0016622341,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Jiminny (New) Jiminny (New)","depth":15,"bounds":{"left":0.19265293,"top":0.10933759,"width":0.034574468,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny (New)","depth":17,"bounds":{"left":0.19996676,"top":0.11292897,"width":0.027260639,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":15,"bounds":{"left":0.22905585,"top":0.11173184,"width":0.0016622341,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Epic - Change parent","depth":15,"bounds":{"left":0.23254654,"top":0.10933759,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"JY-19240","depth":15,"bounds":{"left":0.24052526,"top":0.10933759,"width":0.017952127,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-19240","depth":17,"bounds":{"left":0.24052526,"top":0.11292897,"width":0.017952127,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":15,"bounds":{"left":0.26030585,"top":0.11173184,"width":0.0016622341,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Bug - Change work type","depth":15,"bounds":{"left":0.26379654,"top":0.10933759,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"JY-20773","depth":15,"bounds":{"left":0.27177528,"top":0.10933759,"width":0.018284574,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20773","depth":17,"bounds":{"left":0.27177528,"top":0.11292897,"width":0.018284574,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy link","depth":16,"bounds":{"left":0.28873006,"top":0.11213089,"width":0.005319149,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"User Pilot not receiving events on report generated- Summary, edit","depth":11,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"User Pilot not receiving events on report generated","depth":11,"bounds":{"left":0.17204122,"top":0.1396648,"width":0.19049202,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"User Pilot not receiving events on report generated","depth":12,"bounds":{"left":0.17204122,"top":0.13926576,"width":0.19049202,"height":0.023543496},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Add or create work related to this Bug","depth":12,"bounds":{"left":0.17137633,"top":0.17158818,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Add or create work related to this Bug","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"View app actions","depth":12,"bounds":{"left":0.1846742,"top":0.17158818,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"View app actions","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Collapse Key details Key details","depth":11,"bounds":{"left":0.16339761,"top":0.20989625,"width":0.22057846,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXButton","text":"Collapse Key details","depth":13,"bounds":{"left":0.16206782,"top":0.21308859,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Collapse Key details","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Key details","depth":14,"bounds":{"left":0.17137633,"top":0.2150838,"width":0.02825798,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Description","depth":12,"bounds":{"left":0.17137633,"top":0.2386273,"width":0.025598405,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Description","depth":13,"bounds":{"left":0.17137633,"top":0.23902634,"width":0.025598405,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit Description, edit","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"When new report is generated User Pilot should receive tracking event. The event is still in pending meaning no new event has arrived.","depth":14,"bounds":{"left":0.17204122,"top":0.26097366,"width":0.20113032,"height":0.03312051},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Steps to reproduce","depth":12,"bounds":{"left":0.17137633,"top":0.31723863,"width":0.042386968,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Steps to reproduce","depth":13,"bounds":{"left":0.17137633,"top":0.3180367,"width":0.042386968,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More information about","depth":12,"bounds":{"left":0.21509309,"top":0.31923383,"width":0.005319149,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Edit Steps to reproduce, edit","depth":12,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"None","depth":13,"bounds":{"left":0.17137633,"top":0.34118116,"width":0.011801862,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Actual outcome","depth":13,"bounds":{"left":0.17137633,"top":0.3858739,"width":0.034906916,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More information about","depth":12,"bounds":{"left":0.21027261,"top":0.386672,"width":0.005319149,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Edit Actual outcome","depth":13,"bounds":{"left":0.25648272,"top":0.3926576,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add text","depth":14,"bounds":{"left":0.25648272,"top":0.3858739,"width":0.018450798,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Expected outcome","depth":13,"bounds":{"left":0.17137633,"top":0.42418197,"width":0.04155585,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More information about","depth":12,"bounds":{"left":0.21692154,"top":0.42498004,"width":0.005319149,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Edit Expected outcome","depth":13,"bounds":{"left":0.25648272,"top":0.4309657,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add text","depth":14,"bounds":{"left":0.25648272,"top":0.42418197,"width":0.018450798,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Subtasks","depth":11,"bounds":{"left":0.17137633,"top":0.47765362,"width":0.023936171,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Subtasks","depth":12,"bounds":{"left":0.17137633,"top":0.47765362,"width":0.023936171,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add subtask","depth":12,"bounds":{"left":0.16738696,"top":0.49840382,"width":0.035405584,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add subtask","depth":14,"bounds":{"left":0.17137633,"top":0.5039904,"width":0.027426861,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Linked work items","depth":11,"bounds":{"left":0.17137633,"top":0.54309654,"width":0.04654255,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Linked work items","depth":12,"bounds":{"left":0.17137633,"top":0.54309654,"width":0.04654255,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add linked work item","depth":12,"bounds":{"left":0.16738696,"top":0.5622506,"width":0.05418883,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add linked work item","depth":14,"bounds":{"left":0.17137633,"top":0.5678372,"width":0.046210106,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Collapse Activity Activity","depth":12,"bounds":{"left":0.16339761,"top":0.6069433,"width":0.22057846,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXButton","text":"Collapse Activity","depth":14,"bounds":{"left":0.16206782,"top":0.6101357,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Collapse Activity","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Activity","depth":15,"bounds":{"left":0.17137633,"top":0.6117318,"width":0.019946808,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"All","depth":14,"bounds":{"left":0.17237367,"top":0.6380686,"width":0.01412899,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"radio button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"All","depth":16,"bounds":{"left":0.17669548,"top":0.641261,"width":0.005485372,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Comments","depth":14,"bounds":{"left":0.18716756,"top":0.6380686,"width":0.032413565,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"radio button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Comments","depth":16,"bounds":{"left":0.19148937,"top":0.641261,"width":0.023769947,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"History","depth":14,"bounds":{"left":0.22024602,"top":0.6380686,"width":0.024268618,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"radio button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"History","depth":16,"bounds":{"left":0.22456782,"top":0.641261,"width":0.015625,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Work log","depth":14,"bounds":{"left":0.24517952,"top":0.6380686,"width":0.02825798,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"radio button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Work log","depth":16,"bounds":{"left":0.24950133,"top":0.641261,"width":0.019614361,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Atlassian Intelligence Summarise","depth":14,"bounds":{"left":0.36535904,"top":0.6420591,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Summarise","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Newest first Newest first","depth":13,"bounds":{"left":0.37599733,"top":0.6420591,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Newest first","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add a comment…","depth":15,"bounds":{"left":0.1853391,"top":0.68196326,"width":0.19797207,"height":0.07182761},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Suggest a reply...","depth":17,"bounds":{"left":0.19132313,"top":0.72027135,"width":0.046708778,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Suggest a reply...","depth":19,"bounds":{"left":0.1953125,"top":0.7226656,"width":0.03873005,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Status update...","depth":17,"bounds":{"left":0.2393617,"top":0.72027135,"width":0.04305186,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Status update...","depth":19,"bounds":{"left":0.24335106,"top":0.7226656,"width":0.03507314,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Thanks...","depth":17,"bounds":{"left":0.28374335,"top":0.72027135,"width":0.028590426,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Thanks...","depth":19,"bounds":{"left":0.28773272,"top":0.7226656,"width":0.020611702,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Pro tip:","depth":16,"bounds":{"left":0.1846742,"top":0.7621708,"width":0.013796543,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"press","depth":15,"bounds":{"left":0.19847074,"top":0.7621708,"width":0.012632979,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"M","depth":17,"bounds":{"left":0.21243352,"top":0.7629689,"width":0.0034906915,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to comment","depth":15,"bounds":{"left":0.21725398,"top":0.7621708,"width":0.023603724,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"More information about Lukas Kovalik","depth":18,"bounds":{"left":0.18999335,"top":0.8036712,"width":0.031083776,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More information about Lukas Kovalik","depth":20,"bounds":{"left":0.18999335,"top":0.8032721,"width":0.031083776,"height":0.019553073},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Lukas Kovalik","depth":21,"bounds":{"left":0.18999335,"top":0.8076616,"width":0.031083776,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy link to comment","depth":18,"bounds":{"left":0.22240691,"top":0.8064645,"width":0.005319149,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"4 hours ago","depth":18,"bounds":{"left":0.18999335,"top":0.82322425,"width":0.022606382,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"The issue is most probably AutomatedReportGenerated event and not listener sending data to UP. The PR should address it.","depth":19,"bounds":{"left":0.18999335,"top":0.8475658,"width":0.18999335,"height":0.03312051},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Reply","depth":17,"bounds":{"left":0.18999335,"top":0.8930567,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Add thumbs up reaction","depth":17,"bounds":{"left":0.20063165,"top":0.8930567,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Add reaction","depth":17,"bounds":{"left":0.21126994,"top":0.8930567,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Edit","depth":17,"bounds":{"left":0.22190824,"top":0.8930567,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"More actions","depth":17,"bounds":{"left":0.23254654,"top":0.8930567,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Resize work item view side panel","depth":12,"bounds":{"left":0.7077792,"top":0.0981644,"width":0.07280585,"height":0.01396648},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Watch options: You are watching this issue, 1 person watching","depth":13,"bounds":{"left":0.44830453,"top":0.10694334,"width":0.017121011,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":17,"bounds":{"left":0.45960772,"top":0.11292897,"width":0.0018284575,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Share","depth":14,"bounds":{"left":0.4680851,"top":0.10694334,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Share","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Actions","depth":14,"bounds":{"left":0.48138297,"top":0.10694334,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Actions","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Backlog - Change status","depth":11,"bounds":{"left":0.39860374,"top":0.14365523,"width":0.03174867,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Backlog","depth":13,"bounds":{"left":0.40259308,"top":0.14964086,"width":0.017785905,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Automation","depth":11,"bounds":{"left":0.43567154,"top":0.14365523,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Automation","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Improve Bug","depth":11,"bounds":{"left":0.39860374,"top":0.17877094,"width":0.042220745,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Improve Bug","depth":13,"bounds":{"left":0.40791222,"top":0.18475658,"width":0.028922873,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Details","depth":14,"bounds":{"left":0.40159574,"top":0.22266561,"width":0.08211436,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXHeading","text":"Details","depth":17,"bounds":{"left":0.41090426,"top":0.22585794,"width":0.01861702,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Details","depth":18,"bounds":{"left":0.41090426,"top":0.22625698,"width":0.017287234,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Assignee","depth":12,"bounds":{"left":0.40525267,"top":0.26296887,"width":0.020611702,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Assignee Pin to top. Only you can see pinned fields.","depth":11,"bounds":{"left":0.42852393,"top":0.26376694,"width":0.005319149,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Lukas Kovalik- edit Assignee","depth":12,"bounds":{"left":0.40525267,"top":0.29289705,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"More information about Lukas Kovalik","depth":13,"bounds":{"left":0.40525267,"top":0.28371906,"width":0.04055851,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Lukas Kovalik","depth":15,"bounds":{"left":0.41589096,"top":0.28651237,"width":0.029920213,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Reporter","depth":12,"bounds":{"left":0.40525267,"top":0.3196329,"width":0.019281914,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Reporter Pin to top. Only you can see pinned fields.","depth":11,"bounds":{"left":0.42719415,"top":0.3200319,"width":0.005319149,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Lukas Kovalik- edit Reporter","depth":12,"bounds":{"left":0.40525267,"top":0.34956107,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"More information about Lukas Kovalik","depth":13,"bounds":{"left":0.40525267,"top":0.34038308,"width":0.04055851,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Lukas Kovalik","depth":15,"bounds":{"left":0.41589096,"top":0.34277734,"width":0.029920213,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Development","depth":12,"bounds":{"left":0.40525267,"top":0.3790902,"width":0.029421542,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Development Pin to top. Only you can see pinned fields.","depth":11,"bounds":{"left":0.43733376,"top":0.37869114,"width":0.005319149,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Open with VS Code Link to external website in new tab","depth":12,"bounds":{"left":0.40259308,"top":0.39984038,"width":0.082446806,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Open with VS Code","depth":15,"bounds":{"left":0.4112367,"top":0.40582603,"width":0.043218084,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"1 branch","depth":13,"bounds":{"left":0.40259308,"top":0.4253791,"width":0.082446806,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"1","depth":16,"bounds":{"left":0.40591756,"top":0.43136472,"width":0.0019946808,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"branch","depth":16,"bounds":{"left":0.40791222,"top":0.43136472,"width":0.01662234,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Open branch","depth":13,"bounds":{"left":0.46974733,"top":0.4253791,"width":0.007978723,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Create new branch","depth":13,"bounds":{"left":0.47839096,"top":0.4253791,"width":0.0066489363,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"1 commit 14 days ago","depth":13,"bounds":{"left":0.40259308,"top":0.4509178,"width":0.082446806,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"1","depth":16,"bounds":{"left":0.40591756,"top":0.45690343,"width":0.0019946808,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commit","depth":16,"bounds":{"left":0.40791222,"top":0.45690343,"width":0.017453458,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"14 days ago","depth":17,"bounds":{"left":0.45994017,"top":0.4577015,"width":0.02244016,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Open commit","depth":13,"bounds":{"left":0.46841756,"top":0.4509178,"width":0.007978723,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Create new commit","depth":13,"bounds":{"left":0.47706118,"top":0.4509178,"width":0.007978723,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"1 pull request OPEN","depth":13,"bounds":{"left":0.40259308,"top":0.4764565,"width":0.082446806,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"1","depth":16,"bounds":{"left":0.40591756,"top":0.48244214,"width":0.0019946808,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"pull request","depth":16,"bounds":{"left":0.40791222,"top":0.48244214,"width":0.027094414,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"OPEN","depth":18,"bounds":{"left":0.47057846,"top":0.4848364,"width":0.010472074,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Open pull request","depth":13,"bounds":{"left":0.46841756,"top":0.4764565,"width":0.007978723,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Create new pull request","depth":13,"bounds":{"left":0.47706118,"top":0.4764565,"width":0.007978723,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Labels","depth":12,"bounds":{"left":0.40525267,"top":0.5151636,"width":0.01462766,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Labels Pin to top. Only you can see pinned fields.","depth":11,"bounds":{"left":0.4225399,"top":0.5159617,"width":0.005319149,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit Labels","depth":12,"bounds":{"left":0.40525267,"top":0.5450918,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"None","depth":13,"bounds":{"left":0.40525267,"top":0.5387071,"width":0.011801862,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Sub-Product","depth":12,"bounds":{"left":0.40525267,"top":0.5718276,"width":0.02825798,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Sub-Product Pin to top. Only you can see pinned fields.","depth":11,"bounds":{"left":0.43617022,"top":0.57222664,"width":0.005319149,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit Sub-Product, Add options selected, edit","depth":11,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add options","depth":12,"bounds":{"left":0.40525267,"top":0.5949721,"width":0.02642952,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Story Points","depth":12,"bounds":{"left":0.40525267,"top":0.6280926,"width":0.026928192,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More information about","depth":11,"bounds":{"left":0.43617022,"top":0.62849164,"width":0.005319149,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Story Points Pin to top. Only you can see pinned fields.","depth":11,"bounds":{"left":0.44414893,"top":0.62849164,"width":0.005319149,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit Story Points","depth":12,"bounds":{"left":0.40525267,"top":0.65802073,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2","depth":15,"bounds":{"left":0.40874335,"top":0.6532322,"width":0.0023271276,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Organisations","depth":12,"bounds":{"left":0.40525267,"top":0.6843575,"width":0.030751329,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More information about Organisations","depth":11,"bounds":{"left":0.43999335,"top":0.68595374,"width":0.005319149,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Organisations Pin to top. Only you can see pinned fields.","depth":11,"bounds":{"left":0.4479721,"top":0.68515563,"width":0.005319149,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit Organisations, None selected, edit","depth":11,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"None","depth":12,"bounds":{"left":0.40525267,"top":0.70790106,"width":0.011801862,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Components","depth":12,"bounds":{"left":0.40525267,"top":0.7410216,"width":0.028424202,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Components Pin to top. Only you can see pinned fields.","depth":11,"bounds":{"left":0.43633643,"top":0.74142057,"width":0.005319149,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit Components, edit","depth":11,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Platform","depth":12,"bounds":{"left":0.4069149,"top":0.76256984,"width":0.018450798,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform","depth":13,"bounds":{"left":0.4069149,"top":0.7649641,"width":0.018450798,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Fix versions","depth":12,"bounds":{"left":0.40525267,"top":0.7972865,"width":0.026595745,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Fix versions Pin to top. Only you can see pinned fields.","depth":11,"bounds":{"left":0.43450797,"top":0.79768556,"width":0.005319149,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit Fix versions, None selected, edit","depth":11,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"None","depth":12,"bounds":{"left":0.40525267,"top":0.82083,"width":0.011801862,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Parent","depth":12,"bounds":{"left":0.40525267,"top":0.85355145,"width":0.014461436,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More information about","depth":11,"bounds":{"left":0.42370346,"top":0.85434955,"width":0.005319149,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Parent Pin to top. Only you can see pinned fields.","depth":11,"bounds":{"left":0.43168217,"top":0.85434955,"width":0.005319149,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit Parent","depth":12,"bounds":{"left":0.40525267,"top":0.88347965,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Click to visit JY-19240 AJ Reports","depth":13,"bounds":{"left":0.41323137,"top":0.8758978,"width":0.04920213,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-19240 AJ Reports","depth":14,"bounds":{"left":0.41456118,"top":0.877095,"width":0.04654255,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Sprint","depth":12,"bounds":{"left":0.40525267,"top":0.9102155,"width":0.013297873,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More information about","depth":11,"bounds":{"left":0.4225399,"top":0.91061455,"width":0.005319149,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Sprint Pin to top. Only you can see pinned fields.","depth":11,"bounds":{"left":0.43051863,"top":0.91061455,"width":0.005319149,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit Sprint","depth":12,"bounds":{"left":0.40525267,"top":0.94014364,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Platform Sprint 4 Q2","depth":13,"bounds":{"left":0.40525267,"top":0.9333599,"width":0.044714097,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2","depth":14,"bounds":{"left":0.40525267,"top":0.9333599,"width":0.044714097,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Priority","depth":12,"bounds":{"left":0.40525267,"top":0.96648043,"width":0.016123671,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Priority Pin to top. Only you can see pinned fields.","depth":11,"bounds":{"left":0.4240359,"top":0.9668795,"width":0.005319149,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Edit Priority","depth":12,"bounds":{"left":0.40525267,"top":0.99640864,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Medium","depth":13,"bounds":{"left":0.41323137,"top":0.990024,"width":0.017785905,"height":0.009976029},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-9087317327699081296
|
6070239912495511684
|
visual_change
|
accessibility
|
NULL
|
Unnamed Group
TypeError: League\Flysystem\Filesyst Unnamed Group
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
CloudWatch | us-east-2
CloudWatch | us-east-2
Pipelines - jiminny/app
Pipelines - jiminny/app
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
[JY-20773] User Pilot not receiving events on report generated - Jira
[JY-20773] User Pilot not receiving events on report generated - Jira
JY-20773 fix user pilot tracking for automated report generated by LakyLak · Pull Request #12024 · jiminny/app
JY-20773 fix user pilot tracking for automated report generated by LakyLak · Pull Request #12024 · jiminny/app
[JY-20776] Automated report - sentry - Jira
[JY-20776] Automated report - sentry - Jira
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
Platform Team - Backlog - Jira
Platform Team - Backlog - Jira
[JY-20773] User Pilot not receiving events on report generated - Jira
[JY-20773] User Pilot not receiving events on report generated - Jira
Close tab
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to:
Sidebar
Sidebar
Top Bar
Top Bar
Main Content
Main Content
Collapse sidebar [
Collapse sidebar [
Switch sites or apps
Switch sites or apps
Go to your Jira homepage
Search, press enter to navigate to advanced search with your text query
Create
Create
Rovo Ask Rovo
Ask Rovo
Notifications
Notifications
Help
Help
Settings
Settings
[EMAIL]
[EMAIL]
For you
For you
Recent
Recent
Starred
Starred
Apps
Apps
More actions for Apps
More actions for Apps
Spaces
Spaces
Create space
Create space
More actions for spaces
More actions for spaces
Recent
Jiminny (New)
Jiminny (New)
Jiminny (New)
Create board
Create board
More actions for Jiminny (New)
More actions for Jiminny (New)
Platform Team
Platform Team
Board actions
Board actions
Capture Team
Capture Team
Board actions
Board actions
Enterprise Stability Issues 🤕
Enterprise Stability Issues 🤕
Board actions
Board actions
Processing Team
Processing Team
Board actions
Board actions
SE Kanban
SE Kanban
Board actions
Board actions
Service-Desk
Service-Desk
More actions for Service-Desk
More actions for Service-Desk
More spaces
More spaces
Filters
Filters
More actions for Filters
More actions for Filters
Dashboards
Dashboards
Create dashboard
Create dashboard
More actions for Dashboards
More actions for Dashboards
Operations
Operations
More actions for Operations
More actions for Operations
Confluence , (opens new window)
Confluence
, (opens new window)
Teams , (opens new window)
Teams
, (opens new window)
open menu
open menu
Customise sidebar
Customise sidebar
Resize side navigation panel
Spaces
Spaces
/
Jiminny (New) Jiminny (New)
Jiminny (New)
/
Epic - Change parent
JY-19240
JY-19240
/
Bug - Change work type
JY-20773
JY-20773
Copy link
User Pilot not receiving events on report generated- Summary, edit
User Pilot not receiving events on report generated
User Pilot not receiving events on report generated
Add or create work related to this Bug
Add or create work related to this Bug
View app actions
View app actions
Collapse Key details Key details
Collapse Key details
Collapse Key details
Key details
Description
Description
Edit Description, edit
When new report is generated User Pilot should receive tracking event. The event is still in pending meaning no new event has arrived.
Steps to reproduce
Steps to reproduce
More information about
Edit Steps to reproduce, edit
None
Actual outcome
More information about
Edit Actual outcome
Add text
Expected outcome
More information about
Edit Expected outcome
Add text
Subtasks
Subtasks
Add subtask
Add subtask
Linked work items
Linked work items
Add linked work item
Add linked work item
Collapse Activity Activity
Collapse Activity
Collapse Activity
Activity
All
All
Comments
Comments
History
History
Work log
Work log
Atlassian Intelligence Summarise
Summarise
Newest first Newest first
Newest first
Add a comment…
Suggest a reply...
Suggest a reply...
Status update...
Status update...
Thanks...
Thanks...
Pro tip:
press
M
to comment
More information about Lukas Kovalik
More information about Lukas Kovalik
Lukas Kovalik
Copy link to comment
4 hours ago
The issue is most probably AutomatedReportGenerated event and not listener sending data to UP. The PR should address it.
Reply
Add thumbs up reaction
Add reaction
Edit
More actions
Resize work item view side panel
Watch options: You are watching this issue, 1 person watching
1
Share
Share
Actions
Actions
Backlog - Change status
Backlog
Automation
Automation
Improve Bug
Improve Bug
Details
Details
Details
Assignee
Assignee Pin to top. Only you can see pinned fields.
Lukas Kovalik- edit Assignee
More information about Lukas Kovalik
Lukas Kovalik
Reporter
Reporter Pin to top. Only you can see pinned fields.
Lukas Kovalik- edit Reporter
More information about Lukas Kovalik
Lukas Kovalik
Development
Development Pin to top. Only you can see pinned fields.
Open with VS Code Link to external website in new tab
Open with VS Code
1 branch
1
branch
Open branch
Create new branch
1 commit 14 days ago
1
commit
14 days ago
Open commit
Create new commit
1 pull request OPEN
1
pull request
OPEN
Open pull request
Create new pull request
Labels
Labels Pin to top. Only you can see pinned fields.
Edit Labels
None
Sub-Product
Sub-Product Pin to top. Only you can see pinned fields.
Edit Sub-Product, Add options selected, edit
Add options
Story Points
More information about
Story Points Pin to top. Only you can see pinned fields.
Edit Story Points
2
Organisations
More information about Organisations
Organisations Pin to top. Only you can see pinned fields.
Edit Organisations, None selected, edit
None
Components
Components Pin to top. Only you can see pinned fields.
Edit Components, edit
Platform
Platform
Fix versions
Fix versions Pin to top. Only you can see pinned fields.
Edit Fix versions, None selected, edit
None
Parent
More information about
Parent Pin to top. Only you can see pinned fields.
Edit Parent
Click to visit JY-19240 AJ Reports
JY-19240 AJ Reports
Sprint
More information about
Sprint Pin to top. Only you can see pinned fields.
Edit Sprint
Platform Sprint 4 Q2
Platform Sprint 4 Q2
Priority
Priority Pin to top. Only you can see pinned fields.
Edit Priority
Medium...
|
25409
|
NULL
|
NULL
|
NULL
|
|
18208
|
791
|
9
|
2026-05-11T11:04:09.273594+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778497449273_m2.jpg...
|
Notion Calendar
|
NULL
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
VIewWindowmelp°9 JY-20725-handle-HS-search-rate-li VIewWindowmelp°9 JY-20725-handle-HS-search-rate-linProiect(C) TranscodeParameterRescl© UserService.php© Uuid.php> D Traits> D UseCases> D User> D Utils› D Validation> OvOphp nelpers.ong© InitialFrontendState.php© Jiminny.phpc) Plan.oho© Serializer.phpC) TeamScimDetails.ohpbootstrap>C build> contia>@ contrib→ database>M docsM front-end>D lang> node_modules library rootM ohostan> M nublic>O resourcesv Mroutesphp api.phpphp api_v2.phppnp console.onopnp customer_api.onppnp embedded.ongphp nealtn.onppnp scim.onophp uprotectedweb.phpphp web.phpphp webhook.php>O scriptsv O storage•aoo>M debugbar… M frameworkv Mloas.aitianoreê audio. wav= custom.loalC) Hubspot/Service.onp(C) HubSoot/Service.pnpo RematchActivityonCrmObiectDetach.pngC) CheckAnсRateLimitException.phc(C) CrmobiectsResolver.pho() ProviderRateLimiter.phpclass Cuient extends BasecLient imolen352369377= hubspot-journal-poll.log= laravel log< nhnunit ymus tht isorivate function createBatchconfia'inputClass' => \HubSpit @1ssetSconfiqurationsSoInval1dArqumentorivate function orenareßatchReaue$batchReadRequest = $batchConfSinnutClacs = ChatchtonfiortirSinputs = array_map(function (Sinput = new SinputClassSinput->setId($crmId):return $input;}. ScrmIds):SbatchReadRequest->setInputs($SbatchReadReguest->setPropertiorivate function validateAoiResoonif @ Sresponse)4throw new_CrmExcentiondmnnivate function nroceccAniReculto• SearchYour team is now on the Free plan with 1 admin. You retain editing access and other members are read-only. View team permissions to see who can edit, or upgrade to restore collaborationV COLLECTIONSs9: An error occurrea› CRM Owners> CRM Pipelines› Dealsengagements>D OLD ENGAGEMENTSGet list meetingsrosl search tasksGET read callGeT list callsPOST meetings scheduledGET det meetinorost det link to task>POST Create Contact with Association› Hubspotv teration run HSGET Read copyeg. An error occurred.se. successful operationv teration run Search HSrost search contact oy emall copy> Journal & webhoooks v4› OAuth› Properties• DECCADOLSEARCHPOSt search contact by phonePOST search contact by emailPOST search meetingsPOST search notesPost Search callc vaPOST Search related meetinas v3PoSt coarch deals> Ticketsv UlcofullPOST search dealsPOST Read a batch of obiecPost Read a batch of assorGET Readteration run Search HS (#8)u Iteration run Search HS • 20 VUs • May 11, 2026 13:16:51 (1 min) • Fixed profileSummaryTotal requests sent ©Requests/second ©Avg. response time ©P90 ©6.563109,40157 ms176 ms% 10012-16•5412-17•0012-17-0612-17-1212•17-18Dorformonso dotolle fortotol durotinnPOST search contact by email Copy6.563107.21> POST filter per comoanv / onlv oven deal stagesGet engagements old associated by dealSustem Resource WarningSustem resources are constrained. Thesystem may not be able to generate the loadeded for this test and the cest is likely to- Connect Git = Concole 5.) TerminaGET Read Copyhl• Preparation tor kerl.. In oo m100% Lz• Mon 11 May 14:04:080 Iteration run Search HSPOSt search contact by emaiD Iteration run Search HSNo environmenP95 O203 msP99 ©401 msError % ©0,00Failure % ©0,00Peak CPU % ©Peak Memory % ©999 %19.2 %Filter bv requestsvAva. response390 ms 140 req/s3000.0012-17-24Failure %0.0012-17-20112-17-26|12-17•4212•17•49- Requests/second - Ava. response - Error % — Virtual users ..• CPU % •.• Memory %Resp. time (Ava. ms)Min (ms)Max ms)203401Globals Vault Tools?000...
|
NULL
|
-9087051478434250741
|
NULL
|
visual_change
|
ocr
|
NULL
|
VIewWindowmelp°9 JY-20725-handle-HS-search-rate-li VIewWindowmelp°9 JY-20725-handle-HS-search-rate-linProiect(C) TranscodeParameterRescl© UserService.php© Uuid.php> D Traits> D UseCases> D User> D Utils› D Validation> OvOphp nelpers.ong© InitialFrontendState.php© Jiminny.phpc) Plan.oho© Serializer.phpC) TeamScimDetails.ohpbootstrap>C build> contia>@ contrib→ database>M docsM front-end>D lang> node_modules library rootM ohostan> M nublic>O resourcesv Mroutesphp api.phpphp api_v2.phppnp console.onopnp customer_api.onppnp embedded.ongphp nealtn.onppnp scim.onophp uprotectedweb.phpphp web.phpphp webhook.php>O scriptsv O storage•aoo>M debugbar… M frameworkv Mloas.aitianoreê audio. wav= custom.loalC) Hubspot/Service.onp(C) HubSoot/Service.pnpo RematchActivityonCrmObiectDetach.pngC) CheckAnсRateLimitException.phc(C) CrmobiectsResolver.pho() ProviderRateLimiter.phpclass Cuient extends BasecLient imolen352369377= hubspot-journal-poll.log= laravel log< nhnunit ymus tht isorivate function createBatchconfia'inputClass' => \HubSpit @1ssetSconfiqurationsSoInval1dArqumentorivate function orenareßatchReaue$batchReadRequest = $batchConfSinnutClacs = ChatchtonfiortirSinputs = array_map(function (Sinput = new SinputClassSinput->setId($crmId):return $input;}. ScrmIds):SbatchReadRequest->setInputs($SbatchReadReguest->setPropertiorivate function validateAoiResoonif @ Sresponse)4throw new_CrmExcentiondmnnivate function nroceccAniReculto• SearchYour team is now on the Free plan with 1 admin. You retain editing access and other members are read-only. View team permissions to see who can edit, or upgrade to restore collaborationV COLLECTIONSs9: An error occurrea› CRM Owners> CRM Pipelines› Dealsengagements>D OLD ENGAGEMENTSGet list meetingsrosl search tasksGET read callGeT list callsPOST meetings scheduledGET det meetinorost det link to task>POST Create Contact with Association› Hubspotv teration run HSGET Read copyeg. An error occurred.se. successful operationv teration run Search HSrost search contact oy emall copy> Journal & webhoooks v4› OAuth› Properties• DECCADOLSEARCHPOSt search contact by phonePOST search contact by emailPOST search meetingsPOST search notesPost Search callc vaPOST Search related meetinas v3PoSt coarch deals> Ticketsv UlcofullPOST search dealsPOST Read a batch of obiecPost Read a batch of assorGET Readteration run Search HS (#8)u Iteration run Search HS • 20 VUs • May 11, 2026 13:16:51 (1 min) • Fixed profileSummaryTotal requests sent ©Requests/second ©Avg. response time ©P90 ©6.563109,40157 ms176 ms% 10012-16•5412-17•0012-17-0612-17-1212•17-18Dorformonso dotolle fortotol durotinnPOST search contact by email Copy6.563107.21> POST filter per comoanv / onlv oven deal stagesGet engagements old associated by dealSustem Resource WarningSustem resources are constrained. Thesystem may not be able to generate the loadeded for this test and the cest is likely to- Connect Git = Concole 5.) TerminaGET Read Copyhl• Preparation tor kerl.. In oo m100% Lz• Mon 11 May 14:04:080 Iteration run Search HSPOSt search contact by emaiD Iteration run Search HSNo environmenP95 O203 msP99 ©401 msError % ©0,00Failure % ©0,00Peak CPU % ©Peak Memory % ©999 %19.2 %Filter bv requestsvAva. response390 ms 140 req/s3000.0012-17-24Failure %0.0012-17-20112-17-26|12-17•4212•17•49- Requests/second - Ava. response - Error % — Virtual users ..• CPU % •.• Memory %Resp. time (Ava. ms)Min (ms)Max ms)203401Globals Vault Tools?000...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
75800
|
2675
|
0
|
2026-05-27T08:33:42.851386+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-27/1779 /Users/lukas/.screenpipe/data/data/2026-05-27/1779870822851_m2.jpg...
|
Firefox
|
DXP4800PLUS-B5F8 — Personal
|
1
|
nas.lakylak.xyz/desktop/#/
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
Close tab
Garmin DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
Close tab
Garmin Dashboard
Garmin Dashboard
New Tab
New Tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
1.2
KB/s
381
B/s
Files
Control Panel
Storage
App Center
Logs
Support
Task Manager
Music
Cloud Drives
Theater
Photos
Online Office
TextEdit
Virtual Machine
Downloads
DLNA
File Version Explorer
Security
Jellyfin-HT
SAN Manager
Vault
Snapshot
Comics
Sync & Backup
UGREEN AI
Recycle Bin...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"DXP4800PLUS-B5F8","depth":4,"bounds":{"left":0.5,"top":0.0518755,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"DXP4800PLUS-B5F8","depth":5,"bounds":{"left":0.51329786,"top":0.06304868,"width":0.036901597,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.55651593,"top":0.05905826,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Garmin Dashboard","depth":4,"bounds":{"left":0.5,"top":0.08459697,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Garmin Dashboard","depth":5,"bounds":{"left":0.51329786,"top":0.09577015,"width":0.032413565,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.5,"top":0.11731844,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.51329786,"top":0.12849163,"width":0.014960106,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.5028258,"top":0.15163608,"width":0.06333112,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.5028258,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.51379657,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.5249335,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.53607047,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bitwarden","depth":6,"bounds":{"left":0.5472075,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":12,"bounds":{"left":0.97706115,"top":0.06304868,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1.2","depth":10,"bounds":{"left":0.92669547,"top":0.06264964,"width":0.0051529254,"height":0.008379889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"KB/s","depth":10,"bounds":{"left":0.9318484,"top":0.06304868,"width":0.005984043,"height":0.0075818035},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"381","depth":10,"bounds":{"left":0.92669547,"top":0.07222666,"width":0.005984043,"height":0.008379889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"B/s","depth":10,"bounds":{"left":0.93267953,"top":0.0726257,"width":0.0039893617,"height":0.0075818035},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Files","depth":7,"bounds":{"left":0.59175533,"top":0.1707901,"width":0.009973404,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Control Panel","depth":7,"bounds":{"left":0.58261305,"top":0.2697526,"width":0.02825798,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Storage","depth":7,"bounds":{"left":0.58859706,"top":0.36871508,"width":0.016289894,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"App Center","depth":7,"bounds":{"left":0.58494014,"top":0.46767756,"width":0.023603724,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Logs","depth":7,"bounds":{"left":0.59175533,"top":0.5666401,"width":0.009973404,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Support","depth":7,"bounds":{"left":0.58859706,"top":0.66560256,"width":0.016289894,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Task Manager","depth":7,"bounds":{"left":0.58211434,"top":0.76456505,"width":0.02925532,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Music","depth":7,"bounds":{"left":0.5905917,"top":0.86352754,"width":0.012300532,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Cloud Drives","depth":7,"bounds":{"left":0.6313165,"top":0.1707901,"width":0.026595745,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Theater","depth":7,"bounds":{"left":0.63663566,"top":0.2697526,"width":0.015957447,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Photos","depth":7,"bounds":{"left":0.63730055,"top":0.36871508,"width":0.01462766,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Online Office","depth":7,"bounds":{"left":0.63115025,"top":0.46767756,"width":0.026928192,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"TextEdit","depth":7,"bounds":{"left":0.6363032,"top":0.5666401,"width":0.01662234,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Virtual Machine","depth":7,"bounds":{"left":0.6286569,"top":0.66560256,"width":0.031914894,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Downloads","depth":7,"bounds":{"left":0.633145,"top":0.76456505,"width":0.022938829,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"DLNA","depth":7,"bounds":{"left":0.6384641,"top":0.86352754,"width":0.012300532,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"File Version Explorer","depth":7,"bounds":{"left":0.6710439,"top":0.1707901,"width":0.04288564,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Security","depth":7,"bounds":{"left":0.6840093,"top":0.2697526,"width":0.016954787,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Jellyfin-HT","depth":7,"bounds":{"left":0.68151593,"top":0.36871508,"width":0.021941489,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SAN Manager","depth":7,"bounds":{"left":0.67785907,"top":0.46767756,"width":0.02925532,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Vault","depth":7,"bounds":{"left":0.68733376,"top":0.5666401,"width":0.010305851,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Snapshot","depth":7,"bounds":{"left":0.68267953,"top":0.66560256,"width":0.019614361,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Comics","depth":7,"bounds":{"left":0.6846742,"top":0.76456505,"width":0.015625,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Sync & Backup","depth":7,"bounds":{"left":0.67669547,"top":0.86352754,"width":0.03158245,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"UGREEN AI","depth":7,"bounds":{"left":0.72755986,"top":0.1707901,"width":0.025598405,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Recycle Bin","depth":7,"bounds":{"left":0.7280585,"top":0.2697526,"width":0.024601065,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-9086188491583367863
|
-9004578035355625700
|
visual_change
|
accessibility
|
NULL
|
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
Close tab
Garmin DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
Close tab
Garmin Dashboard
Garmin Dashboard
New Tab
New Tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
1.2
KB/s
381
B/s
Files
Control Panel
Storage
App Center
Logs
Support
Task Manager
Music
Cloud Drives
Theater
Photos
Online Office
TextEdit
Virtual Machine
Downloads
DLNA
File Version Explorer
Security
Jellyfin-HT
SAN Manager
Vault
Snapshot
Comics
Sync & Backup
UGREEN AI
Recycle Bin...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
75801
|
2674
|
0
|
2026-05-27T08:33:42.943677+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-27/1779 /Users/lukas/.screenpipe/data/data/2026-05-27/1779870822943_m1.jpg...
|
Firefox
|
DXP4800PLUS-B5F8 — Personal
|
1
|
nas.lakylak.xyz/desktop/#/
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
Close tab
Garmin DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
Close tab
Garmin Dashboard
Garmin Dashboard
New Tab
New Tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
1.2
KB/s
381
B/s
Files
Control Panel
Storage
App Center
Logs
Support
Task Manager
Music
Cloud Drives
Theater
Photos
Online Office
TextEdit
Virtual Machine
Downloads
DLNA
File Version Explorer
Security
Jellyfin-HT
SAN Manager
Vault
Snapshot
Comics
Sync & Backup
UGREEN AI
Recycle Bin...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"DXP4800PLUS-B5F8","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"DXP4800PLUS-B5F8","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Garmin Dashboard","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Garmin Dashboard","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.48576388,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.5086806,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.53194445,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.5552083,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bitwarden","depth":6,"bounds":{"left":0.5784722,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1.2","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"KB/s","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"381","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"B/s","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Files","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Control Panel","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Storage","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"App Center","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Logs","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Support","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Task Manager","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Music","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Cloud Drives","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Theater","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Photos","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Online Office","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"TextEdit","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Virtual Machine","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Downloads","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"DLNA","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"File Version Explorer","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Security","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Jellyfin-HT","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SAN Manager","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Vault","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Snapshot","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Comics","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Sync & Backup","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"UGREEN AI","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Recycle Bin","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-9086188491583367863
|
-9004578035355625700
|
visual_change
|
accessibility
|
NULL
|
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
Close tab
Garmin DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
Close tab
Garmin Dashboard
Garmin Dashboard
New Tab
New Tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
1.2
KB/s
381
B/s
Files
Control Panel
Storage
App Center
Logs
Support
Task Manager
Music
Cloud Drives
Theater
Photos
Online Office
TextEdit
Virtual Machine
Downloads
DLNA
File Version Explorer
Security
Jellyfin-HT
SAN Manager
Vault
Snapshot
Comics
Sync & Backup
UGREEN AI
Recycle Bin...
|
75799
|
NULL
|
NULL
|
NULL
|
|
52707
|
1850
|
86
|
2026-05-18T11:10:30.368073+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-18/1779 /Users/lukas/.screenpipe/data/data/2026-05-18/1779102630368_m2.jpg...
|
Firefox
|
Inbox (5) - kovaliklukas@gmail.com - Gmail — Perso Inbox (5) - kovaliklukas@gmail.com - Gmail — Personal...
|
1
|
mail.google.com/mail/u/0/#inbox
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
Getting Started DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
Getting Started · AFFiNE
Getting Started · AFFiNE
Screenpipe — Archive
Screenpipe — Archive
Download screenpipe — get started in minutes
Download screenpipe — get started in minutes
Self-Hosted Software and Apps
Self-Hosted Software and Apps
Finance Hub
Finance Hub
New Tab
New Tab
As I get older, now 79, I am viewing, seeing or realizing more and more disturbing things that are finally hitting home more profoundly and affecti... - [EMAIL] - Gmail
As I get older, now 79, I am viewing, seeing or realizing more and more disturbing things that are finally hitting home more profoundly and affecti... - [EMAIL] - Gmail
Inbox (5) - [EMAIL] - Gmail
Inbox (5) - [EMAIL] - Gmail
Close tab
Завеждане на щета онлайн | Euroins
Завеждане на щета онлайн | Euroins
Photos - Google Photos
Photos - Google Photos
Kontakt | Veľvyslanectvo Slovenskej republiky v Sofii
Kontakt | Veľvyslanectvo Slovenskej republiky v Sofii
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
None selected
Skip to content
Skip to content
Using Gmail with screen readers
Using Gmail with screen readers
Main menu
Gmail
Search
Search
Search mail
Advanced search options
Search mail
Support
Settings
Ask Gemini
Google apps
Google Account: Lukáš Koválik ([EMAIL])
Compose
Labels
Labels
Inbox 5 unread
Inbox
5
Starred
Starred
Snoozed
Snoozed
Important
Important
Sent
Sent
Drafts 8 unread
Drafts
8
Purchases has menu
Purchases
Social 5219 unread has menu
Social
5,219
Updates 8820 unread has menu
Updates
8,820
Forums 6143 unread has menu
Forums
6,143
Promotions 38829 unread has menu
Promotions
38,829
More labels
More
Labels
Labels
Create new label
Labels
Labels
[Imap]/Nevyžiadaná pošta has menu
[Imap]/Nevyžiadaná pošta
arch has menu
arch
Deleted Items has menu
Deleted Items
Fibank 1229 unread has menu
Fibank
1,229
FL 6 unread has menu
FL
6
Hardware & Software has menu
Hardware & Software
HOSTING 5 unread has menu
HOSTING
5
Infected Items has menu
Infected Items
jiminny-github 7541 unread has menu
jiminny-github
7,541
Junk E-mail 219 unread has menu
Junk E-mail
219
Kontakty has menu
Kontakty
Sent Items has menu
Sent Items
WORK 848 unread has menu
WORK
848
z centra 1274 unread has menu
z centra
1,274
More labels
More
Select
Refresh
More email options
Show more messages
1
–
50
of
21,312
Newer
Older
Toggle split pane mode
Input tools on/off (Ctrl-Shift-K)
Select input tool
Conversations
Conversations
Primary
Primary
Primary
Promotions, 50 new messages,
Promotions, 50 new messages,
50 new
Promotions
Social, 28 new messages,
Social, 28 new messages,
28 new
Social
Updates, 3 new messages,
Updates, 3 new messages,
3 new
Updates
unread, Proton, You have 1 new message(s) in your Proton Mail account k[PASSWORD_DOTS], 13:44, Please log in at https://mail.proton.me to check them. [Proton](https://proton.me/) You have 1 new message(s) Hello, You have 1 new message(s) in your inbox and custom folders. Please log in at https:/.
Not starred
Important according to Google magic.
Proton
You have 1 new message(s) in your Proton Mail account k[PASSWORD_DOTS] - Please log in at https://mail.proton.me to check them. [Proton](https://proton.me/) You have 1 new message(s) Hello, You have 1 new message(s) in your inbox and custom folders. Please log in at https:/
You have 1 new message(s) in your Proton Mail account k[PASSWORD_DOTS]
-
Please log in at https://mail.proton.me to check them. [Proton](https://proton.me/) You have 1 new message(s) Hello, You have 1 new message(s) in your inbox and custom folders. Please log in at https:/
13:44
Michael Savage | Th., Less than one per cent, 12:12, Living somewhere with a free press is now a rare privilege ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏.
Not starred
Not important
Michael Savage | Th.
Less than one per cent - Living somewhere with a free press is now a rare privilege ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏
Less than one per cent
-
Living somewhere with a free press is now a rare privilege ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏
12:12
unread, CircleCI Builds, [CircleCI] Workflow failed: jiminny / app on JY-20613-allow-owner-role-on-team-setup, 11:50, app / build_accept_deploy jiminny / app 2026-05-18T08:37:56Z JY-20613-allow-owner-role-on-team-setup #58486 Commit push by Lukas Kovalik (LakyLak) 6fae8bb JY-20613 support owner role on setup team.
Not starred
Important mainly because it was sent directly to you.
CircleCI Builds
[CircleCI] Workflow failed: jiminny / app on JY-20613-allow-owner-role-on-team-setup - app / build_accept_deploy jiminny / app 2026-05-18T08:37:56Z JY-20613-allow-owner-role-on-team-setup #58486 Commit push by Lukas Kovalik (LakyLak) 6fae8bb JY-20613 support owner role on setup team
[CircleCI] Workflow failed: jiminny / app on JY-20613-allow-owner-role-on-team-setup
-
app / build_accept_deploy jiminny / app 2026-05-18T08:37:56Z JY-20613-allow-owner-role-on-team-setup #58486 Commit push by Lukas Kovalik (LakyLak) 6fae8bb JY-20613 support owner role on setup team
11:50
unread, CircleCI Builds, [CircleCI] Workflow failed: jiminny / app on pipedrive-sdk-poc, 11:31, app / build_accept_deploy jiminny / app 2026-05-18T08:17:39Z pipedrive-sdk-poc #58483 Commit push by Lukas Kovalik (LakyLak) 34656c5 Document Pipedrive SDK v2 migration POC details Authored by Lukas.
Not starred
Important mainly because it was sent directly to you.
CircleCI Builds
[CircleCI] Workflow failed: jiminny / app on pipedrive-sdk-poc - app / build_accept_deploy jiminny / app 2026-05-18T08:17:39Z pipedrive-sdk-poc #58483 Commit push by Lukas Kovalik (LakyLak) 34656c5 Document Pipedrive SDK v2 migration POC details Authored by Lukas
[CircleCI] Workflow failed: jiminny / app on pipedrive-sdk-poc
-
app / build_accept_deploy jiminny / app 2026-05-18T08:17:39Z pipedrive-sdk-poc #58483 Commit push by Lukas Kovalik (LakyLak) 34656c5 Document Pipedrive SDK v2 migration POC details Authored by Lukas
11:31
unread, CircleCI Builds, [CircleCI] Workflow canceled: jiminny / app on pipedrive-sdk-poc, 11:17, app / build_accept_deploy jiminny / app 2026-05-18T08:08:54Z pipedrive-sdk-poc #58480 Commit push by Lukas Kovalik (LakyLak) 907c548 pipedrive offical SDK v2 POC Authored by Lukas Kovalik (LakyLak).
Not starred
Important mainly because it was sent directly to you.
CircleCI Builds
[CircleCI] Workflow canceled: jiminny / app on pipedrive-sdk-poc - app / build_accept_deploy jiminny / app 2026-05-18T08:08:54Z pipedrive-sdk-poc #58480 Commit push by Lukas Kovalik (LakyLak) 907c548 pipedrive offical SDK v2 POC Authored by Lukas Kovalik (LakyLak)
[CircleCI] Workflow canceled: jiminny / app on pipedrive-sdk-poc
-
app / build_accept_deploy jiminny / app 2026-05-18T08:08:54Z pipedrive-sdk-poc #58480 Commit push by Lukas Kovalik (LakyLak) 907c548 pipedrive offical SDK v2 POC Authored by Lukas Kovalik (LakyLak)
11:17
unread, Quora Suggested Spa., What is the most spectacular photograph ever taken?, 10:08, What is the most spectacular photograph ever taken? The Knowledge Hub 360 The Knowledge Hub 360 • 7.2K followers A place where ideas meet real-world value What is the most spectacular photograph ever....
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"DXP4800PLUS-B5F8","depth":4,"bounds":{"left":0.5,"top":0.0518755,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"DXP4800PLUS-B5F8","depth":5,"bounds":{"left":0.51329786,"top":0.06304868,"width":0.036901597,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Getting Started · AFFiNE","depth":4,"bounds":{"left":0.5,"top":0.08459697,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Getting Started · AFFiNE","depth":5,"bounds":{"left":0.51329786,"top":0.09577015,"width":0.04255319,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Screenpipe — Archive","depth":4,"bounds":{"left":0.5,"top":0.11731844,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Screenpipe — Archive","depth":5,"bounds":{"left":0.51329786,"top":0.12849163,"width":0.037898935,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Download screenpipe — get started in minutes","depth":4,"bounds":{"left":0.5,"top":0.15003991,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Download screenpipe — get started in minutes","depth":5,"bounds":{"left":0.51329786,"top":0.16121309,"width":0.0809508,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Self-Hosted Software and Apps","depth":4,"bounds":{"left":0.5,"top":0.18276137,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Self-Hosted Software and Apps","depth":5,"bounds":{"left":0.51329786,"top":0.19393456,"width":0.054853722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Finance Hub","depth":4,"bounds":{"left":0.5,"top":0.21548285,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Finance Hub","depth":5,"bounds":{"left":0.51329786,"top":0.22665602,"width":0.021609042,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.5,"top":0.2482043,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.51329786,"top":0.25937748,"width":0.014960106,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"As I get older, now 79, I am viewing, seeing or realizing more and more disturbing things that are finally hitting home more profoundly and affecti... - kovaliklukas@gmail.com - Gmail","depth":4,"bounds":{"left":0.5,"top":0.28092578,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"As I get older, now 79, I am viewing, seeing or realizing more and more disturbing things that are finally hitting home more profoundly and affecti... - kovaliklukas@gmail.com - Gmail","depth":5,"bounds":{"left":0.51329786,"top":0.29209897,"width":0.31382978,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Inbox (5) - kovaliklukas@gmail.com - Gmail","depth":4,"bounds":{"left":0.5,"top":0.31364724,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Inbox (5) - kovaliklukas@gmail.com - Gmail","depth":5,"bounds":{"left":0.51329786,"top":0.32482043,"width":0.07513298,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.55651593,"top":0.32083002,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Завеждане на щета онлайн | Euroins","depth":4,"bounds":{"left":0.5,"top":0.3463687,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Завеждане на щета онлайн | Euroins","depth":5,"bounds":{"left":0.51329786,"top":0.3575419,"width":0.0653258,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Photos - Google Photos","depth":4,"bounds":{"left":0.5,"top":0.3790902,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Photos - Google Photos","depth":5,"bounds":{"left":0.51329786,"top":0.39026338,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Kontakt | Veľvyslanectvo Slovenskej republiky v Sofii","depth":4,"bounds":{"left":0.5,"top":0.41181165,"width":0.06881649,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Kontakt | Veľvyslanectvo Slovenskej republiky v Sofii","depth":5,"bounds":{"left":0.51329786,"top":0.42298484,"width":0.091090426,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.5028258,"top":0.4461293,"width":0.06333112,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.5028258,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.51379657,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.5249335,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.53607047,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bitwarden","depth":6,"bounds":{"left":0.5472075,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"None selected","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Skip to content","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Using Gmail with screen readers","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Using Gmail with screen readers","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Main menu","depth":12,"bounds":{"left":0.5728058,"top":0.058260176,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXLink","text":"Gmail","depth":13,"bounds":{"left":0.5900931,"top":0.061452515,"width":0.036236703,"height":0.035115723},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Search","depth":12,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Search","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"Search mail","depth":18,"bounds":{"left":0.6722075,"top":0.06943336,"width":0.18916224,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Advanced search options","depth":12,"bounds":{"left":0.8746675,"top":0.058260176,"width":0.01861702,"height":0.03671189},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Search mail","depth":12,"bounds":{"left":0.65392286,"top":0.058260176,"width":0.01861702,"height":0.03671189},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Support","depth":12,"bounds":{"left":0.92353725,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Settings","depth":12,"bounds":{"left":0.9381649,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Ask Gemini","depth":12,"bounds":{"left":0.95212764,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Google apps","depth":14,"bounds":{"left":0.96609044,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: Lukáš Koválik (kovaliklukas@gmail.com)","depth":14,"bounds":{"left":0.98204786,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Compose","depth":9,"bounds":{"left":0.57147604,"top":0.10933759,"width":0.04737367,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":12,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Inbox 5 unread","depth":16,"bounds":{"left":0.5900931,"top":0.15003991,"width":0.012466756,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox","depth":17,"bounds":{"left":0.5900931,"top":0.15003991,"width":0.012466756,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5","depth":16,"bounds":{"left":0.64361703,"top":0.15123703,"width":0.0023271276,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Starred","depth":16,"bounds":{"left":0.5900931,"top":0.16919394,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Starred","depth":17,"bounds":{"left":0.5900931,"top":0.16919394,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Snoozed","depth":16,"bounds":{"left":0.5900931,"top":0.18834797,"width":0.018284574,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Snoozed","depth":17,"bounds":{"left":0.5900931,"top":0.18834797,"width":0.018284574,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Important","depth":17,"bounds":{"left":0.5900931,"top":0.207502,"width":0.020777926,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Important","depth":18,"bounds":{"left":0.5900931,"top":0.207502,"width":0.020777926,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sent","depth":16,"bounds":{"left":0.5900931,"top":0.22665602,"width":0.009640957,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sent","depth":17,"bounds":{"left":0.5900931,"top":0.22665602,"width":0.009640957,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Drafts 8 unread","depth":16,"bounds":{"left":0.5900931,"top":0.24581006,"width":0.013796543,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Drafts","depth":17,"bounds":{"left":0.5900931,"top":0.24581006,"width":0.013796543,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8","depth":16,"bounds":{"left":0.6437833,"top":0.24700718,"width":0.0021609042,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Purchases has menu","depth":16,"bounds":{"left":0.5900931,"top":0.26496407,"width":0.021941489,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Purchases","depth":17,"bounds":{"left":0.5900931,"top":0.26496407,"width":0.021941489,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Social 5219 unread has menu","depth":16,"bounds":{"left":0.5900931,"top":0.28411812,"width":0.013796543,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Social","depth":17,"bounds":{"left":0.5900931,"top":0.28411812,"width":0.013796543,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5,219","depth":16,"bounds":{"left":0.63663566,"top":0.28531525,"width":0.00930851,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Updates 8820 unread has menu","depth":16,"bounds":{"left":0.5900931,"top":0.30327216,"width":0.018949468,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Updates","depth":17,"bounds":{"left":0.5900931,"top":0.30327216,"width":0.018949468,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8,820","depth":16,"bounds":{"left":0.63580453,"top":0.3044693,"width":0.010139627,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Forums 6143 unread has menu","depth":16,"bounds":{"left":0.5900931,"top":0.32242617,"width":0.016788565,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Forums","depth":17,"bounds":{"left":0.5900931,"top":0.32242617,"width":0.016788565,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6,143","depth":16,"bounds":{"left":0.63663566,"top":0.3236233,"width":0.00930851,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Promotions 38829 unread has menu","depth":16,"bounds":{"left":0.5900931,"top":0.3415802,"width":0.025930852,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Promotions","depth":17,"bounds":{"left":0.5900931,"top":0.3415802,"width":0.025930852,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"38,829","depth":16,"bounds":{"left":0.6341423,"top":0.34277734,"width":0.011801862,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"More labels","depth":12,"bounds":{"left":0.5688165,"top":0.35834,"width":0.07978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"More","depth":14,"bounds":{"left":0.5900931,"top":0.36073422,"width":0.010804521,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Labels","depth":11,"bounds":{"left":0.5774601,"top":0.39984038,"width":0.061835106,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":12,"bounds":{"left":0.5774601,"top":0.39984038,"width":0.016456118,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create new label","depth":11,"bounds":{"left":0.6392952,"top":0.40023944,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":12,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[Imap]/Nevyžiadaná pošta has menu","depth":16,"bounds":{"left":0.5900931,"top":0.42817238,"width":0.055352394,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[Imap]/Nevyžiadaná pošta","depth":17,"bounds":{"left":0.5900931,"top":0.42817238,"width":0.055352394,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"arch has menu","depth":16,"bounds":{"left":0.5900931,"top":0.44732642,"width":0.009474734,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"arch","depth":17,"bounds":{"left":0.5900931,"top":0.44732642,"width":0.009474734,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Deleted Items has menu","depth":16,"bounds":{"left":0.5900931,"top":0.46648043,"width":0.029089095,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Deleted Items","depth":17,"bounds":{"left":0.5900931,"top":0.46648043,"width":0.029089095,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Fibank 1229 unread has menu","depth":16,"bounds":{"left":0.5900931,"top":0.48563448,"width":0.01512633,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Fibank","depth":17,"bounds":{"left":0.5900931,"top":0.48563448,"width":0.01512633,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1,229","depth":16,"bounds":{"left":0.63680184,"top":0.4868316,"width":0.009142287,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"FL 6 unread has menu","depth":16,"bounds":{"left":0.5900931,"top":0.5047885,"width":0.005319149,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"FL","depth":17,"bounds":{"left":0.5900931,"top":0.5047885,"width":0.005319149,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6","depth":16,"bounds":{"left":0.64361703,"top":0.5059856,"width":0.0023271276,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Hardware & Software has menu","depth":16,"bounds":{"left":0.5900931,"top":0.52394253,"width":0.044714097,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Hardware & Software","depth":17,"bounds":{"left":0.5900931,"top":0.52394253,"width":0.044714097,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"HOSTING 5 unread has menu","depth":16,"bounds":{"left":0.5900931,"top":0.54309654,"width":0.02144282,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"HOSTING","depth":17,"bounds":{"left":0.5900931,"top":0.54309654,"width":0.02144282,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5","depth":16,"bounds":{"left":0.6437833,"top":0.5442937,"width":0.0021609042,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Infected Items has menu","depth":16,"bounds":{"left":0.5900931,"top":0.5622506,"width":0.030086435,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Infected Items","depth":17,"bounds":{"left":0.5900931,"top":0.5622506,"width":0.030086435,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"jiminny-github 7541 unread has menu","depth":17,"bounds":{"left":0.5900931,"top":0.5814046,"width":0.03324468,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny-github","depth":18,"bounds":{"left":0.5900931,"top":0.5814046,"width":0.03324468,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7,541","depth":17,"bounds":{"left":0.6371343,"top":0.5826017,"width":0.00880984,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Junk E-mail 219 unread has menu","depth":16,"bounds":{"left":0.5900931,"top":0.60055864,"width":0.026761968,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Junk E-mail","depth":17,"bounds":{"left":0.5900931,"top":0.60055864,"width":0.026761968,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"219","depth":16,"bounds":{"left":0.6399601,"top":0.6017558,"width":0.005984043,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Kontakty has menu","depth":16,"bounds":{"left":0.5900931,"top":0.6197127,"width":0.018450798,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Kontakty","depth":17,"bounds":{"left":0.5900931,"top":0.6197127,"width":0.018450798,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sent Items has menu","depth":16,"bounds":{"left":0.5900931,"top":0.6388667,"width":0.022273935,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sent Items","depth":17,"bounds":{"left":0.5900931,"top":0.6388667,"width":0.022273935,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"WORK 848 unread has menu","depth":16,"bounds":{"left":0.5900931,"top":0.65802073,"width":0.014461436,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"WORK","depth":17,"bounds":{"left":0.5900931,"top":0.65802073,"width":0.014461436,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"848","depth":16,"bounds":{"left":0.6392952,"top":0.6592179,"width":0.0066489363,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"z centra 1274 unread has menu","depth":16,"bounds":{"left":0.5900931,"top":0.6771748,"width":0.018118352,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"z centra","depth":17,"bounds":{"left":0.5900931,"top":0.6771748,"width":0.018118352,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1,274","depth":16,"bounds":{"left":0.6371343,"top":0.6783719,"width":0.00880984,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"More labels","depth":12,"bounds":{"left":0.5688165,"top":0.69393456,"width":0.07978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"More","depth":14,"bounds":{"left":0.5900931,"top":0.6963288,"width":0.010804521,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Select","depth":13,"bounds":{"left":0.65924203,"top":0.11412609,"width":0.013297873,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Refresh","depth":13,"bounds":{"left":0.67918885,"top":0.11412609,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"More email options","depth":13,"bounds":{"left":0.6924867,"top":0.11412609,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show more messages","depth":14,"bounds":{"left":0.8774933,"top":0.10774142,"width":0.030751329,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":16,"bounds":{"left":0.88015294,"top":0.11612131,"width":0.0016622341,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"–","depth":16,"bounds":{"left":0.88181514,"top":0.11612131,"width":0.002493351,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"50","depth":16,"bounds":{"left":0.8843085,"top":0.11612131,"width":0.0048204786,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"of","depth":16,"bounds":{"left":0.889129,"top":0.11612131,"width":0.005817819,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"21,312","depth":16,"bounds":{"left":0.8949468,"top":0.11612131,"width":0.010638298,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Newer","depth":14,"bounds":{"left":0.91223407,"top":0.11412609,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Older","depth":14,"bounds":{"left":0.9255319,"top":0.11412609,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Toggle split pane mode","depth":13,"bounds":{"left":0.9388298,"top":0.11412609,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Input tools on/off (Ctrl-Shift-K)","depth":15,"bounds":{"left":0.9574468,"top":0.11412609,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Select input tool","depth":15,"bounds":{"left":0.9640958,"top":0.11412609,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversations","depth":14,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversations","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Primary","depth":16,"bounds":{"left":0.65392286,"top":0.14126097,"width":0.08045213,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Primary","depth":17,"bounds":{"left":0.65392286,"top":0.14126097,"width":0.07978723,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Primary","depth":20,"bounds":{"left":0.6712101,"top":0.1528332,"width":0.016954787,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Promotions, 50 new messages,","depth":16,"bounds":{"left":0.734375,"top":0.14126097,"width":0.08045213,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Promotions, 50 new messages,","depth":17,"bounds":{"left":0.734375,"top":0.14126097,"width":0.080119684,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"50 new","depth":20,"bounds":{"left":0.7805851,"top":0.15403032,"width":0.013630319,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Promotions","depth":20,"bounds":{"left":0.75166225,"top":0.1528332,"width":0.024933511,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Social, 28 new messages,","depth":16,"bounds":{"left":0.81482714,"top":0.14126097,"width":0.08045213,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Social, 28 new messages,","depth":17,"bounds":{"left":0.81482714,"top":0.14126097,"width":0.080119684,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"28 new","depth":20,"bounds":{"left":0.84923536,"top":0.15403032,"width":0.013297873,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Social","depth":20,"bounds":{"left":0.83211434,"top":0.1528332,"width":0.013131649,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Updates, 3 new messages,","depth":16,"bounds":{"left":0.8952792,"top":0.14126097,"width":0.08045213,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Updates, 3 new messages,","depth":17,"bounds":{"left":0.8952792,"top":0.14126097,"width":0.080119684,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"3 new","depth":20,"bounds":{"left":0.93484044,"top":0.15403032,"width":0.010970744,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Updates","depth":20,"bounds":{"left":0.9125665,"top":0.1528332,"width":0.018284574,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"unread, Proton, You have 1 new message(s) in your Proton Mail account k***********, 13:44, Please log in at https://mail.proton.me to check them. [Proton](https://proton.me/) You have 1 new message(s) Hello, You have 1 new message(s) in your inbox and custom folders. Please log in at https:/.","depth":19,"bounds":{"left":0.65924203,"top":0.18276137,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Not starred","depth":19,"bounds":{"left":0.66921544,"top":0.18276137,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Important according to Google magic.","depth":19,"bounds":{"left":0.67918885,"top":0.18276137,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"switch","subrole":"AXSwitch","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Proton","depth":21,"bounds":{"left":0.68916225,"top":0.18355946,"width":0.01512633,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"You have 1 new message(s) in your Proton Mail account k*********** - Please log in at https://mail.proton.me to check them. [Proton](https://proton.me/) You have 1 new message(s) Hello, You have 1 new message(s) in your inbox and custom folders. Please log in at https:/","depth":19,"bounds":{"left":0.7556516,"top":0.18276137,"width":0.18916224,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"You have 1 new message(s) in your Proton Mail account k***********","depth":23,"bounds":{"left":0.7556516,"top":0.18355946,"width":0.15392287,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"-","depth":23,"bounds":{"left":0.90957445,"top":0.18355946,"width":0.004488032,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please log in at https://mail.proton.me to check them. [Proton](https://proton.me/) You have 1 new message(s) Hello, You have 1 new message(s) in your inbox and custom folders. Please log in at https:/","depth":22,"bounds":{"left":0.9140625,"top":0.18355946,"width":0.0859375,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"13:44","depth":20,"bounds":{"left":0.96575797,"top":0.18475658,"width":0.010305851,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Michael Savage | Th., Less than one per cent, 12:12, Living somewhere with a free press is now a rare privilege ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏.","depth":19,"bounds":{"left":0.65924203,"top":0.20510775,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Not starred","depth":19,"bounds":{"left":0.66921544,"top":0.20510775,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Not important","depth":19,"bounds":{"left":0.67918885,"top":0.20510775,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"switch","subrole":"AXSwitch","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Michael Savage | Th.","depth":21,"bounds":{"left":0.68916225,"top":0.20590582,"width":0.043218084,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Less than one per cent - Living somewhere with a free press is now a rare privilege ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏","depth":19,"bounds":{"left":0.7556516,"top":0.20510775,"width":0.18916224,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Less than one per cent","depth":23,"bounds":{"left":0.7556516,"top":0.20590582,"width":0.048204787,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"-","depth":23,"bounds":{"left":0.8038564,"top":0.20590582,"width":0.004654255,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Living somewhere with a free press is now a rare privilege ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏","depth":22,"bounds":{"left":0.80851066,"top":0.20590582,"width":0.19148934,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12:12","depth":20,"bounds":{"left":0.9674202,"top":0.20710295,"width":0.008643617,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"unread, CircleCI Builds, [CircleCI] Workflow failed: jiminny / app on JY-20613-allow-owner-role-on-team-setup, 11:50, app / build_accept_deploy jiminny / app 2026-05-18T08:37:56Z JY-20613-allow-owner-role-on-team-setup #58486 Commit push by Lukas Kovalik (LakyLak) 6fae8bb JY-20613 support owner role on setup team.","depth":19,"bounds":{"left":0.65924203,"top":0.22745411,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Not starred","depth":19,"bounds":{"left":0.66921544,"top":0.22745411,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Important mainly because it was sent directly to you.","depth":19,"bounds":{"left":0.67918885,"top":0.22745411,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"switch","subrole":"AXSwitch","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CircleCI Builds","depth":21,"bounds":{"left":0.68916225,"top":0.22825219,"width":0.03324468,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[CircleCI] Workflow failed: jiminny / app on JY-20613-allow-owner-role-on-team-setup - app / build_accept_deploy jiminny / app 2026-05-18T08:37:56Z JY-20613-allow-owner-role-on-team-setup #58486 Commit push by Lukas Kovalik (LakyLak) 6fae8bb JY-20613 support owner role on setup team","depth":19,"bounds":{"left":0.7556516,"top":0.22745411,"width":0.18916224,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[CircleCI] Workflow failed: jiminny / app on JY-20613-allow-owner-role-on-team-setup","depth":23,"bounds":{"left":0.7556516,"top":0.22825219,"width":0.1966423,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"-","depth":23,"bounds":{"left":0.94481385,"top":0.22825219,"width":0.004488032,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app / build_accept_deploy jiminny / app 2026-05-18T08:37:56Z JY-20613-allow-owner-role-on-team-setup #58486 Commit push by Lukas Kovalik (LakyLak) 6fae8bb JY-20613 support owner role on setup team","depth":22,"bounds":{"left":0.94930184,"top":0.22825219,"width":0.05069816,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:50","depth":20,"bounds":{"left":0.9662567,"top":0.22944932,"width":0.009807181,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"unread, CircleCI Builds, [CircleCI] Workflow failed: jiminny / app on pipedrive-sdk-poc, 11:31, app / build_accept_deploy jiminny / app 2026-05-18T08:17:39Z pipedrive-sdk-poc #58483 Commit push by Lukas Kovalik (LakyLak) 34656c5 Document Pipedrive SDK v2 migration POC details Authored by Lukas.","depth":19,"bounds":{"left":0.65924203,"top":0.24980047,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Not starred","depth":19,"bounds":{"left":0.66921544,"top":0.24980047,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Important mainly because it was sent directly to you.","depth":19,"bounds":{"left":0.67918885,"top":0.24980047,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"switch","subrole":"AXSwitch","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CircleCI Builds","depth":21,"bounds":{"left":0.68916225,"top":0.25059855,"width":0.03324468,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[CircleCI] Workflow failed: jiminny / app on pipedrive-sdk-poc - app / build_accept_deploy jiminny / app 2026-05-18T08:17:39Z pipedrive-sdk-poc #58483 Commit push by Lukas Kovalik (LakyLak) 34656c5 Document Pipedrive SDK v2 migration POC details Authored by Lukas","depth":19,"bounds":{"left":0.7556516,"top":0.24980047,"width":0.18916224,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[CircleCI] Workflow failed: jiminny / app on pipedrive-sdk-poc","depth":23,"bounds":{"left":0.7556516,"top":0.25059855,"width":0.14045878,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"-","depth":23,"bounds":{"left":0.89611036,"top":0.25059855,"width":0.004488032,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app / build_accept_deploy jiminny / app 2026-05-18T08:17:39Z pipedrive-sdk-poc #58483 Commit push by Lukas Kovalik (LakyLak) 34656c5 Document Pipedrive SDK v2 migration POC details Authored by Lukas","depth":22,"bounds":{"left":0.9005984,"top":0.25059855,"width":0.09940159,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:31","depth":20,"bounds":{"left":0.967254,"top":0.25179568,"width":0.00880984,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"unread, CircleCI Builds, [CircleCI] Workflow canceled: jiminny / app on pipedrive-sdk-poc, 11:17, app / build_accept_deploy jiminny / app 2026-05-18T08:08:54Z pipedrive-sdk-poc #58480 Commit push by Lukas Kovalik (LakyLak) 907c548 pipedrive offical SDK v2 POC Authored by Lukas Kovalik (LakyLak).","depth":19,"bounds":{"left":0.65924203,"top":0.27214685,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Not starred","depth":19,"bounds":{"left":0.66921544,"top":0.27214685,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Important mainly because it was sent directly to you.","depth":19,"bounds":{"left":0.67918885,"top":0.27214685,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"switch","subrole":"AXSwitch","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CircleCI Builds","depth":21,"bounds":{"left":0.68916225,"top":0.27294493,"width":0.03324468,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[CircleCI] Workflow canceled: jiminny / app on pipedrive-sdk-poc - app / build_accept_deploy jiminny / app 2026-05-18T08:08:54Z pipedrive-sdk-poc #58480 Commit push by Lukas Kovalik (LakyLak) 907c548 pipedrive offical SDK v2 POC Authored by Lukas Kovalik (LakyLak)","depth":19,"bounds":{"left":0.7556516,"top":0.27214685,"width":0.18916224,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[CircleCI] Workflow canceled: jiminny / app on pipedrive-sdk-poc","depth":23,"bounds":{"left":0.7556516,"top":0.27294493,"width":0.1484375,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"-","depth":23,"bounds":{"left":0.9040891,"top":0.27294493,"width":0.004488032,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"app / build_accept_deploy jiminny / app 2026-05-18T08:08:54Z pipedrive-sdk-poc #58480 Commit push by Lukas Kovalik (LakyLak) 907c548 pipedrive offical SDK v2 POC Authored by Lukas Kovalik (LakyLak)","depth":22,"bounds":{"left":0.90857714,"top":0.27294493,"width":0.091422856,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11:17","depth":20,"bounds":{"left":0.967254,"top":0.27414206,"width":0.00880984,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"unread, Quora Suggested Spa., What is the most spectacular photograph ever taken?, 10:08, What is the most spectacular photograph ever taken? The Knowledge Hub 360 The Knowledge Hub 360 • 7.2K followers A place where ideas meet real-world value What is the most spectacular photograph ever.","depth":19,"bounds":{"left":0.65924203,"top":0.29449323,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-9086165237510857338
|
-9171883464535172399
|
visual_change
|
accessibility
|
NULL
|
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
Getting Started DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
Getting Started · AFFiNE
Getting Started · AFFiNE
Screenpipe — Archive
Screenpipe — Archive
Download screenpipe — get started in minutes
Download screenpipe — get started in minutes
Self-Hosted Software and Apps
Self-Hosted Software and Apps
Finance Hub
Finance Hub
New Tab
New Tab
As I get older, now 79, I am viewing, seeing or realizing more and more disturbing things that are finally hitting home more profoundly and affecti... - [EMAIL] - Gmail
As I get older, now 79, I am viewing, seeing or realizing more and more disturbing things that are finally hitting home more profoundly and affecti... - [EMAIL] - Gmail
Inbox (5) - [EMAIL] - Gmail
Inbox (5) - [EMAIL] - Gmail
Close tab
Завеждане на щета онлайн | Euroins
Завеждане на щета онлайн | Euroins
Photos - Google Photos
Photos - Google Photos
Kontakt | Veľvyslanectvo Slovenskej republiky v Sofii
Kontakt | Veľvyslanectvo Slovenskej republiky v Sofii
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
None selected
Skip to content
Skip to content
Using Gmail with screen readers
Using Gmail with screen readers
Main menu
Gmail
Search
Search
Search mail
Advanced search options
Search mail
Support
Settings
Ask Gemini
Google apps
Google Account: Lukáš Koválik ([EMAIL])
Compose
Labels
Labels
Inbox 5 unread
Inbox
5
Starred
Starred
Snoozed
Snoozed
Important
Important
Sent
Sent
Drafts 8 unread
Drafts
8
Purchases has menu
Purchases
Social 5219 unread has menu
Social
5,219
Updates 8820 unread has menu
Updates
8,820
Forums 6143 unread has menu
Forums
6,143
Promotions 38829 unread has menu
Promotions
38,829
More labels
More
Labels
Labels
Create new label
Labels
Labels
[Imap]/Nevyžiadaná pošta has menu
[Imap]/Nevyžiadaná pošta
arch has menu
arch
Deleted Items has menu
Deleted Items
Fibank 1229 unread has menu
Fibank
1,229
FL 6 unread has menu
FL
6
Hardware & Software has menu
Hardware & Software
HOSTING 5 unread has menu
HOSTING
5
Infected Items has menu
Infected Items
jiminny-github 7541 unread has menu
jiminny-github
7,541
Junk E-mail 219 unread has menu
Junk E-mail
219
Kontakty has menu
Kontakty
Sent Items has menu
Sent Items
WORK 848 unread has menu
WORK
848
z centra 1274 unread has menu
z centra
1,274
More labels
More
Select
Refresh
More email options
Show more messages
1
–
50
of
21,312
Newer
Older
Toggle split pane mode
Input tools on/off (Ctrl-Shift-K)
Select input tool
Conversations
Conversations
Primary
Primary
Primary
Promotions, 50 new messages,
Promotions, 50 new messages,
50 new
Promotions
Social, 28 new messages,
Social, 28 new messages,
28 new
Social
Updates, 3 new messages,
Updates, 3 new messages,
3 new
Updates
unread, Proton, You have 1 new message(s) in your Proton Mail account k[PASSWORD_DOTS], 13:44, Please log in at https://mail.proton.me to check them. [Proton](https://proton.me/) You have 1 new message(s) Hello, You have 1 new message(s) in your inbox and custom folders. Please log in at https:/.
Not starred
Important according to Google magic.
Proton
You have 1 new message(s) in your Proton Mail account k[PASSWORD_DOTS] - Please log in at https://mail.proton.me to check them. [Proton](https://proton.me/) You have 1 new message(s) Hello, You have 1 new message(s) in your inbox and custom folders. Please log in at https:/
You have 1 new message(s) in your Proton Mail account k[PASSWORD_DOTS]
-
Please log in at https://mail.proton.me to check them. [Proton](https://proton.me/) You have 1 new message(s) Hello, You have 1 new message(s) in your inbox and custom folders. Please log in at https:/
13:44
Michael Savage | Th., Less than one per cent, 12:12, Living somewhere with a free press is now a rare privilege ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏.
Not starred
Not important
Michael Savage | Th.
Less than one per cent - Living somewhere with a free press is now a rare privilege ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏
Less than one per cent
-
Living somewhere with a free press is now a rare privilege ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏
12:12
unread, CircleCI Builds, [CircleCI] Workflow failed: jiminny / app on JY-20613-allow-owner-role-on-team-setup, 11:50, app / build_accept_deploy jiminny / app 2026-05-18T08:37:56Z JY-20613-allow-owner-role-on-team-setup #58486 Commit push by Lukas Kovalik (LakyLak) 6fae8bb JY-20613 support owner role on setup team.
Not starred
Important mainly because it was sent directly to you.
CircleCI Builds
[CircleCI] Workflow failed: jiminny / app on JY-20613-allow-owner-role-on-team-setup - app / build_accept_deploy jiminny / app 2026-05-18T08:37:56Z JY-20613-allow-owner-role-on-team-setup #58486 Commit push by Lukas Kovalik (LakyLak) 6fae8bb JY-20613 support owner role on setup team
[CircleCI] Workflow failed: jiminny / app on JY-20613-allow-owner-role-on-team-setup
-
app / build_accept_deploy jiminny / app 2026-05-18T08:37:56Z JY-20613-allow-owner-role-on-team-setup #58486 Commit push by Lukas Kovalik (LakyLak) 6fae8bb JY-20613 support owner role on setup team
11:50
unread, CircleCI Builds, [CircleCI] Workflow failed: jiminny / app on pipedrive-sdk-poc, 11:31, app / build_accept_deploy jiminny / app 2026-05-18T08:17:39Z pipedrive-sdk-poc #58483 Commit push by Lukas Kovalik (LakyLak) 34656c5 Document Pipedrive SDK v2 migration POC details Authored by Lukas.
Not starred
Important mainly because it was sent directly to you.
CircleCI Builds
[CircleCI] Workflow failed: jiminny / app on pipedrive-sdk-poc - app / build_accept_deploy jiminny / app 2026-05-18T08:17:39Z pipedrive-sdk-poc #58483 Commit push by Lukas Kovalik (LakyLak) 34656c5 Document Pipedrive SDK v2 migration POC details Authored by Lukas
[CircleCI] Workflow failed: jiminny / app on pipedrive-sdk-poc
-
app / build_accept_deploy jiminny / app 2026-05-18T08:17:39Z pipedrive-sdk-poc #58483 Commit push by Lukas Kovalik (LakyLak) 34656c5 Document Pipedrive SDK v2 migration POC details Authored by Lukas
11:31
unread, CircleCI Builds, [CircleCI] Workflow canceled: jiminny / app on pipedrive-sdk-poc, 11:17, app / build_accept_deploy jiminny / app 2026-05-18T08:08:54Z pipedrive-sdk-poc #58480 Commit push by Lukas Kovalik (LakyLak) 907c548 pipedrive offical SDK v2 POC Authored by Lukas Kovalik (LakyLak).
Not starred
Important mainly because it was sent directly to you.
CircleCI Builds
[CircleCI] Workflow canceled: jiminny / app on pipedrive-sdk-poc - app / build_accept_deploy jiminny / app 2026-05-18T08:08:54Z pipedrive-sdk-poc #58480 Commit push by Lukas Kovalik (LakyLak) 907c548 pipedrive offical SDK v2 POC Authored by Lukas Kovalik (LakyLak)
[CircleCI] Workflow canceled: jiminny / app on pipedrive-sdk-poc
-
app / build_accept_deploy jiminny / app 2026-05-18T08:08:54Z pipedrive-sdk-poc #58480 Commit push by Lukas Kovalik (LakyLak) 907c548 pipedrive offical SDK v2 POC Authored by Lukas Kovalik (LakyLak)
11:17
unread, Quora Suggested Spa., What is the most spectacular photograph ever taken?, 10:08, What is the most spectacular photograph ever taken? The Knowledge Hub 360 The Knowledge Hub 360 • 7.2K followers A place where ideas meet real-world value What is the most spectacular photograph ever....
|
52706
|
NULL
|
NULL
|
NULL
|
|
20287
|
873
|
18
|
2026-05-11T14:52:47.736165+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778511167736_m2.jpg...
|
Code
|
Client.php (Working Tree) (Client.php) — app — 9 p Client.php (Working Tree) (Client.php) — app — 9 problems in this file • Modified...
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧ Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧G) - 17 pending changes
17
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update
2
Testing
Claude Code
SOURCE CONTROL
SOURCE CONTROL
Changes Section
CHANGES
CHANGES
Commit Changes on "JY-20725-handle-HS-search-rate-limit"
Commit
More Actions...
Changes
17
.env.local
M
.env.nikilocal
U
.env.other
U
ids.txt
U
raw_sql_query.sql
U
WEBHOOK_FILTERING_IMPLEMENTATION.md
U
JiminnyDebugCommand.php
app/Console/Commands
M
SimulateWebhooksCommand.php
app/Console/Commands/Crm/Hubspot
U
CreateMockAskJiminnyReportResultCommand.php
app/Console/Commands/Reports
U
RateLimitException.php
app/Exceptions
M
MatchActivityCrmData.php
app/Jobs/Crm
M
Client.php
app/Services/Crm/Hubspot
9, M
logging.php
config
M
favicon.ico
public
U
HandleHubspotRateLimitTest.php
tests/Unit/Jobs/Middleware
M
CanAccessAiReportsTest.php
tests/Unit/Policies
U
ClientTest.php
tests/Unit/Services/Crm/Hubspot
M
Graph Section
GRAPH
GRAPH
JY-20725 add more tests
Lukas Kovalik
JY-20725-handle-HS-search-rate-limit
JY-20725 add tests
Lukas Kovalik
JY-20725 add HS rate limit handling on activities rematching
Lukas Kovalik
Merge pull request #12056 from jiminny/JY-20818-move-AJ-reports-to-separated-datadog-metric
Lukas Kovalik
origin/master
Merge branch 'master' into JY-20818-move-AJ-reports-to-separated-datadog-metric
Lukas Kovalik
Merge pull request #12057 from jiminny/JY-20819-increase-download-transctip-rate-limit
mihailmihaylovjiminny
JY-20819: Increase download transcription rate limit
mihail.mihaylov
JY-20818 move ask jiminny reports to its own datadog metric
Lukas Kovalik
Merge pull request #12052 from jiminny/JY-20817-fix-deleting-old-tracks
mihailmihaylovjiminny
Merge branch 'master' into JY-20817-fix-deleting-old-tracks
mihailmihaylovjiminny
Merge pull request #12047 from jiminny/JY-20662-remove-word-boost
ilian-jiminny
JY-20662 return void
ilian
JY-20662 Removed word_boost parameter from Assembly transcription request
ilian
JY-20817: Fix deleting old tracks
mihail.mihaylov
Merge pull request #12043 from jiminny/JY-19938-bot-status-in-csv-es
ilian-jiminny
Merge branch 'master' into JY-19938-bot-status-in-csv-es
ilian-jiminny
Merge pull request #12044 from jiminny/JY-19938-ES-mapping-update
ilian-jiminny
JY-19938 Added consent information and recording error reason to Elastic Search mapping
ilian
JY-19938 reorder array map
ilian
JY-19938: Address PR review comments
ilian
JY-19938 lint fixes
ilian
HandleHubspotRateLimitTest.php (Working Tree) (HandleHubspotRateLimitTest.php), Editor Group 1
ClientTest.php (Working Tree) (ClientTest.php), Editor Group 1
Client.php (Working Tree) (Client.php), Editor Group 1
MatchActivityCrmData.php (Working Tree) (MatchActivityCrmData.php), Editor Group 1
RateLimitException.php (Working Tree) (RateLimitException.php), Editor Group 1
Review rate limit handli…, Editor Group 2
remote
app (Git) - JY-20725-handle-HS-search-rate-limit*, Checkout Branch/Tag...
JY-20725-handle-HS-search-rate-limit*
app (Git) - Synchronize Changes
Errors: 6, Warnings: 11, Infos: 28
6
11
28
Notifications
key, PHP extension: Premium features not active.
8.3, Language Level: 8.3
Sign In
Sign In
PHP
LF
UTF-8
Spaces: 4
Ln 120, Col 1
git-commit Not Committed Yet
Not Committed Yet
Diff editor
Clear
Review rate limit handling for HubSpot job queue
Session history
New session
Message actions
RateLimitException.php#9-9
RateLimitException.php#9-9
Reveiw the diff. Mostly
@app/Jobs/Crm/MatchActivityCrmData.php
,
@app/Exceptions/RateLimitException.php
,
@app/Jobs/Middleware/HandleHubspotRateLimit.php
,
@app/Services/Crm/Hubspot/Client.php
and
@app/Services/Crm/Hubspot/Pagination/HubspotPaginationService.php
. The issue is I am trying to make sure that instant spike of MatchActivityCrmData jobs willl handle 429 exceptions from hubspot. Revewi ti carefully and try to see potential issues and bottlenecks. Also RateLimitException was changerd from LogicException to RuntimeException. Is it ok. This exception is used by other paerts of code. Will ti break anything?
Thought for 5s
Thought for 5s
I'll review the implementation carefully. Let me first check how
RateLimitException
is used elsewhere and look at the test files and
PaginationState
.
Bash
Find all usages of RateLimitException outside the changed files
IN
grep -rn "RateLimitException" /Users/lukas/jiminny/app/app --include="*.php" | grep -v "HandleHubspotRateLimit\|MatchActivityCrmData\|Client.php\|RateLimitException.php"
Copy code to clipboard
OUT
/Users/lukas/jiminny/app/app/Component/ActionItems/Jobs/GenerateActionItemsJob.php:13:use Jiminny\Exceptions\RateLimitException;
/Users/lukas/jiminny/app/app/Component/ActionItems/Jobs/GenerateActionItemsJob.php:51: GuzzleException|HttpBadRequestException|JsonException|RateLimitException|UnexpectedValueException $exception
/Users/lukas/jiminny/app/app/Component/AiCallScoring/Jobs/GenerateAiCallScoringJob.php:17:use Jiminny\Exceptions\RateLimitException;
/Users/lukas/jiminny/app/app/Component/AiCallScoring/Jobs/GenerateAiCallScoringJob.php:68: RateLimitException |
/Users/lukas/jiminny/app/app/Component/TranscriptionSummary/Jobs/SummarizeTranscriptionJob.php:15:use Jiminny\Exceptions\RateLimitException;
/Users/lukas/jiminny/app/app/Component/TranscriptionSummary/Jobs/SummarizeTranscriptionJob.php:58: GuzzleException|HttpBadRequestException|JsonException|RateLimitException|UnexpectedValueException $exception
/Users/lukas/jiminny/app/app/Component/AiActivityType/Jobs/GenerateAiActivityTypeJob.php:19:use Jiminny\Exceptions\RateLimitException;
/Users/lukas/jiminny/app/app/Component/AiActivityType/Jobs/GenerateAiActivityTypeJob.php:76: RateLimitException |
/Users/lukas/jiminny/app/app/Services/Crm/Hubspot/HubspotClientInterface.php:8:use Jiminny\Exceptions\RateLimitException;
/Users/lukas/jiminny/app/app/Services/Crm/Hubspot/HubspotClientInterface.php:50: * @throws RateLimitException When rate limit is hit
/Users/lukas/jiminny/app/app/Services/Crm/Hubspot/Pagination/HubspotPaginationService.php:195: // RateLimitException and other exceptions are re-thrown as-is
Read
PaginationState.php
PaginationState.php
Read
HandleHubspotRateLimitTest.php
HandleHubspotRateLimitTest.php
Bash
Check how RateLimitException is used in GenerateActionItemsJob
IN
grep -rn "RateLimitException\|LogicException\|RuntimeException" /Users/lukas/jiminny/app/app/Component/ActionItems/Jobs/GenerateActionItemsJob.php | head -20
Copy code to clipboard
OUT
/Users/lukas/jiminny/app/app/Component/ActionItems/Jobs/GenerateActionItemsJob.php:13:use Jiminny\Exceptions\RateLimitException;
/Users/lukas/jiminny/app/app/Component/ActionItems/Jobs/GenerateActionItemsJob.php:51: GuzzleException|HttpBadRequestException|JsonException|RateLimitException|UnexpectedValueException $exception
Bash
Find all catch blocks or instanceof checks for RateLimitException
IN
grep -rn "catch.*RateLimitException\|instanceof RateLimitException" /Users/lukas/jiminny/app/app --include="*.php"
Copy code to clipboard
OUT
/Users/lukas/jiminny/app/app/Jobs/Middleware/HandleHubspotRateLimit.php:11: * Job middleware that catches RateLimitException from HubSpot API calls
/Users/lukas/jiminny/app/app/Jobs/Middleware/HandleHubspotRateLimit.php:24: } catch (RateLimitException $e) {
/Users/lukas/jiminny/app/app/Jobs/Crm/MatchActivityCrmData.php:149: if (! $e instanceof RateLimitException) {
Bash
Find all test references to RateLimitException
IN
grep -rn "RateLimitException" /Users/lukas/jiminny/app/tests --include="*.php"
Copy code to clipboard
OUT
/Users/lukas/jiminny/app/tests/Unit/Exceptions/RateLimitExceptionTest.php:8:use Jiminny\Exceptions\RateLimitException;
/Users/lukas/jiminny/app/tests/Unit/Exceptions/RateLimitExceptionTest.php:13:#[CoversClass(RateLimitException::class)]
/Users/lukas/jiminny/app/tests/Unit/Exceptions/RateLimitExceptionTest.php:14:class RateLimitExceptionTest extends TestCase
/Users/lukas/jiminny/app/tests/Unit/Exceptions/RateLimitExceptionTest.php:18: $exception = new RateLimitException();
/Users/lukas/jiminny/app/tests/Unit/Exceptions/RateLimitExceptionTest.php:28: $exception = new RateLimitException('Hubspot rate limit', 30, $previous);
/Users/lukas/jiminny/app/tests/Unit/Exceptions/RateLimitExceptionTest.php:52: $exception = new RateLimitException('test', $input);
/Users/lukas/jiminny/app/tests/Unit/Jobs/Middleware/HandleHubspotRateLimitTest.php:10:use Jiminny\Exceptions\RateLimitException;
/Users/lukas/jiminny/app/tests/Unit/Jobs/Middleware/HandleHubspotRateLimitTest.php:49: public function testPropagatesNonRateLimitExceptions(): void
/Users/lukas/jiminny/app/tests/Unit/Jobs/Middleware/HandleHubspotRateLimitTest.php:103: throw new RateLimitException('rate limited', $retryAfter);
/Users/lukas/jiminny/app/tests/Unit/Jobs/Middleware/HandleHubspotRateLimitTest.php:150: throw new RateLimitException('rate limited', 1);
Bash
Check retry/failure handling in GenerateActionItemsJob
IN
grep -n "RateLimitException\|retryUntil\|backoff\|maxExceptions\|maxTries\|release\|failed" /Users/lukas/jiminny/app/app/Component/ActionItems/Jobs/GenerateActionItemsJob.php | head -30
Copy code to clipboard
OUT
13:use Jiminny\Exceptions\RateLimitException;
51: GuzzleException|HttpBadRequestException|JsonException|RateLimitException|UnexpectedValueException $exception
56: $this->release(30);
Bash
Check retry/failure handling in GenerateAiCallScoringJob
IN
grep -n "RateLimitException\|retryUntil\|backoff\|maxExceptions\|maxTries\|release\|failed" /Users/lukas/jiminny/app/app/Component/AiCallScoring/Jobs/GenerateAiCallScoringJob.php | head -30
Copy code to clipboard
OUT
17:use Jiminny\Exceptions\RateLimitException;
68: RateLimitException |
75: $this->release(30);
Bash
Check retry config in MatchActivityCrmData
IN
grep -n "retryAfter\|RETRY_WINDOW\|retryUntil\|maxTries" /Users/lukas/jiminny/app/app/Jobs/Crm/MatchActivityCrmData.php
Copy code to clipboard
OUT
34: private const int RETRY_WINDOW_MINUTES = 30;
45: public function retryUntil(): \DateTimeInterface
47: return now()->addMinutes(self::RETRY_WINDOW_MINUTES);
77: return self::RETRY_WINDOW_MINUTES * 60 + 60;
Bash
Check the cache key format
IN
grep -n "getRateLimitCacheKey\|hubspot:ratelimit" /Users/lukas/jiminny/app/app/Services/Crm/Hubspot/Client.php
Copy code to clipboard
OUT
78: $cacheKey = $this->getRateLimitCacheKey();
110: private function getRateLimitCacheKey(): string
112: return sprintf('hubspot:ratelimit:portal:%d', $this->config->getId());
Bash
Find other jobs that might use the rate limit middleware
IN
grep -rn "class.*Middleware\|HandleHubspotRateLimit" /Users/lukas/jiminny/app/app/Jobs --include="*.php" | grep -v "HandleHubspotRateLimit.php"
Copy code to clipboard
OUT
/Users/lukas/jiminny/app/app/Jobs/Crm/MatchActivityCrmData.php:18:use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
/Users/lukas/jiminny/app/app/Jobs/Crm/MatchActivityCrmData.php:42: return [new HandleHubspotRateLimit()];
Read
HubspotClientInterface.php
HubspotClientInterface.php
Thought for 73s
Thought for 73s
Now I have a complete picture. Here's my full analysis:
Review Summary
Review Summary
RuntimeException vs LogicException — Safe ✓
RuntimeException
vs
LogicException
— Safe ✓
The change is semantically correct and won't break anything.
LogicException
is for programmer errors (invalid arguments, contract violations). A rate limit from an external API is a runtime condition —
RuntimeException
is the right parent. All callers catch
RateLimitException
by name explicitly (union catches in
GenerateActionItemsJob
,
GenerateAiCallScoringJob
,
SummarizeTranscriptionJob
,
GenerateAiActivityTypeJob
) or check
instanceof RateLimitException
— none relies on the parent class type, so nothing breaks.
Issue 1 — Circuit breaker stores stale delay (most important bug)
Issue 1 — Circuit breaker stores stale delay (most important bug)
Client.php:94
Client.php:94
Copy code to clipboard
Redis::setex($cacheKey, $retryAfter, (string) $retryAfter);
The TTL is
$retryAfter
seconds, but the
stored value
is also the original
$retryAfter
. If a second job reads this cache 20 seconds later, it throws
RateLimitException(retryAfter=30)
and gets delayed 30 more seconds — even though only 10 remain. In a spike of 100 jobs, each subsequent job inflates its wait.
Fix:
store the absolute expiry timestamp, reconstruct the remaining delay when reading:
Copy code to clipboard
// on 429:
Redis::setex($cacheKey, $retryAfter, (string) (time() + $retryAfter));
// in the cached-read branch:
$remaining = max(1, (int) $cachedRetryAfter - time());
throw new RateLimitException('Hubspot rate limit (cached circuit-breaker)', $remaining);
Issue 2 — retryUntil() window may expire under sustained rate limiting
Issue 2 —
retryUntil()
window may expire under sustained rate limiting
MatchActivityCrmData.php:45-47
MatchActivityCrmData.php:45-47
retryUntil()
is calculated as
now() + 30 min
at
dispatch time
and stored in the job payload. With
MAX_RETRY_DELAY = 600s
, you only get ~3 rate-limit-triggered releases before the 30-minute window closes and the job is permanently failed. For a heavy 429 spike (daily limit, parsed as 600s), this can easily happen.
Consider increasing
RETRY_WINDOW_MINUTES
to 60 or more, or making it configurable. The
uniqueFor()
value should be updated to match.
Issue 3 — Only search() uses the circuit breaker, but other methods can still hit 429
Issue 3 — Only
search()
uses the circuit breaker, but other methods can still hit 429
Client.php:76-108
Client.php:76-108
executeRequest()
is only called from
search()
. Methods like
getOpportunityById()
,
batchReadObjects()
,
getContactByEmail()
bypass it entirely. If HubSpot's 429 is account-wide (not search-endpoint-specific), those methods will keep firing even while the circuit breaker is supposedly open. This is likely fine because the search endpoint has its own tighter quota (5 RPS), but it's worth being explicit in a comment.
Issue 4 — object $job type in middleware has no compile-time safety
Issue 4 —
object $job
type in middleware has no compile-time safety
HandleHubspotRateLimit.php:20
HandleHubspotRateLimit.php:20
Copy code to clipboard
public function handle(object $job, callable $next): void
Calling
$job->attempts()
and
$job->release()
on
object
will work at runtime since Laravel always passes jobs that use
InteractsWithQueue
, but PHPStan/psalm won't verify this and it could silently break in tests with bare mocks (as your test does use a
Job
mock that has
attempts()
— so the test is fine, but the real type contract is invisible). Minor, but worth tightening to
\Illuminate\Contracts\Queue\Job
.
What works well ✓
What works well ✓
RateLimitException
is caught by the middleware...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Explorer (⇧⌘E)","depth":19,"bounds":{"left":0.0,"top":0.047885075,"width":0.015957447,"height":0.03830806},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":22,"bounds":{"left":0.0039893617,"top":0.057462092,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Search (⇧⌘F)","depth":19,"bounds":{"left":0.0,"top":0.08619314,"width":0.015957447,"height":0.03830806},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":22,"bounds":{"left":0.0039893617,"top":0.09577015,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Source Control (⌃⇧G) - 17 pending changes","depth":19,"bounds":{"left":0.0,"top":0.1245012,"width":0.015957447,"height":0.03830806},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":true},{"role":"AXStaticText","text":"","depth":22,"bounds":{"left":0.0039893617,"top":0.13407822,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"17","depth":22,"bounds":{"left":0.00831117,"top":0.1452514,"width":0.003656915,"height":0.008778931},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.008643617,"top":0.14604948,"width":0.0016622341,"height":0.007980846}},{"char_start":1,"char_count":1,"bounds":{"left":0.009973404,"top":0.14604948,"width":0.0019946808,"height":0.007980846}}],"role_description":"text"},{"role":"AXRadioButton","text":"Run and Debug (⇧⌘D)","depth":19,"bounds":{"left":0.0,"top":0.16280925,"width":0.015957447,"height":0.03830806},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":22,"bounds":{"left":0.0039893617,"top":0.17238627,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Remote Explorer","depth":19,"bounds":{"left":0.0,"top":0.20111732,"width":0.015957447,"height":0.03830806},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":22,"bounds":{"left":0.0039893617,"top":0.21069433,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Extensions (⇧⌘X) - 2 require update","depth":19,"bounds":{"left":0.0,"top":0.23942538,"width":0.015957447,"height":0.03830806},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":22,"bounds":{"left":0.0039893617,"top":0.2490024,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":22,"bounds":{"left":0.009640957,"top":0.2601756,"width":0.0019946808,"height":0.008778931},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Testing","depth":19,"bounds":{"left":0.0,"top":0.27773345,"width":0.015957447,"height":0.03830806},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":22,"bounds":{"left":0.0039893617,"top":0.28731045,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Claude Code","depth":19,"bounds":{"left":0.0,"top":0.3160415,"width":0.015957447,"height":0.03830806},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"SOURCE CONTROL","depth":17,"bounds":{"left":0.022606382,"top":0.047885075,"width":0.034242023,"height":0.02793296},"on_screen":true,"role_description":"heading"},{"role":"AXStaticText","text":"SOURCE CONTROL","depth":18,"bounds":{"left":0.022606382,"top":0.056664005,"width":0.034242023,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Changes Section","depth":21,"bounds":{"left":0.015957447,"top":0.07581804,"width":0.09940159,"height":0.017557861},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"","depth":23,"bounds":{"left":0.01662234,"top":0.07821229,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXHeading","text":"CHANGES","depth":22,"bounds":{"left":0.022606382,"top":0.07581804,"width":0.01861702,"height":0.017557861},"on_screen":true,"role_description":"heading"},{"role":"AXStaticText","text":"CHANGES","depth":23,"bounds":{"left":0.022606382,"top":0.079010375,"width":0.01861702,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXTextArea","text":"","depth":28,"bounds":{"left":0.024601065,"top":0.09896249,"width":0.07247341,"height":0.015961692},"on_screen":true,"value":"","role_description":"editor","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":29,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Commit Changes on \"JY-20725-handle-HS-search-rate-limit\"","depth":26,"bounds":{"left":0.022273935,"top":0.1245012,"width":0.08045213,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.052526597,"top":0.12849163,"width":0.0056515955,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Commit","depth":27,"bounds":{"left":0.058843084,"top":0.1292897,"width":0.014960106,"height":0.011173184},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.059175532,"top":0.1292897,"width":0.0029920214,"height":0.011173184}},{"char_start":1,"char_count":5,"bounds":{"left":0.061835106,"top":0.1292897,"width":0.011968086,"height":0.011173184}}],"role_description":"text"},{"role":"AXPopUpButton","text":"More Actions...","depth":26,"bounds":{"left":0.10305851,"top":0.1245012,"width":0.00831117,"height":0.0207502},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":28,"bounds":{"left":0.1043883,"top":0.12849163,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.019614361,"top":0.15163608,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Changes","depth":27,"bounds":{"left":0.025930852,"top":0.15163608,"width":0.01761968,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.15243416,"width":0.0029920214,"height":0.011971269}},{"char_start":1,"char_count":6,"bounds":{"left":0.028922873,"top":0.15243416,"width":0.014960106,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"17","depth":27,"bounds":{"left":0.10571808,"top":0.15243416,"width":0.0039893617,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.10605053,"top":0.15243416,"width":0.0016622341,"height":0.0103751}},{"char_start":1,"char_count":1,"bounds":{"left":0.10771277,"top":0.15243416,"width":0.0019946808,"height":0.0103751}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":28,"bounds":{"left":0.021276595,"top":0.16759777,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".env.local","depth":28,"bounds":{"left":0.028590426,"top":0.16919394,"width":0.01861702,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.16999201,"width":0.0013297872,"height":0.011971269}},{"char_start":1,"char_count":9,"bounds":{"left":0.029920213,"top":0.16999201,"width":0.01761968,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"M","depth":28,"bounds":{"left":0.106715426,"top":0.16999201,"width":0.003656915,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":28,"bounds":{"left":0.021276595,"top":0.18515563,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".env.nikilocal","depth":28,"bounds":{"left":0.028590426,"top":0.1867518,"width":0.025598405,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.18754987,"width":0.0013297872,"height":0.011971269}},{"char_start":1,"char_count":13,"bounds":{"left":0.029920213,"top":0.18754987,"width":0.024268618,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"U","depth":28,"bounds":{"left":0.107380316,"top":0.18754987,"width":0.0029920214,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":28,"bounds":{"left":0.021276595,"top":0.20271349,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".env.other","depth":28,"bounds":{"left":0.028590426,"top":0.20430966,"width":0.019946808,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.20510775,"width":0.0013297872,"height":0.011971269}},{"char_start":1,"char_count":9,"bounds":{"left":0.029920213,"top":0.20510775,"width":0.01861702,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"U","depth":28,"bounds":{"left":0.107380316,"top":0.20510775,"width":0.0029920214,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":28,"bounds":{"left":0.021276595,"top":0.22027135,"width":0.0076462766,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ids.txt","depth":28,"bounds":{"left":0.028590426,"top":0.22186752,"width":0.012300532,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.22266561,"width":0.0009973404,"height":0.011971269}},{"char_start":1,"char_count":6,"bounds":{"left":0.029587766,"top":0.22266561,"width":0.011635638,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"U","depth":28,"bounds":{"left":0.107380316,"top":0.22266561,"width":0.0029920214,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":28,"bounds":{"left":0.021276595,"top":0.23782921,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"raw_sql_query.sql","depth":28,"bounds":{"left":0.028590426,"top":0.23942538,"width":0.035904255,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.24022347,"width":0.0016622341,"height":0.011971269}},{"char_start":1,"char_count":16,"bounds":{"left":0.03025266,"top":0.24022347,"width":0.034242023,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"U","depth":28,"bounds":{"left":0.107380316,"top":0.24022347,"width":0.0029920214,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":28,"bounds":{"left":0.021276595,"top":0.25538707,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"WEBHOOK_FILTERING_IMPLEMENTATION.md","depth":28,"bounds":{"left":0.028590426,"top":0.25698325,"width":0.076130316,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.25778133,"width":0.0043218085,"height":0.011971269}},{"char_start":1,"char_count":34,"bounds":{"left":0.032912236,"top":0.25778133,"width":0.08909574,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"U","depth":28,"bounds":{"left":0.107380316,"top":0.25778133,"width":0.0029920214,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":28,"bounds":{"left":0.021276595,"top":0.27294493,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"JiminnyDebugCommand.php","depth":28,"bounds":{"left":0.028590426,"top":0.2745411,"width":0.05817819,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.2753392,"width":0.0023271276,"height":0.011971269}},{"char_start":1,"char_count":22,"bounds":{"left":0.030917553,"top":0.2753392,"width":0.055851065,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"app/Console/Commands","depth":28,"bounds":{"left":0.08843085,"top":0.2745411,"width":0.01462766,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.0887633,"top":0.27613726,"width":0.0023271276,"height":0.011173184}},{"char_start":1,"char_count":19,"bounds":{"left":0.09075798,"top":0.27613726,"width":0.043218084,"height":0.011173184}}],"role_description":"text"},{"role":"AXStaticText","text":"M","depth":28,"bounds":{"left":0.106715426,"top":0.2753392,"width":0.003656915,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":28,"bounds":{"left":0.021276595,"top":0.2905028,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SimulateWebhooksCommand.php","depth":28,"bounds":{"left":0.028590426,"top":0.29209897,"width":0.06781915,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.29289705,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":26,"bounds":{"left":0.03125,"top":0.29289705,"width":0.065159574,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"app/Console/Commands/Crm/Hubspot","depth":28,"bounds":{"left":0.098071806,"top":0.29209897,"width":0.005984043,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.09840426,"top":0.29369512,"width":0.0023271276,"height":0.011173184}},{"char_start":1,"char_count":31,"bounds":{"left":0.100398935,"top":0.29369512,"width":0.06881649,"height":0.011173184}}],"role_description":"text"},{"role":"AXStaticText","text":"U","depth":28,"bounds":{"left":0.107380316,"top":0.29289705,"width":0.0029920214,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":28,"bounds":{"left":0.021276595,"top":0.30806065,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"CreateMockAskJiminnyReportResultCommand.php","depth":28,"bounds":{"left":0.028590426,"top":0.30965683,"width":0.07646277,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.3104549,"width":0.0029920214,"height":0.011971269}},{"char_start":1,"char_count":42,"bounds":{"left":0.03158245,"top":0.3104549,"width":0.099734046,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"app/Console/Commands/Reports","depth":28,"bounds":{"left":0.10538564,"top":0.3104549,"width":0.0003324468,"height":0.011173184},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.13331117,"top":0.31125298,"width":0.0023271276,"height":0.011173184}},{"char_start":1,"char_count":27,"bounds":{"left":0.13530585,"top":0.31125298,"width":0.05851064,"height":0.011173184}}],"role_description":"text"},{"role":"AXStaticText","text":"U","depth":28,"bounds":{"left":0.107380316,"top":0.3104549,"width":0.0029920214,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":28,"bounds":{"left":0.021276595,"top":0.3256185,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"RateLimitException.php","depth":28,"bounds":{"left":0.028590426,"top":0.3272147,"width":0.047539894,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.32801276,"width":0.0029920214,"height":0.011971269}},{"char_start":1,"char_count":21,"bounds":{"left":0.03158245,"top":0.32801276,"width":0.04488032,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"app/Exceptions","depth":28,"bounds":{"left":0.078125,"top":0.3272147,"width":0.024933511,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.078125,"top":0.32881084,"width":0.0023271276,"height":0.011173184}},{"char_start":1,"char_count":13,"bounds":{"left":0.08045213,"top":0.32881084,"width":0.026263298,"height":0.011173184}}],"role_description":"text"},{"role":"AXStaticText","text":"M","depth":28,"bounds":{"left":0.106715426,"top":0.32801276,"width":0.003656915,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":28,"bounds":{"left":0.021276595,"top":0.34317636,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"MatchActivityCrmData.php","depth":28,"bounds":{"left":0.028590426,"top":0.34477255,"width":0.054521278,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.34557062,"width":0.0039893617,"height":0.011971269}},{"char_start":1,"char_count":23,"bounds":{"left":0.032579787,"top":0.34557062,"width":0.050531916,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"app/Jobs/Crm","depth":28,"bounds":{"left":0.084773935,"top":0.34477255,"width":0.020279255,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.084773935,"top":0.3463687,"width":0.0023271276,"height":0.011173184}},{"char_start":1,"char_count":11,"bounds":{"left":0.087101065,"top":0.3463687,"width":0.023603724,"height":0.011173184}}],"role_description":"text"},{"role":"AXStaticText","text":"M","depth":28,"bounds":{"left":0.106715426,"top":0.34557062,"width":0.003656915,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":28,"bounds":{"left":0.021276595,"top":0.36073422,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Client.php","depth":28,"bounds":{"left":0.028590426,"top":0.3623304,"width":0.020611702,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.36312848,"width":0.0029920214,"height":0.011971269}},{"char_start":1,"char_count":9,"bounds":{"left":0.03158245,"top":0.36312848,"width":0.01761968,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"app/Services/Crm/Hubspot","depth":28,"bounds":{"left":0.05119681,"top":0.3623304,"width":0.028590426,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.05119681,"top":0.3639266,"width":0.0023271276,"height":0.011173184}},{"char_start":1,"char_count":23,"bounds":{"left":0.053523935,"top":0.3639266,"width":0.047539894,"height":0.011173184}}],"role_description":"text"},{"role":"AXStaticText","text":"9, M","depth":28,"bounds":{"left":0.10206117,"top":0.36312848,"width":0.00831117,"height":0.011173184},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.10206117,"top":0.36312848,"width":0.0026595744,"height":0.011173184}},{"char_start":1,"char_count":3,"bounds":{"left":0.10472074,"top":0.36312848,"width":0.005984043,"height":0.011173184}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":28,"bounds":{"left":0.021276595,"top":0.3782921,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"logging.php","depth":28,"bounds":{"left":0.028590426,"top":0.37988827,"width":0.023936171,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.38068634,"width":0.0009973404,"height":0.011971269}},{"char_start":1,"char_count":10,"bounds":{"left":0.029587766,"top":0.38068634,"width":0.022938829,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"config","depth":28,"bounds":{"left":0.05418883,"top":0.38068634,"width":0.011968086,"height":0.011173184},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.054521278,"top":0.38148445,"width":0.0023271276,"height":0.011173184}},{"char_start":1,"char_count":5,"bounds":{"left":0.05651596,"top":0.38148445,"width":0.009640957,"height":0.011173184}}],"role_description":"text"},{"role":"AXStaticText","text":"M","depth":28,"bounds":{"left":0.106715426,"top":0.38068634,"width":0.003656915,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":28,"bounds":{"left":0.021276595,"top":0.39584997,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"favicon.ico","depth":28,"bounds":{"left":0.028590426,"top":0.39744613,"width":0.021609042,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.3982442,"width":0.0016622341,"height":0.011971269}},{"char_start":1,"char_count":10,"bounds":{"left":0.03025266,"top":0.3982442,"width":0.020279255,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"public","depth":28,"bounds":{"left":0.05219415,"top":0.3982442,"width":0.011303191,"height":0.011173184},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.05219415,"top":0.3990423,"width":0.0026595744,"height":0.011173184}},{"char_start":1,"char_count":5,"bounds":{"left":0.054521278,"top":0.3990423,"width":0.008976064,"height":0.011173184}}],"role_description":"text"},{"role":"AXStaticText","text":"U","depth":28,"bounds":{"left":0.107380316,"top":0.3982442,"width":0.0029920214,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":28,"bounds":{"left":0.021276595,"top":0.41340783,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"HandleHubspotRateLimitTest.php","depth":28,"bounds":{"left":0.028590426,"top":0.415004,"width":0.06781915,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.41580206,"width":0.0033244682,"height":0.011971269}},{"char_start":1,"char_count":29,"bounds":{"left":0.031914894,"top":0.41580206,"width":0.064494684,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"tests/Unit/Jobs/Middleware","depth":28,"bounds":{"left":0.098071806,"top":0.415004,"width":0.004986702,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.098071806,"top":0.41660017,"width":0.0016622341,"height":0.011173184}},{"char_start":1,"char_count":25,"bounds":{"left":0.09940159,"top":0.41660017,"width":0.04886968,"height":0.011173184}}],"role_description":"text"},{"role":"AXStaticText","text":"M","depth":28,"bounds":{"left":0.106715426,"top":0.41580206,"width":0.003656915,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":28,"bounds":{"left":0.021276595,"top":0.4309657,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"CanAccessAiReportsTest.php","depth":28,"bounds":{"left":0.028590426,"top":0.43256184,"width":0.059175532,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.43335995,"width":0.0029920214,"height":0.011971269}},{"char_start":1,"char_count":25,"bounds":{"left":0.03158245,"top":0.43335995,"width":0.05618351,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"tests/Unit/Policies","depth":28,"bounds":{"left":0.089428194,"top":0.43256184,"width":0.013962766,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.08976064,"top":0.43415803,"width":0.0013297872,"height":0.011173184}},{"char_start":1,"char_count":18,"bounds":{"left":0.091090426,"top":0.43415803,"width":0.031914894,"height":0.011173184}}],"role_description":"text"},{"role":"AXStaticText","text":"U","depth":28,"bounds":{"left":0.107380316,"top":0.43335995,"width":0.0029920214,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":28,"bounds":{"left":0.021276595,"top":0.44852355,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ClientTest.php","depth":28,"bounds":{"left":0.028590426,"top":0.4501197,"width":0.028922873,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.4509178,"width":0.0029920214,"height":0.011971269}},{"char_start":1,"char_count":13,"bounds":{"left":0.03158245,"top":0.4509178,"width":0.026263298,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"tests/Unit/Services/Crm/Hubspot","depth":28,"bounds":{"left":0.059507977,"top":0.4501197,"width":0.043882977,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.059507977,"top":0.4517159,"width":0.0016622341,"height":0.011173184}},{"char_start":1,"char_count":30,"bounds":{"left":0.060837764,"top":0.4517159,"width":0.059507977,"height":0.011173184}}],"role_description":"text"},{"role":"AXStaticText","text":"M","depth":28,"bounds":{"left":0.106715426,"top":0.4509178,"width":0.003656915,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Graph Section","depth":21,"bounds":{"left":0.015957447,"top":0.60015965,"width":0.09940159,"height":0.017557861},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"","depth":23,"bounds":{"left":0.01662234,"top":0.60255384,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXHeading","text":"GRAPH","depth":22,"bounds":{"left":0.022606382,"top":0.60015965,"width":0.013297873,"height":0.017557861},"on_screen":true,"role_description":"heading"},{"role":"AXStaticText","text":"GRAPH","depth":23,"bounds":{"left":0.022606382,"top":0.60415006,"width":0.013297873,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"JY-20725 add more tests","depth":28,"bounds":{"left":0.023271276,"top":0.6201117,"width":0.038231384,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.023271276,"top":0.6209098,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":22,"bounds":{"left":0.025930852,"top":0.6209098,"width":0.05086436,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"Lukas Kovalik","depth":28,"bounds":{"left":0.061835106,"top":0.6209098,"width":0.0003324468,"height":0.011173184},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.078457445,"top":0.6217079,"width":0.0026595744,"height":0.011173184}},{"char_start":1,"char_count":12,"bounds":{"left":0.080784574,"top":0.6217079,"width":0.023271276,"height":0.011173184}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.06382979,"top":0.6201117,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"JY-20725-handle-HS-search-rate-limit","depth":27,"bounds":{"left":0.06948138,"top":0.6209098,"width":0.032579787,"height":0.011173184},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.06948138,"top":0.6209098,"width":0.0023271276,"height":0.011173184}},{"char_start":1,"char_count":35,"bounds":{"left":0.07180851,"top":0.6209098,"width":0.071476065,"height":0.011173184}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.10571808,"top":0.6201117,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"JY-20725 add tests","depth":28,"bounds":{"left":0.023271276,"top":0.63766956,"width":0.03956117,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.023271276,"top":0.63846767,"width":0.0023271276,"height":0.011971269}},{"char_start":1,"char_count":17,"bounds":{"left":0.025598405,"top":0.63846767,"width":0.03723404,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"Lukas Kovalik","depth":28,"bounds":{"left":0.064494684,"top":0.63846767,"width":0.024933511,"height":0.011173184},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.06482713,"top":0.6392658,"width":0.0023271276,"height":0.011173184}},{"char_start":1,"char_count":12,"bounds":{"left":0.066821806,"top":0.6392658,"width":0.022938829,"height":0.011173184}}],"role_description":"text"},{"role":"AXStaticText","text":"JY-20725 add HS rate limit handling on activities rematching","depth":28,"bounds":{"left":0.023271276,"top":0.6552275,"width":0.08577128,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.023271276,"top":0.6560255,"width":0.0023271276,"height":0.011971269}},{"char_start":1,"char_count":59,"bounds":{"left":0.025598405,"top":0.6560255,"width":0.11968085,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"Lukas Kovalik","depth":28,"bounds":{"left":0.109707445,"top":0.6560255,"width":0.0003324468,"height":0.011173184},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.14727394,"top":0.65682364,"width":0.0023271276,"height":0.011173184}},{"char_start":1,"char_count":12,"bounds":{"left":0.14926861,"top":0.65682364,"width":0.022938829,"height":0.011173184}}],"role_description":"text"},{"role":"AXStaticText","text":"Merge pull request #12056 from jiminny/JY-20818-move-AJ-reports-to-separated-datadog-metric","depth":28,"bounds":{"left":0.026928192,"top":0.67278534,"width":0.049534574,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Lukas Kovalik","depth":28,"bounds":{"left":0.0774601,"top":0.6735834,"width":0.0003324468,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.07945479,"top":0.67278534,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"origin/master","depth":27,"bounds":{"left":0.08510638,"top":0.6735834,"width":0.024933511,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Merge branch 'master' into JY-20818-move-AJ-reports-to-separated-datadog-metric","depth":28,"bounds":{"left":0.030585106,"top":0.6903432,"width":0.078457445,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Lukas Kovalik","depth":28,"bounds":{"left":0.109707445,"top":0.69114125,"width":0.0003324468,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Merge pull request #12057 from jiminny/JY-20819-increase-download-transctip-rate-limit","depth":28,"bounds":{"left":0.030585106,"top":0.70790106,"width":0.078457445,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"mihailmihaylovjiminny","depth":28,"bounds":{"left":0.109707445,"top":0.7086991,"width":0.0003324468,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"JY-20819: Increase download transcription rate limit","depth":28,"bounds":{"left":0.030585106,"top":0.7254589,"width":0.078457445,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"mihail.mihaylov","depth":28,"bounds":{"left":0.109707445,"top":0.72625697,"width":0.0003324468,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"JY-20818 move ask jiminny reports to its own datadog metric","depth":28,"bounds":{"left":0.030585106,"top":0.7430168,"width":0.07945479,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Lukas Kovalik","depth":28,"bounds":{"left":0.109707445,"top":0.7438148,"width":0.0003324468,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Merge pull request #12052 from jiminny/JY-20817-fix-deleting-old-tracks","depth":28,"bounds":{"left":0.030585106,"top":0.76057464,"width":0.0787899,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"mihailmihaylovjiminny","depth":28,"bounds":{"left":0.109707445,"top":0.7613727,"width":0.0003324468,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Merge branch 'master' into JY-20817-fix-deleting-old-tracks","depth":28,"bounds":{"left":0.030585106,"top":0.7781325,"width":0.078125,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"mihailmihaylovjiminny","depth":28,"bounds":{"left":0.109707445,"top":0.77893054,"width":0.0003324468,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Merge pull request #12047 from jiminny/JY-20662-remove-word-boost","depth":28,"bounds":{"left":0.030585106,"top":0.79569036,"width":0.0787899,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ilian-jiminny","depth":28,"bounds":{"left":0.109707445,"top":0.7964884,"width":0.0003324468,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"JY-20662 return void","depth":28,"bounds":{"left":0.030585106,"top":0.8132482,"width":0.043218084,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ilian","depth":28,"bounds":{"left":0.075465426,"top":0.81404626,"width":0.0076462766,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"JY-20662 Removed word_boost parameter from Assembly transcription request","depth":28,"bounds":{"left":0.030585106,"top":0.8308061,"width":0.0787899,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ilian","depth":28,"bounds":{"left":0.109707445,"top":0.8316041,"width":0.0003324468,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"JY-20817: Fix deleting old tracks","depth":28,"bounds":{"left":0.030585106,"top":0.84836394,"width":0.06582447,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"mihail.mihaylov","depth":28,"bounds":{"left":0.098071806,"top":0.84836394,"width":0.010305851,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Merge pull request #12043 from jiminny/JY-19938-bot-status-in-csv-es","depth":28,"bounds":{"left":0.030585106,"top":0.8659218,"width":0.0787899,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ilian-jiminny","depth":28,"bounds":{"left":0.109707445,"top":0.8667199,"width":0.0003324468,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Merge branch 'master' into JY-19938-bot-status-in-csv-es","depth":28,"bounds":{"left":0.030585106,"top":0.88347965,"width":0.0787899,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ilian-jiminny","depth":28,"bounds":{"left":0.109707445,"top":0.88427776,"width":0.0003324468,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Merge pull request #12044 from jiminny/JY-19938-ES-mapping-update","depth":28,"bounds":{"left":0.030585106,"top":0.9010375,"width":0.0787899,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ilian-jiminny","depth":28,"bounds":{"left":0.109707445,"top":0.9018356,"width":0.0003324468,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"JY-19938 Added consent information and recording error reason to Elastic Search mapping","depth":28,"bounds":{"left":0.030585106,"top":0.9185954,"width":0.0787899,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ilian","depth":28,"bounds":{"left":0.109707445,"top":0.9193935,"width":0.0003324468,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"JY-19938 reorder array map","depth":28,"bounds":{"left":0.030585106,"top":0.93615323,"width":0.056848403,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ilian","depth":28,"bounds":{"left":0.089428194,"top":0.93695134,"width":0.00731383,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"JY-19938: Address PR review comments","depth":28,"bounds":{"left":0.030585106,"top":0.9537111,"width":0.07912234,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ilian","depth":28,"bounds":{"left":0.109707445,"top":0.9545092,"width":0.0003324468,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"JY-19938 lint fixes","depth":28,"bounds":{"left":0.030585106,"top":0.97126895,"width":0.03756649,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ilian","depth":28,"bounds":{"left":0.06981383,"top":0.97206706,"width":0.0076462766,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"HandleHubspotRateLimitTest.php (Working Tree) (HandleHubspotRateLimitTest.php), Editor Group 1","depth":28,"on_screen":false,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"ClientTest.php (Working Tree) (ClientTest.php), Editor Group 1","depth":28,"bounds":{"left":0.11569149,"top":0.047885075,"width":0.08577128,"height":0.02793296},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Client.php (Working Tree) (Client.php), Editor Group 1","depth":28,"bounds":{"left":0.20113032,"top":0.047885075,"width":0.08211436,"height":0.02793296},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXRadioButton","text":"MatchActivityCrmData.php (Working Tree) (MatchActivityCrmData.php), Editor Group 1","depth":28,"bounds":{"left":0.28324467,"top":0.047885075,"width":0.11103723,"height":0.02793296},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"RateLimitException.php (Working Tree) (RateLimitException.php), Editor Group 1","depth":28,"bounds":{"left":0.39394948,"top":0.047885075,"width":0.09075798,"height":0.02793296},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":29,"bounds":{"left":0.12832446,"top":0.07821229,"width":0.0056515955,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":29,"bounds":{"left":0.15093085,"top":0.07821229,"width":0.0056515955,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":29,"bounds":{"left":0.16489361,"top":0.07821229,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":29,"bounds":{"left":0.1875,"top":0.07821229,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":29,"bounds":{"left":0.22074468,"top":0.07821229,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":29,"bounds":{"left":0.22606383,"top":0.07821229,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":29,"bounds":{"left":0.24401596,"top":0.07821229,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":29,"bounds":{"left":0.24933511,"top":0.07821229,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Review rate limit handli…, Editor Group 2","depth":28,"bounds":{"left":0.5578458,"top":0.047885075,"width":0.07014628,"height":0.02793296},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXButton","text":"remote","depth":16,"bounds":{"left":0.0006648936,"top":0.98244214,"width":0.010638298,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"app (Git) - JY-20725-handle-HS-search-rate-limit*, Checkout Branch/Tag...","depth":16,"bounds":{"left":0.012965426,"top":0.98244214,"width":0.084773935,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":17,"bounds":{"left":0.013962766,"top":0.9848364,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"JY-20725-handle-HS-search-rate-limit*","depth":17,"bounds":{"left":0.019281914,"top":0.9856345,"width":0.0774601,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"app (Git) - Synchronize Changes","depth":16,"bounds":{"left":0.09740692,"top":0.98244214,"width":0.0076462766,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Errors: 6, Warnings: 11, Infos: 28","depth":16,"bounds":{"left":0.107380316,"top":0.98244214,"width":0.03756649,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":17,"bounds":{"left":0.109042555,"top":0.9848364,"width":0.0056515955,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":17,"bounds":{"left":0.1143617,"top":0.9856345,"width":0.004986702,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":17,"bounds":{"left":0.11934841,"top":0.9848364,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"11","depth":17,"bounds":{"left":0.124667555,"top":0.9856345,"width":0.00731383,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":17,"bounds":{"left":0.13164894,"top":0.9848364,"width":0.0056515955,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"28","depth":17,"bounds":{"left":0.13696809,"top":0.9856345,"width":0.0063164895,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Notifications","depth":16,"bounds":{"left":0.9886968,"top":0.98244214,"width":0.010638298,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"key, PHP extension: Premium features not active.","depth":16,"bounds":{"left":0.9790558,"top":0.98244214,"width":0.008643617,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"8.3, Language Level: 8.3","depth":16,"bounds":{"left":0.9674202,"top":0.98244214,"width":0.009640957,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sign In","depth":16,"bounds":{"left":0.9428192,"top":0.98244214,"width":0.022938829,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":17,"bounds":{"left":0.9444814,"top":0.9848364,"width":0.0056515955,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Sign In","depth":17,"bounds":{"left":0.94980055,"top":0.9856345,"width":0.014295213,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"PHP","depth":16,"bounds":{"left":0.92952126,"top":0.98244214,"width":0.011635638,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"LF","depth":16,"bounds":{"left":0.91988033,"top":0.98244214,"width":0.007978723,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"UTF-8","depth":16,"bounds":{"left":0.90226066,"top":0.98244214,"width":0.015625,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Spaces: 4","depth":16,"bounds":{"left":0.87832445,"top":0.98244214,"width":0.022273935,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Ln 120, Col 1","depth":16,"bounds":{"left":0.84773934,"top":0.98244214,"width":0.028922873,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"git-commit Not Committed Yet","depth":16,"bounds":{"left":0.80019945,"top":0.98244214,"width":0.045545213,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":17,"bounds":{"left":0.8018617,"top":0.9848364,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Not Committed Yet","depth":17,"bounds":{"left":0.8071808,"top":0.9856345,"width":0.036901597,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Diff editor","depth":12,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Clear","depth":12,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Review rate limit handling for HubSpot job queue","depth":19,"bounds":{"left":0.56017286,"top":0.08060654,"width":0.099734046,"height":0.022346368},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Session history","depth":19,"bounds":{"left":0.9780585,"top":0.08060654,"width":0.00930851,"height":0.022346368},"on_screen":true,"help_text":"Session history","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New session","depth":19,"bounds":{"left":0.9886968,"top":0.08060654,"width":0.00930851,"height":0.022346368},"on_screen":true,"help_text":"New session","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Message actions","depth":24,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"RateLimitException.php#9-9","depth":23,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"RateLimitException.php#9-9","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Reveiw the diff. Mostly","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"@app/Jobs/Crm/MatchActivityCrmData.php","depth":26,"on_screen":false,"help_text":"Open app/Jobs/Crm/MatchActivityCrmData.php","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":",","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"@app/Exceptions/RateLimitException.php","depth":26,"on_screen":false,"help_text":"Open app/Exceptions/RateLimitException.php","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":",","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"@app/Jobs/Middleware/HandleHubspotRateLimit.php","depth":26,"on_screen":false,"help_text":"Open app/Jobs/Middleware/HandleHubspotRateLimit.php","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":",","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"@app/Services/Crm/Hubspot/Client.php","depth":26,"on_screen":false,"help_text":"Open app/Services/Crm/Hubspot/Client.php","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"@app/Services/Crm/Hubspot/Pagination/HubspotPaginationService.php","depth":26,"on_screen":false,"help_text":"Open app/Services/Crm/Hubspot/Pagination/HubspotPaginationService.php","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":". The issue is I am trying to make sure that instant spike of MatchActivityCrmData jobs willl handle 429 exceptions from hubspot. Revewi ti carefully and try to see potential issues and bottlenecks. Also RateLimitException was changerd from LogicException to RuntimeException. Is it ok. This exception is used by other paerts of code. Will ti break anything?","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Thought for 5s","depth":23,"on_screen":false,"role_description":"disclosure triangle","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Thought for 5s","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"I'll review the implementation carefully. Let me first check how","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimitException","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"is used elsewhere and look at the test files and","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PaginationState","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Bash","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Find all usages of RateLimitException outside the changed files","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IN","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"grep -rn \"RateLimitException\" /Users/lukas/jiminny/app/app --include=\"*.php\" | grep -v \"HandleHubspotRateLimit\\|MatchActivityCrmData\\|Client.php\\|RateLimitException.php\"","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy code to clipboard","depth":24,"on_screen":false,"help_text":"Copy code","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"OUT","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Component/ActionItems/Jobs/GenerateActionItemsJob.php:13:use Jiminny\\Exceptions\\RateLimitException;\n/Users/lukas/jiminny/app/app/Component/ActionItems/Jobs/GenerateActionItemsJob.php:51: GuzzleException|HttpBadRequestException|JsonException|RateLimitException|UnexpectedValueException $exception\n/Users/lukas/jiminny/app/app/Component/AiCallScoring/Jobs/GenerateAiCallScoringJob.php:17:use Jiminny\\Exceptions\\RateLimitException;\n/Users/lukas/jiminny/app/app/Component/AiCallScoring/Jobs/GenerateAiCallScoringJob.php:68: RateLimitException |\n/Users/lukas/jiminny/app/app/Component/TranscriptionSummary/Jobs/SummarizeTranscriptionJob.php:15:use Jiminny\\Exceptions\\RateLimitException;\n/Users/lukas/jiminny/app/app/Component/TranscriptionSummary/Jobs/SummarizeTranscriptionJob.php:58: GuzzleException|HttpBadRequestException|JsonException|RateLimitException|UnexpectedValueException $exception\n/Users/lukas/jiminny/app/app/Component/AiActivityType/Jobs/GenerateAiActivityTypeJob.php:19:use Jiminny\\Exceptions\\RateLimitException;\n/Users/lukas/jiminny/app/app/Component/AiActivityType/Jobs/GenerateAiActivityTypeJob.php:76: RateLimitException |\n/Users/lukas/jiminny/app/app/Services/Crm/Hubspot/HubspotClientInterface.php:8:use Jiminny\\Exceptions\\RateLimitException;\n/Users/lukas/jiminny/app/app/Services/Crm/Hubspot/HubspotClientInterface.php:50: * @throws RateLimitException When rate limit is hit\n/Users/lukas/jiminny/app/app/Services/Crm/Hubspot/Pagination/HubspotPaginationService.php:195: // RateLimitException and other exceptions are re-thrown as-is","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Read","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"PaginationState.php","depth":24,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"PaginationState.php","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Read","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"HandleHubspotRateLimitTest.php","depth":24,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"HandleHubspotRateLimitTest.php","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Bash","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Check how RateLimitException is used in GenerateActionItemsJob","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IN","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"grep -rn \"RateLimitException\\|LogicException\\|RuntimeException\" /Users/lukas/jiminny/app/app/Component/ActionItems/Jobs/GenerateActionItemsJob.php | head -20","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy code to clipboard","depth":24,"on_screen":false,"help_text":"Copy code","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"OUT","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Component/ActionItems/Jobs/GenerateActionItemsJob.php:13:use Jiminny\\Exceptions\\RateLimitException;\n/Users/lukas/jiminny/app/app/Component/ActionItems/Jobs/GenerateActionItemsJob.php:51: GuzzleException|HttpBadRequestException|JsonException|RateLimitException|UnexpectedValueException $exception","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Bash","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Find all catch blocks or instanceof checks for RateLimitException","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IN","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"grep -rn \"catch.*RateLimitException\\|instanceof RateLimitException\" /Users/lukas/jiminny/app/app --include=\"*.php\"","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy code to clipboard","depth":24,"on_screen":false,"help_text":"Copy code","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"OUT","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Jobs/Middleware/HandleHubspotRateLimit.php:11: * Job middleware that catches RateLimitException from HubSpot API calls\n/Users/lukas/jiminny/app/app/Jobs/Middleware/HandleHubspotRateLimit.php:24: } catch (RateLimitException $e) {\n/Users/lukas/jiminny/app/app/Jobs/Crm/MatchActivityCrmData.php:149: if (! $e instanceof RateLimitException) {","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Bash","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Find all test references to RateLimitException","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IN","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"grep -rn \"RateLimitException\" /Users/lukas/jiminny/app/tests --include=\"*.php\"","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy code to clipboard","depth":24,"on_screen":false,"help_text":"Copy code","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"OUT","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/tests/Unit/Exceptions/RateLimitExceptionTest.php:8:use Jiminny\\Exceptions\\RateLimitException;\n/Users/lukas/jiminny/app/tests/Unit/Exceptions/RateLimitExceptionTest.php:13:#[CoversClass(RateLimitException::class)]\n/Users/lukas/jiminny/app/tests/Unit/Exceptions/RateLimitExceptionTest.php:14:class RateLimitExceptionTest extends TestCase\n/Users/lukas/jiminny/app/tests/Unit/Exceptions/RateLimitExceptionTest.php:18: $exception = new RateLimitException();\n/Users/lukas/jiminny/app/tests/Unit/Exceptions/RateLimitExceptionTest.php:28: $exception = new RateLimitException('Hubspot rate limit', 30, $previous);\n/Users/lukas/jiminny/app/tests/Unit/Exceptions/RateLimitExceptionTest.php:52: $exception = new RateLimitException('test', $input);\n/Users/lukas/jiminny/app/tests/Unit/Jobs/Middleware/HandleHubspotRateLimitTest.php:10:use Jiminny\\Exceptions\\RateLimitException;\n/Users/lukas/jiminny/app/tests/Unit/Jobs/Middleware/HandleHubspotRateLimitTest.php:49: public function testPropagatesNonRateLimitExceptions(): void\n/Users/lukas/jiminny/app/tests/Unit/Jobs/Middleware/HandleHubspotRateLimitTest.php:103: throw new RateLimitException('rate limited', $retryAfter);\n/Users/lukas/jiminny/app/tests/Unit/Jobs/Middleware/HandleHubspotRateLimitTest.php:150: throw new RateLimitException('rate limited', 1);","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Bash","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Check retry/failure handling in GenerateActionItemsJob","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IN","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"grep -n \"RateLimitException\\|retryUntil\\|backoff\\|maxExceptions\\|maxTries\\|release\\|failed\" /Users/lukas/jiminny/app/app/Component/ActionItems/Jobs/GenerateActionItemsJob.php | head -30","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy code to clipboard","depth":24,"on_screen":false,"help_text":"Copy code","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"OUT","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"13:use Jiminny\\Exceptions\\RateLimitException;\n51: GuzzleException|HttpBadRequestException|JsonException|RateLimitException|UnexpectedValueException $exception\n56: $this->release(30);","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Bash","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Check retry/failure handling in GenerateAiCallScoringJob","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IN","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"grep -n \"RateLimitException\\|retryUntil\\|backoff\\|maxExceptions\\|maxTries\\|release\\|failed\" /Users/lukas/jiminny/app/app/Component/AiCallScoring/Jobs/GenerateAiCallScoringJob.php | head -30","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy code to clipboard","depth":24,"on_screen":false,"help_text":"Copy code","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"OUT","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"17:use Jiminny\\Exceptions\\RateLimitException;\n68: RateLimitException |\n75: $this->release(30);","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Bash","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Check retry config in MatchActivityCrmData","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IN","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"grep -n \"retryAfter\\|RETRY_WINDOW\\|retryUntil\\|maxTries\" /Users/lukas/jiminny/app/app/Jobs/Crm/MatchActivityCrmData.php","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy code to clipboard","depth":24,"on_screen":false,"help_text":"Copy code","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"OUT","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"34: private const int RETRY_WINDOW_MINUTES = 30;\n45: public function retryUntil(): \\DateTimeInterface\n47: return now()->addMinutes(self::RETRY_WINDOW_MINUTES);\n77: return self::RETRY_WINDOW_MINUTES * 60 + 60;","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Bash","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Check the cache key format","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IN","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"grep -n \"getRateLimitCacheKey\\|hubspot:ratelimit\" /Users/lukas/jiminny/app/app/Services/Crm/Hubspot/Client.php","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy code to clipboard","depth":24,"on_screen":false,"help_text":"Copy code","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"OUT","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"78: $cacheKey = $this->getRateLimitCacheKey();\n110: private function getRateLimitCacheKey(): string\n112: return sprintf('hubspot:ratelimit:portal:%d', $this->config->getId());","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Bash","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Find other jobs that might use the rate limit middleware","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IN","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"grep -rn \"class.*Middleware\\|HandleHubspotRateLimit\" /Users/lukas/jiminny/app/app/Jobs --include=\"*.php\" | grep -v \"HandleHubspotRateLimit.php\"","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy code to clipboard","depth":24,"on_screen":false,"help_text":"Copy code","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"OUT","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Jobs/Crm/MatchActivityCrmData.php:18:use Jiminny\\Jobs\\Middleware\\HandleHubspotRateLimit;\n/Users/lukas/jiminny/app/app/Jobs/Crm/MatchActivityCrmData.php:42: return [new HandleHubspotRateLimit()];","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Read","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"HubspotClientInterface.php","depth":24,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"HubspotClientInterface.php","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Thought for 73s","depth":23,"on_screen":false,"role_description":"disclosure triangle","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Thought for 73s","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Now I have a complete picture. Here's my full analysis:","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXHeading","text":"Review Summary","depth":22,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"Review Summary","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXHeading","text":"RuntimeException vs LogicException — Safe ✓","depth":22,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"RuntimeException","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"vs","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LogicException","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"— Safe ✓","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"The change is semantically correct and won't break anything.","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LogicException","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"is for programmer errors (invalid arguments, contract violations). A rate limit from an external API is a runtime condition —","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RuntimeException","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"is the right parent. All callers catch","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimitException","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"by name explicitly (union catches in","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GenerateActionItemsJob","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":",","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GenerateAiCallScoringJob","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":",","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SummarizeTranscriptionJob","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":",","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GenerateAiActivityTypeJob","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":") or check","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"instanceof RateLimitException","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"— none relies on the parent class type, so nothing breaks.","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXHeading","text":"Issue 1 — Circuit breaker stores stale delay (most important bug)","depth":22,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"Issue 1 — Circuit breaker stores stale delay (most important bug)","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"Client.php:94","depth":23,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Client.php:94","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy code to clipboard","depth":23,"on_screen":false,"help_text":"Copy code","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Redis::setex($cacheKey, $retryAfter, (string) $retryAfter);","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"The TTL is","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"$retryAfter","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"seconds, but the","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"stored value","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"is also the original","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"$retryAfter","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":". If a second job reads this cache 20 seconds later, it throws","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimitException(retryAfter=30)","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"and gets delayed 30 more seconds — even though only 10 remain. In a spike of 100 jobs, each subsequent job inflates its wait.","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Fix:","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"store the absolute expiry timestamp, reconstruct the remaining delay when reading:","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy code to clipboard","depth":23,"on_screen":false,"help_text":"Copy code","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"// on 429:\nRedis::setex($cacheKey, $retryAfter, (string) (time() + $retryAfter));\n\n// in the cached-read branch:\n$remaining = max(1, (int) $cachedRetryAfter - time());\nthrow new RateLimitException('Hubspot rate limit (cached circuit-breaker)', $remaining);","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXHeading","text":"Issue 2 — retryUntil() window may expire under sustained rate limiting","depth":22,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"Issue 2 —","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"retryUntil()","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"window may expire under sustained rate limiting","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"MatchActivityCrmData.php:45-47","depth":23,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"MatchActivityCrmData.php:45-47","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"retryUntil()","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"is calculated as","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"now() + 30 min","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"at","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"dispatch time","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"and stored in the job payload. With","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MAX_RETRY_DELAY = 600s","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":", you only get ~3 rate-limit-triggered releases before the 30-minute window closes and the job is permanently failed. For a heavy 429 spike (daily limit, parsed as 600s), this can easily happen.","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Consider increasing","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RETRY_WINDOW_MINUTES","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"to 60 or more, or making it configurable. The","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"uniqueFor()","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"value should be updated to match.","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXHeading","text":"Issue 3 — Only search() uses the circuit breaker, but other methods can still hit 429","depth":22,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"Issue 3 — Only","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"search()","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"uses the circuit breaker, but other methods can still hit 429","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"Client.php:76-108","depth":23,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Client.php:76-108","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"executeRequest()","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"is only called from","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"search()","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":". Methods like","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"getOpportunityById()","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":",","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"batchReadObjects()","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":",","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"getContactByEmail()","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"bypass it entirely. If HubSpot's 429 is account-wide (not search-endpoint-specific), those methods will keep firing even while the circuit breaker is supposedly open. This is likely fine because the search endpoint has its own tighter quota (5 RPS), but it's worth being explicit in a comment.","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXHeading","text":"Issue 4 — object $job type in middleware has no compile-time safety","depth":22,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"Issue 4 —","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"object $job","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"type in middleware has no compile-time safety","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"HandleHubspotRateLimit.php:20","depth":23,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"HandleHubspotRateLimit.php:20","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy code to clipboard","depth":23,"on_screen":false,"help_text":"Copy code","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"public function handle(object $job, callable $next): void","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Calling","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"$job->attempts()","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"and","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"$job->release()","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"on","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"object","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"will work at runtime since Laravel always passes jobs that use","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"InteractsWithQueue","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":", but PHPStan/psalm won't verify this and it could silently break in tests with bare mocks (as your test does use a","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Job","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"mock that has","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"attempts()","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"— so the test is fine, but the real type contract is invisible). Minor, but worth tightening to","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"\\Illuminate\\Contracts\\Queue\\Job","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXHeading","text":"What works well ✓","depth":22,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"What works well ✓","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimitException","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"is caught by the middleware","depth":24,"on_screen":false,"role_description":"text"}]...
|
-9085806743506945806
|
6120906794114589251
|
idle
|
accessibility
|
NULL
|
Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧ Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧G) - 17 pending changes
17
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update
2
Testing
Claude Code
SOURCE CONTROL
SOURCE CONTROL
Changes Section
CHANGES
CHANGES
Commit Changes on "JY-20725-handle-HS-search-rate-limit"
Commit
More Actions...
Changes
17
.env.local
M
.env.nikilocal
U
.env.other
U
ids.txt
U
raw_sql_query.sql
U
WEBHOOK_FILTERING_IMPLEMENTATION.md
U
JiminnyDebugCommand.php
app/Console/Commands
M
SimulateWebhooksCommand.php
app/Console/Commands/Crm/Hubspot
U
CreateMockAskJiminnyReportResultCommand.php
app/Console/Commands/Reports
U
RateLimitException.php
app/Exceptions
M
MatchActivityCrmData.php
app/Jobs/Crm
M
Client.php
app/Services/Crm/Hubspot
9, M
logging.php
config
M
favicon.ico
public
U
HandleHubspotRateLimitTest.php
tests/Unit/Jobs/Middleware
M
CanAccessAiReportsTest.php
tests/Unit/Policies
U
ClientTest.php
tests/Unit/Services/Crm/Hubspot
M
Graph Section
GRAPH
GRAPH
JY-20725 add more tests
Lukas Kovalik
JY-20725-handle-HS-search-rate-limit
JY-20725 add tests
Lukas Kovalik
JY-20725 add HS rate limit handling on activities rematching
Lukas Kovalik
Merge pull request #12056 from jiminny/JY-20818-move-AJ-reports-to-separated-datadog-metric
Lukas Kovalik
origin/master
Merge branch 'master' into JY-20818-move-AJ-reports-to-separated-datadog-metric
Lukas Kovalik
Merge pull request #12057 from jiminny/JY-20819-increase-download-transctip-rate-limit
mihailmihaylovjiminny
JY-20819: Increase download transcription rate limit
mihail.mihaylov
JY-20818 move ask jiminny reports to its own datadog metric
Lukas Kovalik
Merge pull request #12052 from jiminny/JY-20817-fix-deleting-old-tracks
mihailmihaylovjiminny
Merge branch 'master' into JY-20817-fix-deleting-old-tracks
mihailmihaylovjiminny
Merge pull request #12047 from jiminny/JY-20662-remove-word-boost
ilian-jiminny
JY-20662 return void
ilian
JY-20662 Removed word_boost parameter from Assembly transcription request
ilian
JY-20817: Fix deleting old tracks
mihail.mihaylov
Merge pull request #12043 from jiminny/JY-19938-bot-status-in-csv-es
ilian-jiminny
Merge branch 'master' into JY-19938-bot-status-in-csv-es
ilian-jiminny
Merge pull request #12044 from jiminny/JY-19938-ES-mapping-update
ilian-jiminny
JY-19938 Added consent information and recording error reason to Elastic Search mapping
ilian
JY-19938 reorder array map
ilian
JY-19938: Address PR review comments
ilian
JY-19938 lint fixes
ilian
HandleHubspotRateLimitTest.php (Working Tree) (HandleHubspotRateLimitTest.php), Editor Group 1
ClientTest.php (Working Tree) (ClientTest.php), Editor Group 1
Client.php (Working Tree) (Client.php), Editor Group 1
MatchActivityCrmData.php (Working Tree) (MatchActivityCrmData.php), Editor Group 1
RateLimitException.php (Working Tree) (RateLimitException.php), Editor Group 1
Review rate limit handli…, Editor Group 2
remote
app (Git) - JY-20725-handle-HS-search-rate-limit*, Checkout Branch/Tag...
JY-20725-handle-HS-search-rate-limit*
app (Git) - Synchronize Changes
Errors: 6, Warnings: 11, Infos: 28
6
11
28
Notifications
key, PHP extension: Premium features not active.
8.3, Language Level: 8.3
Sign In
Sign In
PHP
LF
UTF-8
Spaces: 4
Ln 120, Col 1
git-commit Not Committed Yet
Not Committed Yet
Diff editor
Clear
Review rate limit handling for HubSpot job queue
Session history
New session
Message actions
RateLimitException.php#9-9
RateLimitException.php#9-9
Reveiw the diff. Mostly
@app/Jobs/Crm/MatchActivityCrmData.php
,
@app/Exceptions/RateLimitException.php
,
@app/Jobs/Middleware/HandleHubspotRateLimit.php
,
@app/Services/Crm/Hubspot/Client.php
and
@app/Services/Crm/Hubspot/Pagination/HubspotPaginationService.php
. The issue is I am trying to make sure that instant spike of MatchActivityCrmData jobs willl handle 429 exceptions from hubspot. Revewi ti carefully and try to see potential issues and bottlenecks. Also RateLimitException was changerd from LogicException to RuntimeException. Is it ok. This exception is used by other paerts of code. Will ti break anything?
Thought for 5s
Thought for 5s
I'll review the implementation carefully. Let me first check how
RateLimitException
is used elsewhere and look at the test files and
PaginationState
.
Bash
Find all usages of RateLimitException outside the changed files
IN
grep -rn "RateLimitException" /Users/lukas/jiminny/app/app --include="*.php" | grep -v "HandleHubspotRateLimit\|MatchActivityCrmData\|Client.php\|RateLimitException.php"
Copy code to clipboard
OUT
/Users/lukas/jiminny/app/app/Component/ActionItems/Jobs/GenerateActionItemsJob.php:13:use Jiminny\Exceptions\RateLimitException;
/Users/lukas/jiminny/app/app/Component/ActionItems/Jobs/GenerateActionItemsJob.php:51: GuzzleException|HttpBadRequestException|JsonException|RateLimitException|UnexpectedValueException $exception
/Users/lukas/jiminny/app/app/Component/AiCallScoring/Jobs/GenerateAiCallScoringJob.php:17:use Jiminny\Exceptions\RateLimitException;
/Users/lukas/jiminny/app/app/Component/AiCallScoring/Jobs/GenerateAiCallScoringJob.php:68: RateLimitException |
/Users/lukas/jiminny/app/app/Component/TranscriptionSummary/Jobs/SummarizeTranscriptionJob.php:15:use Jiminny\Exceptions\RateLimitException;
/Users/lukas/jiminny/app/app/Component/TranscriptionSummary/Jobs/SummarizeTranscriptionJob.php:58: GuzzleException|HttpBadRequestException|JsonException|RateLimitException|UnexpectedValueException $exception
/Users/lukas/jiminny/app/app/Component/AiActivityType/Jobs/GenerateAiActivityTypeJob.php:19:use Jiminny\Exceptions\RateLimitException;
/Users/lukas/jiminny/app/app/Component/AiActivityType/Jobs/GenerateAiActivityTypeJob.php:76: RateLimitException |
/Users/lukas/jiminny/app/app/Services/Crm/Hubspot/HubspotClientInterface.php:8:use Jiminny\Exceptions\RateLimitException;
/Users/lukas/jiminny/app/app/Services/Crm/Hubspot/HubspotClientInterface.php:50: * @throws RateLimitException When rate limit is hit
/Users/lukas/jiminny/app/app/Services/Crm/Hubspot/Pagination/HubspotPaginationService.php:195: // RateLimitException and other exceptions are re-thrown as-is
Read
PaginationState.php
PaginationState.php
Read
HandleHubspotRateLimitTest.php
HandleHubspotRateLimitTest.php
Bash
Check how RateLimitException is used in GenerateActionItemsJob
IN
grep -rn "RateLimitException\|LogicException\|RuntimeException" /Users/lukas/jiminny/app/app/Component/ActionItems/Jobs/GenerateActionItemsJob.php | head -20
Copy code to clipboard
OUT
/Users/lukas/jiminny/app/app/Component/ActionItems/Jobs/GenerateActionItemsJob.php:13:use Jiminny\Exceptions\RateLimitException;
/Users/lukas/jiminny/app/app/Component/ActionItems/Jobs/GenerateActionItemsJob.php:51: GuzzleException|HttpBadRequestException|JsonException|RateLimitException|UnexpectedValueException $exception
Bash
Find all catch blocks or instanceof checks for RateLimitException
IN
grep -rn "catch.*RateLimitException\|instanceof RateLimitException" /Users/lukas/jiminny/app/app --include="*.php"
Copy code to clipboard
OUT
/Users/lukas/jiminny/app/app/Jobs/Middleware/HandleHubspotRateLimit.php:11: * Job middleware that catches RateLimitException from HubSpot API calls
/Users/lukas/jiminny/app/app/Jobs/Middleware/HandleHubspotRateLimit.php:24: } catch (RateLimitException $e) {
/Users/lukas/jiminny/app/app/Jobs/Crm/MatchActivityCrmData.php:149: if (! $e instanceof RateLimitException) {
Bash
Find all test references to RateLimitException
IN
grep -rn "RateLimitException" /Users/lukas/jiminny/app/tests --include="*.php"
Copy code to clipboard
OUT
/Users/lukas/jiminny/app/tests/Unit/Exceptions/RateLimitExceptionTest.php:8:use Jiminny\Exceptions\RateLimitException;
/Users/lukas/jiminny/app/tests/Unit/Exceptions/RateLimitExceptionTest.php:13:#[CoversClass(RateLimitException::class)]
/Users/lukas/jiminny/app/tests/Unit/Exceptions/RateLimitExceptionTest.php:14:class RateLimitExceptionTest extends TestCase
/Users/lukas/jiminny/app/tests/Unit/Exceptions/RateLimitExceptionTest.php:18: $exception = new RateLimitException();
/Users/lukas/jiminny/app/tests/Unit/Exceptions/RateLimitExceptionTest.php:28: $exception = new RateLimitException('Hubspot rate limit', 30, $previous);
/Users/lukas/jiminny/app/tests/Unit/Exceptions/RateLimitExceptionTest.php:52: $exception = new RateLimitException('test', $input);
/Users/lukas/jiminny/app/tests/Unit/Jobs/Middleware/HandleHubspotRateLimitTest.php:10:use Jiminny\Exceptions\RateLimitException;
/Users/lukas/jiminny/app/tests/Unit/Jobs/Middleware/HandleHubspotRateLimitTest.php:49: public function testPropagatesNonRateLimitExceptions(): void
/Users/lukas/jiminny/app/tests/Unit/Jobs/Middleware/HandleHubspotRateLimitTest.php:103: throw new RateLimitException('rate limited', $retryAfter);
/Users/lukas/jiminny/app/tests/Unit/Jobs/Middleware/HandleHubspotRateLimitTest.php:150: throw new RateLimitException('rate limited', 1);
Bash
Check retry/failure handling in GenerateActionItemsJob
IN
grep -n "RateLimitException\|retryUntil\|backoff\|maxExceptions\|maxTries\|release\|failed" /Users/lukas/jiminny/app/app/Component/ActionItems/Jobs/GenerateActionItemsJob.php | head -30
Copy code to clipboard
OUT
13:use Jiminny\Exceptions\RateLimitException;
51: GuzzleException|HttpBadRequestException|JsonException|RateLimitException|UnexpectedValueException $exception
56: $this->release(30);
Bash
Check retry/failure handling in GenerateAiCallScoringJob
IN
grep -n "RateLimitException\|retryUntil\|backoff\|maxExceptions\|maxTries\|release\|failed" /Users/lukas/jiminny/app/app/Component/AiCallScoring/Jobs/GenerateAiCallScoringJob.php | head -30
Copy code to clipboard
OUT
17:use Jiminny\Exceptions\RateLimitException;
68: RateLimitException |
75: $this->release(30);
Bash
Check retry config in MatchActivityCrmData
IN
grep -n "retryAfter\|RETRY_WINDOW\|retryUntil\|maxTries" /Users/lukas/jiminny/app/app/Jobs/Crm/MatchActivityCrmData.php
Copy code to clipboard
OUT
34: private const int RETRY_WINDOW_MINUTES = 30;
45: public function retryUntil(): \DateTimeInterface
47: return now()->addMinutes(self::RETRY_WINDOW_MINUTES);
77: return self::RETRY_WINDOW_MINUTES * 60 + 60;
Bash
Check the cache key format
IN
grep -n "getRateLimitCacheKey\|hubspot:ratelimit" /Users/lukas/jiminny/app/app/Services/Crm/Hubspot/Client.php
Copy code to clipboard
OUT
78: $cacheKey = $this->getRateLimitCacheKey();
110: private function getRateLimitCacheKey(): string
112: return sprintf('hubspot:ratelimit:portal:%d', $this->config->getId());
Bash
Find other jobs that might use the rate limit middleware
IN
grep -rn "class.*Middleware\|HandleHubspotRateLimit" /Users/lukas/jiminny/app/app/Jobs --include="*.php" | grep -v "HandleHubspotRateLimit.php"
Copy code to clipboard
OUT
/Users/lukas/jiminny/app/app/Jobs/Crm/MatchActivityCrmData.php:18:use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
/Users/lukas/jiminny/app/app/Jobs/Crm/MatchActivityCrmData.php:42: return [new HandleHubspotRateLimit()];
Read
HubspotClientInterface.php
HubspotClientInterface.php
Thought for 73s
Thought for 73s
Now I have a complete picture. Here's my full analysis:
Review Summary
Review Summary
RuntimeException vs LogicException — Safe ✓
RuntimeException
vs
LogicException
— Safe ✓
The change is semantically correct and won't break anything.
LogicException
is for programmer errors (invalid arguments, contract violations). A rate limit from an external API is a runtime condition —
RuntimeException
is the right parent. All callers catch
RateLimitException
by name explicitly (union catches in
GenerateActionItemsJob
,
GenerateAiCallScoringJob
,
SummarizeTranscriptionJob
,
GenerateAiActivityTypeJob
) or check
instanceof RateLimitException
— none relies on the parent class type, so nothing breaks.
Issue 1 — Circuit breaker stores stale delay (most important bug)
Issue 1 — Circuit breaker stores stale delay (most important bug)
Client.php:94
Client.php:94
Copy code to clipboard
Redis::setex($cacheKey, $retryAfter, (string) $retryAfter);
The TTL is
$retryAfter
seconds, but the
stored value
is also the original
$retryAfter
. If a second job reads this cache 20 seconds later, it throws
RateLimitException(retryAfter=30)
and gets delayed 30 more seconds — even though only 10 remain. In a spike of 100 jobs, each subsequent job inflates its wait.
Fix:
store the absolute expiry timestamp, reconstruct the remaining delay when reading:
Copy code to clipboard
// on 429:
Redis::setex($cacheKey, $retryAfter, (string) (time() + $retryAfter));
// in the cached-read branch:
$remaining = max(1, (int) $cachedRetryAfter - time());
throw new RateLimitException('Hubspot rate limit (cached circuit-breaker)', $remaining);
Issue 2 — retryUntil() window may expire under sustained rate limiting
Issue 2 —
retryUntil()
window may expire under sustained rate limiting
MatchActivityCrmData.php:45-47
MatchActivityCrmData.php:45-47
retryUntil()
is calculated as
now() + 30 min
at
dispatch time
and stored in the job payload. With
MAX_RETRY_DELAY = 600s
, you only get ~3 rate-limit-triggered releases before the 30-minute window closes and the job is permanently failed. For a heavy 429 spike (daily limit, parsed as 600s), this can easily happen.
Consider increasing
RETRY_WINDOW_MINUTES
to 60 or more, or making it configurable. The
uniqueFor()
value should be updated to match.
Issue 3 — Only search() uses the circuit breaker, but other methods can still hit 429
Issue 3 — Only
search()
uses the circuit breaker, but other methods can still hit 429
Client.php:76-108
Client.php:76-108
executeRequest()
is only called from
search()
. Methods like
getOpportunityById()
,
batchReadObjects()
,
getContactByEmail()
bypass it entirely. If HubSpot's 429 is account-wide (not search-endpoint-specific), those methods will keep firing even while the circuit breaker is supposedly open. This is likely fine because the search endpoint has its own tighter quota (5 RPS), but it's worth being explicit in a comment.
Issue 4 — object $job type in middleware has no compile-time safety
Issue 4 —
object $job
type in middleware has no compile-time safety
HandleHubspotRateLimit.php:20
HandleHubspotRateLimit.php:20
Copy code to clipboard
public function handle(object $job, callable $next): void
Calling
$job->attempts()
and
$job->release()
on
object
will work at runtime since Laravel always passes jobs that use
InteractsWithQueue
, but PHPStan/psalm won't verify this and it could silently break in tests with bare mocks (as your test does use a
Job
mock that has
attempts()
— so the test is fine, but the real type contract is invisible). Minor, but worth tightening to
\Illuminate\Contracts\Queue\Job
.
What works well ✓
What works well ✓
RateLimitException
is caught by the middleware...
|
20285
|
/Users/lukas/jiminny/app/app/Services/Crm/Hubspot/ /Users/lukas/jiminny/app/app/Services/Crm/Hubspot/Client.php...
|
NULL
|
NULL
|
|
74161
|
2638
|
26
|
2026-05-27T07:07:39.646261+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-27/1779 /Users/lukas/.screenpipe/data/data/2026-05-27/1779865659646_m1.jpg...
|
Firefox
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira — Work...
|
1
|
jiminny.atlassian.net/jira/software/c/projects/JY/ jiminny.atlassian.net/jira/software/c/projects/JY/boards/37...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
TypeError: Jiminny\Services\Mail\TextRelayService::setHistoryPoint(): Argument #2 ($historyPoint) must be of type int, string given, called in /home/jiminny/app/Services/Mail/TextRelayService.php on line 118 — jiminny — app
TypeError: Jiminny\Services\Mail\TextRelayService::setHistoryPoint(): Argument #2 ($historyPoint) must be of type int, string given, called in /home/jiminny/app/Services/Mail/TextRelayService.php on line 118 — jiminny — app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to:
Top Bar
Top Bar
Sidebar
Sidebar
Main Content
Main Content
Space navigation
Space navigation
Expand sidebar [
Expand sidebar [
Switch sites or apps
Switch sites or apps
Go to your Jira homepage
Search, press enter to navigate to advanced search with your text query
Create
Create
Rovo Ask Rovo
Ask Rovo
Notifications
Notifications
Help
Help
Settings
Settings
[EMAIL]
[EMAIL]
Spaces
Spaces
/
Jiminny (New)
Jiminny (New)
Platform Team
Platform Team
Add people
Add people
Board actions
Board actions
Share
Automation
Give feedback
Give feedback
Enter full screen
Enter full screen
Summary
Summary
Timeline
Timeline
Backlog
Backlog
Active sprints
Active sprints
Calendar
Calendar
Reports
Reports
Testing Board
Testing Board
List
List
Forms
Forms
Components
Components
Development
Development
Code
Code
Security
Security
Releases
Releases
Deployments
Deployments
Archived work items
Archived work items
Docs
Docs
Shortcuts
Shortcuts
Slack integration
Slack integration
Reporting Center
Reporting Center
Add to navigation
As you type to search or apply filters, the board updates with work items to match.
Search on current page
Filter by assignee
Filter assignees by Lukas Kovalik
Filter assignees by Aneliya Angelova
Filter assignees by Nikolay Ivanov
Filter assignees by Nikolay Nikolov
Filter assignees by Nikolay Yankov
Filter assignees by Steliyan Georgiev
Filter assignees by Unassigned
Epic
Epic
Type
Type
Quick filters
Quick filters
Complete sprint
Complete sprint
Sprint details
Sprint details
Group by Queries
Group
: Queries
Sprint insights
Sprint insights
View settings
View settings
More actions
More actions
Ready For DEV
READY FOR DEV
1
JY-19958 Upgrade BE libraries - May. Use the enter key to load the work item.
Upgrade BE libraries - May
MAINTENANCE
Backlog
JY-19958
JY-19958
1
In DEV
IN DEV...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"TypeError: Jiminny\\Services\\Mail\\TextRelayService::setHistoryPoint(): Argument #2 ($historyPoint) must be of type int, string given, called in /home/jiminny/app/Services/Mail/TextRelayService.php on line 118 — jiminny — app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"TypeError: Jiminny\\Services\\Mail\\TextRelayService::setHistoryPoint(): Argument #2 ($historyPoint) must be of type int, string given, called in /home/jiminny/app/Services/Mail/TextRelayService.php on line 118 — jiminny — app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to:","depth":3,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Top Bar","depth":4,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Top Bar","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sidebar","depth":4,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sidebar","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Main Content","depth":4,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Main Content","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Space navigation","depth":4,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Space navigation","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand sidebar [","depth":3,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Expand sidebar [","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Switch sites or apps","depth":4,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Switch sites or apps","depth":6,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Go to your Jira homepage","depth":3,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXComboBox","text":"Search, press enter to navigate to advanced search with your text query","depth":5,"on_screen":true,"help_text":"","placeholder":"Search","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Create","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create","depth":6,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Rovo Ask Rovo","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Rovo","depth":8,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Notifications","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notifications","depth":8,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Help","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Help","depth":8,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Settings","depth":6,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Settings","depth":8,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"lukas.kovalik@jiminny.com","depth":6,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"lukas.kovalik@jiminny.com","depth":8,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Spaces","depth":7,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Spaces","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Jiminny (New)","depth":7,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny (New)","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Platform Team","depth":4,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Platform Team","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add people","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add people","depth":6,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":4,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":6,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Share","depth":4,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Automation","depth":4,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give feedback","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Give feedback","depth":6,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Enter full screen","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Enter full screen","depth":6,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Summary","depth":7,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Summary","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Timeline","depth":7,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Timeline","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Backlog","depth":7,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Backlog","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Active sprints","depth":7,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Active sprints","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Calendar","depth":7,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Calendar","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Reports","depth":7,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Reports","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Testing Board","depth":7,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Testing Board","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"List","depth":7,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"List","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Forms","depth":7,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Forms","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Components","depth":7,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Components","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Development","depth":7,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Development","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":7,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security","depth":7,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Releases","depth":7,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Releases","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Deployments","depth":7,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Deployments","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Archived work items","depth":7,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Archived work items","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Docs","depth":7,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Docs","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Shortcuts","depth":8,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Shortcuts","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Slack integration","depth":7,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Slack integration","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Reporting Center","depth":7,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Reporting Center","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Add to navigation","depth":5,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"As you type to search or apply filters, the board updates with work items to match.","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"Search on current page","depth":5,"on_screen":true,"placeholder":"Search board","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Filter by assignee","depth":6,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Filter assignees by Lukas Kovalik","depth":5,"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Filter assignees by Aneliya Angelova","depth":5,"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Filter assignees by Nikolay Ivanov","depth":5,"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Filter assignees by Nikolay Nikolov","depth":5,"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Filter assignees by Nikolay Yankov","depth":5,"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Filter assignees by Steliyan Georgiev","depth":5,"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Filter assignees by Unassigned","depth":5,"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Epic","depth":7,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Epic","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Type","depth":7,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Type","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Quick filters","depth":7,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Quick filters","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Complete sprint","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Complete sprint","depth":6,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Sprint details","depth":4,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Sprint details","depth":6,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Group by Queries","depth":4,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Group","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": Queries","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Sprint insights","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Sprint insights","depth":6,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"View settings","depth":4,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"View settings","depth":6,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions","depth":4,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions","depth":6,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Ready For DEV","depth":10,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"READY FOR DEV","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"JY-19958 Upgrade BE libraries - May. Use the enter key to load the work item.","depth":10,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Upgrade BE libraries - May","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"MAINTENANCE","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Backlog","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-19958","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-19958","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"In DEV","depth":10,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"IN DEV","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-9085616824272452003
|
5374248625637479043
|
click
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Service-Desk - Queues - Platform team - Service space - Jira
TypeError: Jiminny\Services\Mail\TextRelayService::setHistoryPoint(): Argument #2 ($historyPoint) must be of type int, string given, called in /home/jiminny/app/Services/Mail/TextRelayService.php on line 118 — jiminny — app
TypeError: Jiminny\Services\Mail\TextRelayService::setHistoryPoint(): Argument #2 ($historyPoint) must be of type int, string given, called in /home/jiminny/app/Services/Mail/TextRelayService.php on line 118 — jiminny — app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to:
Top Bar
Top Bar
Sidebar
Sidebar
Main Content
Main Content
Space navigation
Space navigation
Expand sidebar [
Expand sidebar [
Switch sites or apps
Switch sites or apps
Go to your Jira homepage
Search, press enter to navigate to advanced search with your text query
Create
Create
Rovo Ask Rovo
Ask Rovo
Notifications
Notifications
Help
Help
Settings
Settings
[EMAIL]
[EMAIL]
Spaces
Spaces
/
Jiminny (New)
Jiminny (New)
Platform Team
Platform Team
Add people
Add people
Board actions
Board actions
Share
Automation
Give feedback
Give feedback
Enter full screen
Enter full screen
Summary
Summary
Timeline
Timeline
Backlog
Backlog
Active sprints
Active sprints
Calendar
Calendar
Reports
Reports
Testing Board
Testing Board
List
List
Forms
Forms
Components
Components
Development
Development
Code
Code
Security
Security
Releases
Releases
Deployments
Deployments
Archived work items
Archived work items
Docs
Docs
Shortcuts
Shortcuts
Slack integration
Slack integration
Reporting Center
Reporting Center
Add to navigation
As you type to search or apply filters, the board updates with work items to match.
Search on current page
Filter by assignee
Filter assignees by Lukas Kovalik
Filter assignees by Aneliya Angelova
Filter assignees by Nikolay Ivanov
Filter assignees by Nikolay Nikolov
Filter assignees by Nikolay Yankov
Filter assignees by Steliyan Georgiev
Filter assignees by Unassigned
Epic
Epic
Type
Type
Quick filters
Quick filters
Complete sprint
Complete sprint
Sprint details
Sprint details
Group by Queries
Group
: Queries
Sprint insights
Sprint insights
View settings
View settings
More actions
More actions
Ready For DEV
READY FOR DEV
1
JY-19958 Upgrade BE libraries - May. Use the enter key to load the work item.
Upgrade BE libraries - May
MAINTENANCE
Backlog
JY-19958
JY-19958
1
In DEV
IN DEV...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
32608
|
1251
|
36
|
2026-05-13T09:29:44.831675+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-13/1778 /Users/lukas/.screenpipe/data/data/2026-05-13/1778664584831_m2.jpg...
|
Firefox
|
jiminny/app/backend-code - Vanta — Work
|
1
|
app.vanta.com/c/jiminny.com/vulnerabilities/findin app.vanta.com/c/jiminny.com/vulnerabilities/findings-by-asset/672dbbd0b16e6beaba3ddc4f...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
[JY-19967] Upgrade Python and libraries - Apr - Ji [JY-19967] Upgrade Python and libraries - Apr - Jira
[JY-19967] Upgrade Python and libraries - Apr - Jira
Dependabot alerts · jiminny/prophet
Dependabot alerts · jiminny/prophet
[JY-19958] Upgrade BE libraries - May - Jira
[JY-19958] Upgrade BE libraries - May - Jira
jiminny/app/backend-code - Vanta
jiminny/app/backend-code - Vanta
Low vulnerabilities identified in packages are addressed (GitHub Repo) - Vanta
Low vulnerabilities identified in packages are addressed (GitHub Repo) - Vanta
jiminny/app/backend-code - Vanta
jiminny/app/backend-code - Vanta
Close tab
[JY-20773] User Pilot not receiving events on report generated - Jira
[JY-20773] User Pilot not receiving events on report generated - Jira
JY-19957 | Remove abanded sympfony debug, compose upgrade by nikolaybiaivanov · Pull Request #12022 · jiminny/app
JY-19957 | Remove abanded sympfony debug, compose upgrade by nikolaybiaivanov · Pull Request #12022 · jiminny/app
Project Phoenix – Figma
Project Phoenix – Figma
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
New Tab
New Tab
Userpilot | Ask Jiminny Report Generated
Userpilot | Ask Jiminny Report Generated
[SRD-6848] Sidekick SMS issue - Jira
[SRD-6848] Sidekick SMS issue - Jira
[JY-19957] Upgrade BE libraries - Apr - Jira
[JY-19957] Upgrade BE libraries - Apr - Jira
JY-19957 | Remove abanded sympfony debug, compose upgrade by nikolaybiaivanov · Pull Request #12022 · jiminny/app
JY-19957 | Remove abanded sympfony debug, compose upgrade by nikolaybiaivanov · Pull Request #12022 · jiminny/app
High vulnerabilities identified in packages are addressed (GitHub Repo) - Vanta
High vulnerabilities identified in packages are addressed (GitHub Repo) - Vanta
Medium vulnerabilities identified in packages are addressed (GitHub Repo) - Vanta
Medium vulnerabilities identified in packages are addressed (GitHub Repo) - Vanta
Low vulnerabilities identified in packages are addressed (GitHub Repo) - Vanta
Low vulnerabilities identified in packages are addressed (GitHub Repo) - Vanta
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Vanta Agent
Product updates
Comments & activity
Settings
Lukas Kovalik
Help - command K
Help...
⌘+K
Home
Home
My work
My work
Vanta Agent
Vanta Agent
Tests
Tests
Reports
Reports
Compliance
Compliance
Customer trust
Customer trust
Risk
Risk
Vendors
Vendors
Assets
Assets
Inventory
Inventory
Code changes
Code changes
Vulnerabilities
Vulnerabilities
Security alerts
Security alerts
Personnel
Personnel
Integrations
Integrations
My security tasks
My security tasks
My access requests
My access requests
Collapse
Findings by asset
Findings by asset
/
jiminny/app/backend-code
jiminny/app/backend-code
jiminny/app/backend-code
jiminny/app/backend-code
Configure image scan
Configure image scan
Refresh
Refresh
Amazon Web Services (AWS)
Findings by asset
Findings by asset
Findings by vulnerability
Findings by vulnerability
Deactivated
Deactivated
History
History
Search
Source
Source
Severity
Severity
Select all rows
PACKAGE
SOURCES
VULNERABILITIES
SCORE
SEVERITY
DUE DATE
Select row 1
linux:6.1.164
339 vulnerabilities
9.8
Critical
Due in 14 days
Select row 2
php/php:8.3.30
8 vulnerabilities
—
Critical
Due in 14 days
Select row 3
glibc:2.36
6 vulnerabilities
8.4
High
Due in 30 days
Select row 4
dpkg:1.21.22
2 vulnerabilities
8.2
High
Due in 30 days
Select row 5...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"[JY-19967] Upgrade Python and libraries - Apr - Jira","depth":4,"bounds":{"left":0.2237367,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-19967] Upgrade Python and libraries - Apr - Jira","depth":5,"bounds":{"left":0.23703457,"top":0.06304868,"width":0.09158909,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Dependabot alerts · jiminny/prophet","depth":4,"bounds":{"left":0.2237367,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Dependabot alerts · jiminny/prophet","depth":5,"bounds":{"left":0.23703457,"top":0.09577015,"width":0.06216755,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-19958] Upgrade BE libraries - May - Jira","depth":4,"bounds":{"left":0.2237367,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-19958] Upgrade BE libraries - May - Jira","depth":5,"bounds":{"left":0.23703457,"top":0.12849163,"width":0.07762633,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"jiminny/app/backend-code - Vanta","depth":4,"bounds":{"left":0.2237367,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny/app/backend-code - Vanta","depth":5,"bounds":{"left":0.23703457,"top":0.16121309,"width":0.059674203,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Low vulnerabilities identified in packages are addressed (GitHub Repo) - Vanta","depth":4,"bounds":{"left":0.2237367,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Low vulnerabilities identified in packages are addressed (GitHub Repo) - Vanta","depth":5,"bounds":{"left":0.23703457,"top":0.19393456,"width":0.13680187,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"jiminny/app/backend-code - Vanta","depth":4,"bounds":{"left":0.2237367,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"jiminny/app/backend-code - Vanta","depth":5,"bounds":{"left":0.23703457,"top":0.22665602,"width":0.059674203,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.29105717,"top":0.22266561,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20773] User Pilot not receiving events on report generated - Jira","depth":4,"bounds":{"left":0.2237367,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20773] User Pilot not receiving events on report generated - Jira","depth":5,"bounds":{"left":0.23703457,"top":0.25937748,"width":0.1200133,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-19957 | Remove abanded sympfony debug, compose upgrade by nikolaybiaivanov · Pull Request #12022 · jiminny/app","depth":4,"bounds":{"left":0.2237367,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-19957 | Remove abanded sympfony debug, compose upgrade by nikolaybiaivanov · Pull Request #12022 · jiminny/app","depth":5,"bounds":{"left":0.23703457,"top":0.29209897,"width":0.20977394,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Project Phoenix – Figma","depth":4,"bounds":{"left":0.2237367,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Project Phoenix – Figma","depth":5,"bounds":{"left":0.23703457,"top":0.32482043,"width":0.041888297,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"TypeError: League\\Flysystem\\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app","depth":4,"bounds":{"left":0.2237367,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"TypeError: League\\Flysystem\\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app","depth":5,"bounds":{"left":0.23703457,"top":0.3575419,"width":0.40475398,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.2237367,"top":0.3790902,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.23703457,"top":0.39026338,"width":0.014960106,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Userpilot | Ask Jiminny Report Generated","depth":4,"bounds":{"left":0.2237367,"top":0.41181165,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Userpilot | Ask Jiminny Report Generated","depth":5,"bounds":{"left":0.23703457,"top":0.42298484,"width":0.07164229,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6848] Sidekick SMS issue - Jira","depth":4,"bounds":{"left":0.2237367,"top":0.4445331,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6848] Sidekick SMS issue - Jira","depth":5,"bounds":{"left":0.23703457,"top":0.4557063,"width":0.06632314,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-19957] Upgrade BE libraries - Apr - Jira","depth":4,"bounds":{"left":0.2237367,"top":0.4772546,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-19957] Upgrade BE libraries - Apr - Jira","depth":5,"bounds":{"left":0.23703457,"top":0.4884278,"width":0.076296546,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-19957 | Remove abanded sympfony debug, compose upgrade by nikolaybiaivanov · Pull Request #12022 · jiminny/app","depth":4,"bounds":{"left":0.2237367,"top":0.509976,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-19957 | Remove abanded sympfony debug, compose upgrade by nikolaybiaivanov · Pull Request #12022 · jiminny/app","depth":5,"bounds":{"left":0.23703457,"top":0.5211492,"width":0.20977394,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"High vulnerabilities identified in packages are addressed (GitHub Repo) - Vanta","depth":4,"bounds":{"left":0.2237367,"top":0.54269755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"High vulnerabilities identified in packages are addressed (GitHub Repo) - Vanta","depth":5,"bounds":{"left":0.23703457,"top":0.55387074,"width":0.13796543,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Medium vulnerabilities identified in packages are addressed (GitHub Repo) - Vanta","depth":4,"bounds":{"left":0.2237367,"top":0.575419,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Medium vulnerabilities identified in packages are addressed (GitHub Repo) - Vanta","depth":5,"bounds":{"left":0.23703457,"top":0.5865922,"width":0.14378324,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Low vulnerabilities identified in packages are addressed (GitHub Repo) - Vanta","depth":4,"bounds":{"left":0.2237367,"top":0.60814047,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Low vulnerabilities identified in packages are addressed (GitHub Repo) - Vanta","depth":5,"bounds":{"left":0.23703457,"top":0.61931366,"width":0.13680187,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.2265625,"top":0.6424581,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.2265625,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.23753324,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.2486702,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.25980717,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.27094415,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Vanta Agent","depth":10,"bounds":{"left":0.9162234,"top":0.061452515,"width":0.010638298,"height":0.025538707},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Product updates","depth":9,"bounds":{"left":0.9428192,"top":0.061452515,"width":0.010638298,"height":0.025538707},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Comments & activity","depth":10,"bounds":{"left":0.95611703,"top":0.061452515,"width":0.010638298,"height":0.025538707},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Settings","depth":10,"bounds":{"left":0.9694149,"top":0.061452515,"width":0.010638298,"height":0.025538707},"on_screen":true,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Lukas Kovalik","depth":10,"bounds":{"left":0.98271275,"top":0.061851557,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Help - command K","depth":11,"bounds":{"left":0.3073471,"top":0.10654429,"width":0.06382979,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Help...","depth":13,"bounds":{"left":0.3179854,"top":0.11213089,"width":0.014461436,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"⌘+K","depth":13,"bounds":{"left":0.3538896,"top":0.11213089,"width":0.010638298,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Home","depth":10,"bounds":{"left":0.3073471,"top":0.14166002,"width":0.06382979,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Home","depth":13,"bounds":{"left":0.31865028,"top":0.14884278,"width":0.013297873,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"My work","depth":10,"bounds":{"left":0.3073471,"top":0.17198724,"width":0.06382979,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"My work","depth":13,"bounds":{"left":0.31865028,"top":0.17917,"width":0.019281914,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Vanta Agent","depth":10,"bounds":{"left":0.3073471,"top":0.20231445,"width":0.06382979,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Vanta Agent","depth":13,"bounds":{"left":0.31865028,"top":0.20949721,"width":0.027426861,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Tests","depth":10,"bounds":{"left":0.3073471,"top":0.23264167,"width":0.06382979,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Tests","depth":13,"bounds":{"left":0.31865028,"top":0.23982441,"width":0.011968086,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Reports","depth":10,"bounds":{"left":0.3073471,"top":0.26296887,"width":0.06382979,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Reports","depth":13,"bounds":{"left":0.31865028,"top":0.27015164,"width":0.017287234,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Compliance","depth":10,"bounds":{"left":0.3073471,"top":0.30526736,"width":0.06382979,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Compliance","depth":12,"bounds":{"left":0.31865028,"top":0.3124501,"width":0.026595745,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Customer trust","depth":10,"bounds":{"left":0.3073471,"top":0.33559456,"width":0.06382979,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Customer trust","depth":12,"bounds":{"left":0.31865028,"top":0.34277734,"width":0.033410903,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Risk","depth":10,"bounds":{"left":0.3073471,"top":0.3659218,"width":0.06382979,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Risk","depth":12,"bounds":{"left":0.31865028,"top":0.37310454,"width":0.00930851,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Vendors","depth":10,"bounds":{"left":0.3073471,"top":0.396249,"width":0.06382979,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Vendors","depth":12,"bounds":{"left":0.31865028,"top":0.40343177,"width":0.01861702,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Assets","depth":10,"bounds":{"left":0.3073471,"top":0.42657623,"width":0.06382979,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"Assets","depth":12,"bounds":{"left":0.31865028,"top":0.43375897,"width":0.014960106,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Inventory","depth":11,"bounds":{"left":0.3073471,"top":0.45690343,"width":0.06382979,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inventory","depth":14,"bounds":{"left":0.31865028,"top":0.46448523,"width":0.019614361,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code changes","depth":11,"bounds":{"left":0.3073471,"top":0.48723066,"width":0.06382979,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code changes","depth":14,"bounds":{"left":0.31865028,"top":0.49481246,"width":0.030086435,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Vulnerabilities","depth":11,"bounds":{"left":0.3073471,"top":0.51755786,"width":0.06382979,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Vulnerabilities","depth":14,"bounds":{"left":0.31865028,"top":0.5251397,"width":0.029421542,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security alerts","depth":11,"bounds":{"left":0.3073471,"top":0.54788506,"width":0.06382979,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security alerts","depth":14,"bounds":{"left":0.31865028,"top":0.5554669,"width":0.03025266,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Personnel","depth":10,"bounds":{"left":0.3073471,"top":0.5798085,"width":0.06382979,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Personnel","depth":12,"bounds":{"left":0.31865028,"top":0.58699125,"width":0.02244016,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Integrations","depth":10,"bounds":{"left":0.3073471,"top":0.62210697,"width":0.06382979,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Integrations","depth":13,"bounds":{"left":0.31865028,"top":0.6292897,"width":0.026595745,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"My security tasks","depth":10,"bounds":{"left":0.3073471,"top":0.6644054,"width":0.06382979,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"My security tasks","depth":13,"bounds":{"left":0.31865028,"top":0.6715882,"width":0.03956117,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"My access requests","depth":10,"bounds":{"left":0.3073471,"top":0.69473267,"width":0.06382979,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"My access requests","depth":13,"bounds":{"left":0.31865028,"top":0.7019154,"width":0.04488032,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Collapse","depth":11,"bounds":{"left":0.36053857,"top":0.9648843,"width":0.010638298,"height":0.025538707},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Findings by asset","depth":12,"bounds":{"left":0.38314494,"top":0.11612131,"width":0.03357713,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Findings by asset","depth":14,"bounds":{"left":0.38314494,"top":0.11652035,"width":0.03357713,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"bounds":{"left":0.41805187,"top":0.11652035,"width":0.0014960107,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"jiminny/app/backend-code","depth":12,"bounds":{"left":0.42087767,"top":0.11612131,"width":0.051529255,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny/app/backend-code","depth":14,"bounds":{"left":0.42087767,"top":0.11652035,"width":0.051529255,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"jiminny/app/backend-code","depth":9,"bounds":{"left":0.38314494,"top":0.13527533,"width":0.4898604,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"jiminny/app/backend-code","depth":11,"bounds":{"left":0.38314494,"top":0.13607343,"width":0.10405585,"height":0.023543496},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Configure image scan","depth":10,"bounds":{"left":0.89162236,"top":0.13527533,"width":0.06499335,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Configure image scan","depth":12,"bounds":{"left":0.9025931,"top":0.14086193,"width":0.049700797,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Refresh","depth":9,"bounds":{"left":0.95927525,"top":0.13527533,"width":0.03274601,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Refresh","depth":11,"bounds":{"left":0.970246,"top":0.14086193,"width":0.017453458,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Amazon Web Services (AWS)","depth":11,"bounds":{"left":0.38314494,"top":0.16480447,"width":0.06582447,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Findings by asset","depth":9,"bounds":{"left":0.38314494,"top":0.19273743,"width":0.07446808,"height":0.025139665},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Findings by asset","depth":11,"bounds":{"left":0.38580453,"top":0.19832402,"width":0.039228722,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Findings by vulnerability","depth":9,"bounds":{"left":0.38314494,"top":0.21947326,"width":0.07446808,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Findings by vulnerability","depth":11,"bounds":{"left":0.38580453,"top":0.2254589,"width":0.053690158,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Deactivated","depth":9,"bounds":{"left":0.38314494,"top":0.24660814,"width":0.07446808,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Deactivated","depth":11,"bounds":{"left":0.38580453,"top":0.2525938,"width":0.026097074,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"History","depth":9,"bounds":{"left":0.38314494,"top":0.273743,"width":0.07446808,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"History","depth":11,"bounds":{"left":0.38580453,"top":0.27972865,"width":0.015625,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"Search","depth":8,"bounds":{"left":0.46293217,"top":0.19273743,"width":0.07795878,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Source","depth":9,"bounds":{"left":0.54355055,"top":0.19273743,"width":0.030751329,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Source","depth":11,"bounds":{"left":0.5482048,"top":0.19832402,"width":0.016123671,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Severity","depth":9,"bounds":{"left":0.5756317,"top":0.19273743,"width":0.03324468,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Severity","depth":11,"bounds":{"left":0.5802859,"top":0.19832402,"width":0.01861702,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Select all rows","depth":13,"bounds":{"left":0.47124335,"top":0.2406225,"width":0.004654255,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"PACKAGE","depth":12,"bounds":{"left":0.4872008,"top":0.24501197,"width":0.01861702,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SOURCES","depth":12,"bounds":{"left":0.66306514,"top":0.24501197,"width":0.018949468,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"VULNERABILITIES","depth":12,"bounds":{"left":0.71575797,"top":0.24501197,"width":0.034574468,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SCORE","depth":12,"bounds":{"left":0.80369014,"top":0.24501197,"width":0.013464096,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SEVERITY","depth":12,"bounds":{"left":0.85638297,"top":0.24501197,"width":0.019281914,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"DUE DATE","depth":12,"bounds":{"left":0.9090758,"top":0.24501197,"width":0.019614361,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Select row 1","depth":14,"bounds":{"left":0.47124335,"top":0.2793296,"width":0.004654255,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"linux:6.1.164","depth":13,"bounds":{"left":0.4872008,"top":0.28252193,"width":0.02642952,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"339 vulnerabilities","depth":13,"bounds":{"left":0.71575797,"top":0.28252193,"width":0.040226065,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"9.8","depth":13,"bounds":{"left":0.80369014,"top":0.28252193,"width":0.006981383,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Critical","depth":13,"bounds":{"left":0.8643617,"top":0.28371906,"width":0.013796543,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Due in 14 days","depth":14,"bounds":{"left":0.9090758,"top":0.28252193,"width":0.032579787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Select row 2","depth":14,"bounds":{"left":0.47124335,"top":0.32402235,"width":0.004654255,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"php/php:8.3.30","depth":13,"bounds":{"left":0.4872008,"top":0.3272147,"width":0.034408245,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8 vulnerabilities","depth":13,"bounds":{"left":0.71575797,"top":0.3272147,"width":0.034574468,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.80369014,"top":0.3272147,"width":0.004654255,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Critical","depth":13,"bounds":{"left":0.8643617,"top":0.32841182,"width":0.013796543,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Due in 14 days","depth":14,"bounds":{"left":0.9090758,"top":0.3272147,"width":0.032579787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Select row 3","depth":14,"bounds":{"left":0.47124335,"top":0.36871508,"width":0.004654255,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"glibc:2.36","depth":13,"bounds":{"left":0.4872008,"top":0.3719074,"width":0.022273935,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6 vulnerabilities","depth":13,"bounds":{"left":0.71575797,"top":0.3719074,"width":0.034574468,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8.4","depth":13,"bounds":{"left":0.80369014,"top":0.3719074,"width":0.0071476065,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"High","depth":13,"bounds":{"left":0.8643617,"top":0.37310454,"width":0.008976064,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Due in 30 days","depth":14,"bounds":{"left":0.9090758,"top":0.3719074,"width":0.03357713,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Select row 4","depth":14,"bounds":{"left":0.47124335,"top":0.41340783,"width":0.004654255,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"dpkg:1.21.22","depth":13,"bounds":{"left":0.4872008,"top":0.41660017,"width":0.027925532,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2 vulnerabilities","depth":13,"bounds":{"left":0.71575797,"top":0.41660017,"width":0.034408245,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8.2","depth":13,"bounds":{"left":0.80369014,"top":0.41660017,"width":0.006981383,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"High","depth":13,"bounds":{"left":0.8643617,"top":0.4177973,"width":0.008976064,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Due in 30 days","depth":14,"bounds":{"left":0.9090758,"top":0.41660017,"width":0.03357713,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Select row 5","depth":14,"bounds":{"left":0.47124335,"top":0.45810056,"width":0.004654255,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"checkbox","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-9084178338168001636
|
-4954775617487390314
|
click
|
accessibility
|
NULL
|
[JY-19967] Upgrade Python and libraries - Apr - Ji [JY-19967] Upgrade Python and libraries - Apr - Jira
[JY-19967] Upgrade Python and libraries - Apr - Jira
Dependabot alerts · jiminny/prophet
Dependabot alerts · jiminny/prophet
[JY-19958] Upgrade BE libraries - May - Jira
[JY-19958] Upgrade BE libraries - May - Jira
jiminny/app/backend-code - Vanta
jiminny/app/backend-code - Vanta
Low vulnerabilities identified in packages are addressed (GitHub Repo) - Vanta
Low vulnerabilities identified in packages are addressed (GitHub Repo) - Vanta
jiminny/app/backend-code - Vanta
jiminny/app/backend-code - Vanta
Close tab
[JY-20773] User Pilot not receiving events on report generated - Jira
[JY-20773] User Pilot not receiving events on report generated - Jira
JY-19957 | Remove abanded sympfony debug, compose upgrade by nikolaybiaivanov · Pull Request #12022 · jiminny/app
JY-19957 | Remove abanded sympfony debug, compose upgrade by nikolaybiaivanov · Pull Request #12022 · jiminny/app
Project Phoenix – Figma
Project Phoenix – Figma
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
New Tab
New Tab
Userpilot | Ask Jiminny Report Generated
Userpilot | Ask Jiminny Report Generated
[SRD-6848] Sidekick SMS issue - Jira
[SRD-6848] Sidekick SMS issue - Jira
[JY-19957] Upgrade BE libraries - Apr - Jira
[JY-19957] Upgrade BE libraries - Apr - Jira
JY-19957 | Remove abanded sympfony debug, compose upgrade by nikolaybiaivanov · Pull Request #12022 · jiminny/app
JY-19957 | Remove abanded sympfony debug, compose upgrade by nikolaybiaivanov · Pull Request #12022 · jiminny/app
High vulnerabilities identified in packages are addressed (GitHub Repo) - Vanta
High vulnerabilities identified in packages are addressed (GitHub Repo) - Vanta
Medium vulnerabilities identified in packages are addressed (GitHub Repo) - Vanta
Medium vulnerabilities identified in packages are addressed (GitHub Repo) - Vanta
Low vulnerabilities identified in packages are addressed (GitHub Repo) - Vanta
Low vulnerabilities identified in packages are addressed (GitHub Repo) - Vanta
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Vanta Agent
Product updates
Comments & activity
Settings
Lukas Kovalik
Help - command K
Help...
⌘+K
Home
Home
My work
My work
Vanta Agent
Vanta Agent
Tests
Tests
Reports
Reports
Compliance
Compliance
Customer trust
Customer trust
Risk
Risk
Vendors
Vendors
Assets
Assets
Inventory
Inventory
Code changes
Code changes
Vulnerabilities
Vulnerabilities
Security alerts
Security alerts
Personnel
Personnel
Integrations
Integrations
My security tasks
My security tasks
My access requests
My access requests
Collapse
Findings by asset
Findings by asset
/
jiminny/app/backend-code
jiminny/app/backend-code
jiminny/app/backend-code
jiminny/app/backend-code
Configure image scan
Configure image scan
Refresh
Refresh
Amazon Web Services (AWS)
Findings by asset
Findings by asset
Findings by vulnerability
Findings by vulnerability
Deactivated
Deactivated
History
History
Search
Source
Source
Severity
Severity
Select all rows
PACKAGE
SOURCES
VULNERABILITIES
SCORE
SEVERITY
DUE DATE
Select row 1
linux:6.1.164
339 vulnerabilities
9.8
Critical
Due in 14 days
Select row 2
php/php:8.3.30
8 vulnerabilities
—
Critical
Due in 14 days
Select row 3
glibc:2.36
6 vulnerabilities
8.4
High
Due in 30 days
Select row 4
dpkg:1.21.22
2 vulnerabilities
8.2
High
Due in 30 days
Select row 5...
|
32606
|
NULL
|
NULL
|
NULL
|
|
59068
|
2101
|
7
|
2026-05-19T14:00:18.187349+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-19/1779 /Users/lukas/.screenpipe/data/data/2026-05-19/1779199218187_m2.jpg...
|
PhpStorm
|
Code Analysis
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Code Analysis
text/html
text/html
text/html
File ~ Code Analysis
text/html
text/html
text/html
File ~/jiminny/app/app/Services/Mail/TextRelayService.php contains problems.<br/>No errors and one warning found.<br/>Would you like to review them?
Cancel
Commit and Push
Review Code Analysis...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Code Analysis","depth":1,"bounds":{"left":0.35006648,"top":0.28411812,"width":0.14926861,"height":0.014365523},"on_screen":true,"value":"Code Analysis","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":2,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":2,"bounds":{"left":0.35006648,"top":0.28411812,"width":0.032247342,"height":0.014365523},"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":2,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"File ~/jiminny/app/app/Services/Mail/TextRelayService.php contains problems.<br/>No errors and one warning found.<br/>Would you like to review them?","depth":1,"bounds":{"left":0.35006648,"top":0.3048683,"width":0.14926861,"height":0.054269753},"on_screen":true,"help_text":"text/html","role_description":"text"},{"role":"AXButton","text":"Cancel","depth":1,"bounds":{"left":0.35006648,"top":0.37430167,"width":0.028590426,"height":0.027134877},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Commit and Push","depth":1,"bounds":{"left":0.38264626,"top":0.37430167,"width":0.051529255,"height":0.027134877},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Review Code Analysis","depth":1,"bounds":{"left":0.4381649,"top":0.37430167,"width":0.061170213,"height":0.027134877},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-9083935993540533458
|
6427139392695469772
|
visual_change
|
accessibility
|
NULL
|
Code Analysis
text/html
text/html
text/html
File ~ Code Analysis
text/html
text/html
text/html
File ~/jiminny/app/app/Services/Mail/TextRelayService.php contains problems.<br/>No errors and one warning found.<br/>Would you like to review them?
Cancel
Commit and Push
Review Code Analysis...
|
NULL
|
NULL
|
NULL
|
NULL
|