|
42881
|
914
|
12
|
2026-04-17T07:42:15.400802+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776411735400_m1.jpg...
|
Slack
|
engineering (Channel) - Jiminny Inc - Slack
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
Directories
jiminny-x-integration-app
platform-inner-team
ai-chapter
alerts
backend
confusion-clinic
curiosity_lab
engineering
frontend
general
infra-changes
jiminny-bg
platform-tickets
product_launches
random
releases
sofia-office
support
thank-yous
the_people_of_jiminny
Aneliya Angelova
,
Nikolay Yankov
,
Steliyan Georgiev
Galya Dimitrova
Nikolay Nikolov
Stoyan Tanev
Vasil Vasilev
Nikolay Ivanov
Aneliya Angelova
Ves
Steliyan Georgiev
Jira Cloud
Toast
Google Calendar
Messages
Messages
Canvas
Canvas
Files
Files
Bookmarks
Bookmarks
Open Positions
Open Positions
Benefits
Benefits
Pins
Pins
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
Nikolay Yankov
Mar 31st at 3:49:10 PM...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Jiminny Inc","depth":12,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXRadioButton","text":"Jiminny (Staging)","depth":12,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Add workspaces","depth":12,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":16,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":16,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":16,"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":16,"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":16,"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":16,"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":21,"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":21,"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":21,"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":21,"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":21,"role_description":"text"},{"role":"AXStaticText","text":"jiminny-x-integration-app","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"curiosity_lab","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"frontend","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"general","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"infra-changes","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"platform-tickets","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"product_launches","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"random","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"releases","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"sofia-office","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"support","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"thank-yous","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"the_people_of_jiminny","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"Aneliya Angelova","depth":23,"role_description":"text"},{"role":"AXStaticText","text":",","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":23,"role_description":"text"},{"role":"AXStaticText","text":",","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Nikolov","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"Stoyan Tanev","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Ivanov","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"Aneliya Angelova","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"Ves","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"Jira Cloud","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"Toast","depth":23,"role_description":"text"},{"role":"AXStaticText","text":"Google Calendar","depth":23,"role_description":"text"},{"role":"AXRadioButton","text":"Messages","depth":17,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Messages","depth":19,"role_description":"text"},{"role":"AXRadioButton","text":"Canvas","depth":17,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Canvas","depth":19,"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":17,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":19,"role_description":"text"},{"role":"AXRadioButton","text":"Bookmarks","depth":17,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Bookmarks","depth":19,"role_description":"text"},{"role":"AXRadioButton","text":"Open Positions","depth":17,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Open Positions","depth":19,"role_description":"text"},{"role":"AXRadioButton","text":"Benefits","depth":17,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Benefits","depth":19,"role_description":"text"},{"role":"AXRadioButton","text":"Pins","depth":17,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Pins","depth":19,"role_description":"text"},{"role":"AXPopUpButton","text":"Add and Edit Channel Tabs","depth":17,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Canvas","depth":17,"role_description":"text"},{"role":"AXStaticText","text":"List","depth":17,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":17,"role_description":"text"},{"role":"AXPopUpButton","text":"Jump to date","depth":23,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Nikolay Yankov","depth":24,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":24,"role_description":"text"},{"role":"AXLink","text":"Mar 31st at 3:49:10 PM","depth":24,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
2712061231413161714
|
-4127129184183149047
|
app_switch
|
hybrid
|
NULL
|
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
Directories
jiminny-x-integration-app
platform-inner-team
ai-chapter
alerts
backend
confusion-clinic
curiosity_lab
engineering
frontend
general
infra-changes
jiminny-bg
platform-tickets
product_launches
random
releases
sofia-office
support
thank-yous
the_people_of_jiminny
Aneliya Angelova
,
Nikolay Yankov
,
Steliyan Georgiev
Galya Dimitrova
Nikolay Nikolov
Stoyan Tanev
Vasil Vasilev
Nikolay Ivanov
Aneliya Angelova
Ves
Steliyan Georgiev
Jira Cloud
Toast
Google Calendar
Messages
Messages
Canvas
Canvas
Files
Files
Bookmarks
Bookmarks
Open Positions
Open Positions
Benefits
Benefits
Pins
Pins
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
Nikolay Yankov
Mar 31st at 3:49:10 PM
iTerm2ShellEdit|ViewSessionScriptsProfilesWindowHelpec2-user@ip-10-20-6-111:~X4laln• ₴5Backend Chapter • 48 m left100% C47 8• Fri 17 Apr 10:42:141₴81DOCKER• ₴1DEV (-zsh)882APP (-zsh)|X3-zsh* Review screenp...• X6ec2-user@ip-10-30-...$7Days Active: 3/3Daily Average: 265.33root@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook metrics -T 459 --from 2026-04-15 -DINFOManaging webhookmetrics for date range.Date RangeConfig IDIll Range SummaryDate RangeTotal DaysOldest Data AgeTotal WebhooksDaily AverageActive Companiesiz Daily Breakdown2026-04-15:335,647 webhooks, 88 companies active2026-04-16: 671,679 webhooks, 88 companies active2026-04-17: 58,351 webhooks, 68 companies active| Company Details2026-04-15 to2026-04-173672026-04-15 to 2026-04-1732.0 days ago1,065,677355,225.6789Company 367 (Sensat - 459)Total Webhooks: 796Days Active: 3/3Daily Average: 265.33company (114 total, avg: 38)association_change: 92 total, avg: 46, active: 2 dayscreation: 3 total, avg: 1.5, active: 2 daysproperty_change: 19 total, avg: 9.5, active: 2 daysUnique properties: 4Top properties: hubspot_owner_id(12), domain(3), name(3), phone(1)deal (164 total, avg: 54.67)property_change: 164 total, avg: 54.67, active: 3 daysUnique properties: 8Top properties: notes_last_updated(134), closedate(7), dealstage(5), hs_deal_stage_probability(5), hs_manual_forecast_category(5)contact (518 total, avg: 172.67)property_change:390 total, avg: 130, active: 3 daysUnique properties: 9Top properties: hubspot_owner_id(186), firstname(35), email(35),associatedcompanyid(33), country(33)creation: 36 total, avg: 18, active: 2 daysassociation_change: 92 total, avg: 46, active: 2 daysroote67e84f80b9d1:/home/jiminny# |ec2-user@ip-10-20-...88...
|
NULL
|
|
42882
|
915
|
19
|
2026-04-17T07:42:15.379583+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776411735379_m2.jpg...
|
Slack
|
engineering (Channel) - Jiminny Inc - Slack
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
Directories
jiminny-x-integration-app
platform-inner-team
ai-chapter
alerts
backend
confusion-clinic
curiosity_lab
engineering
frontend
general
infra-changes
jiminny-bg
platform-tickets
product_launches
random
releases
sofia-office
support
thank-yous
the_people_of_jiminny
Aneliya Angelova
,
Nikolay Yankov
,
Steliyan Georgiev
Galya Dimitrova
Nikolay Nikolov
Stoyan Tanev
Vasil Vasilev
Nikolay Ivanov
Aneliya Angelova
Ves
Steliyan Georgiev
Jira Cloud
Toast
Google Calendar
Messages
Messages
Canvas
Canvas
Files
Files
Bookmarks
Bookmarks
Open Positions
Open Positions
Benefits
Benefits
Pins
Pins
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
Nikolay Yankov
Mar 31st at 3:49:10 PM
3:49 PM
It looks like we've hit the Fontawesome monthly limit for downloads......
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Jiminny Inc","depth":12,"bounds":{"left":0.00546875,"top":0.05486111,"width":0.0125,"height":0.022222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXRadioButton","text":"Jiminny (Staging)","depth":12,"bounds":{"left":0.00546875,"top":0.09097222,"width":0.0125,"height":0.022222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Add workspaces","depth":12,"bounds":{"left":0.00546875,"top":0.12708333,"width":0.0125,"height":0.022222223},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":14,"bounds":{"left":0.026953125,"top":0.048611112,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":16,"bounds":{"left":0.03125,"top":0.08125,"width":0.012109375,"height":0.009027778},"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":14,"bounds":{"left":0.026953125,"top":0.09583333,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":16,"bounds":{"left":0.032421876,"top":0.12847222,"width":0.009765625,"height":0.009027778},"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":14,"bounds":{"left":0.026953125,"top":0.14305556,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":16,"bounds":{"left":0.0296875,"top":0.17569445,"width":0.015234375,"height":0.009027778},"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":14,"bounds":{"left":0.026953125,"top":0.19027779,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":16,"bounds":{"left":0.0328125,"top":0.22291666,"width":0.008984375,"height":0.009027778},"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":14,"bounds":{"left":0.026953125,"top":0.2375,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":16,"bounds":{"left":0.03203125,"top":0.2701389,"width":0.010546875,"height":0.009027778},"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":14,"bounds":{"left":0.026953125,"top":0.2847222,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":16,"bounds":{"left":0.03203125,"top":0.31736112,"width":0.010546875,"height":0.009027778},"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":21,"bounds":{"left":0.06679688,"top":0.0875,"width":0.021484375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":21,"bounds":{"left":0.06679688,"top":0.10694444,"width":0.020703126,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":21,"bounds":{"left":0.06679688,"top":0.12638889,"width":0.021484375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":21,"bounds":{"left":0.06679688,"top":0.14583333,"width":0.034375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":21,"bounds":{"left":0.06679688,"top":0.16527778,"width":0.028515626,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"jiminny-x-integration-app","depth":23,"bounds":{"left":0.07304688,"top":0.24722221,"width":0.0515625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":23,"bounds":{"left":0.07304688,"top":0.26666668,"width":0.05234375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":23,"bounds":{"left":0.07304688,"top":0.3125,"width":0.026171874,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":23,"bounds":{"left":0.07304688,"top":0.33194444,"width":0.014453125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":23,"bounds":{"left":0.07304688,"top":0.3513889,"width":0.021484375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":23,"bounds":{"left":0.07304688,"top":0.37083334,"width":0.040625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"curiosity_lab","depth":23,"bounds":{"left":0.07304688,"top":0.39027777,"width":0.032421876,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":23,"bounds":{"left":0.07304688,"top":0.4097222,"width":0.03046875,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"frontend","depth":23,"bounds":{"left":0.07304688,"top":0.42916667,"width":0.02265625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"general","depth":23,"bounds":{"left":0.07304688,"top":0.4486111,"width":0.019140625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"infra-changes","depth":23,"bounds":{"left":0.07304688,"top":0.46805555,"width":0.034765624,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":23,"bounds":{"left":0.07304688,"top":0.4875,"width":0.02734375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"platform-tickets","depth":23,"bounds":{"left":0.07304688,"top":0.5069444,"width":0.041015625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"product_launches","depth":23,"bounds":{"left":0.07304688,"top":0.5263889,"width":0.0453125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"random","depth":23,"bounds":{"left":0.07304688,"top":0.54583335,"width":0.019921875,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"releases","depth":23,"bounds":{"left":0.07304688,"top":0.56527776,"width":0.020703126,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"sofia-office","depth":23,"bounds":{"left":0.07304688,"top":0.5847222,"width":0.02890625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"support","depth":23,"bounds":{"left":0.07304688,"top":0.6041667,"width":0.0203125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"thank-yous","depth":23,"bounds":{"left":0.07304688,"top":0.6236111,"width":0.02890625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"the_people_of_jiminny","depth":23,"bounds":{"left":0.07304688,"top":0.64305556,"width":0.053125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Aneliya Angelova","depth":23,"bounds":{"left":0.07304688,"top":0.6888889,"width":0.044140626,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":",","depth":23,"bounds":{"left":0.11679687,"top":0.6888889,"width":0.0078125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":23,"bounds":{"left":0.11992188,"top":0.6888889,"width":0.016796876,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":",","depth":23,"bounds":{"left":0.13632813,"top":0.70416665,"width":0.000390625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":23,"bounds":{"left":0.13632813,"top":0.70416665,"width":0.000390625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":23,"bounds":{"left":0.07304688,"top":0.7083333,"width":0.04140625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Nikolov","depth":23,"bounds":{"left":0.07304688,"top":0.7277778,"width":0.040234376,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Stoyan Tanev","depth":23,"bounds":{"left":0.07304688,"top":0.74722224,"width":0.033984374,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":23,"bounds":{"left":0.07304688,"top":0.76666665,"width":0.03125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Ivanov","depth":23,"bounds":{"left":0.07304688,"top":0.7861111,"width":0.037890624,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Aneliya Angelova","depth":23,"bounds":{"left":0.07304688,"top":0.8055556,"width":0.044140626,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Ves","depth":23,"bounds":{"left":0.07304688,"top":0.825,"width":0.009375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":23,"bounds":{"left":0.07304688,"top":0.84444445,"width":0.044921875,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Jira Cloud","depth":23,"bounds":{"left":0.07304688,"top":0.8902778,"width":0.02578125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Toast","depth":23,"bounds":{"left":0.07304688,"top":0.9097222,"width":0.013671875,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Google Calendar","depth":23,"bounds":{"left":0.07304688,"top":0.9291667,"width":0.0359375,"height":0.0125},"role_description":"text"},{"role":"AXRadioButton","text":"Messages","depth":17,"bounds":{"left":0.14335938,"top":0.07986111,"width":0.036328126,"height":0.02638889},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Messages","depth":19,"bounds":{"left":0.15429688,"top":0.0875,"width":0.022265624,"height":0.011111111},"role_description":"text"},{"role":"AXRadioButton","text":"Canvas","depth":17,"bounds":{"left":0.18085937,"top":0.07986111,"width":0.03046875,"height":0.02638889},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Canvas","depth":19,"bounds":{"left":0.19179687,"top":0.0875,"width":0.01640625,"height":0.011111111},"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":17,"bounds":{"left":0.2125,"top":0.07986111,"width":0.025,"height":0.02638889},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":19,"bounds":{"left":0.2234375,"top":0.0875,"width":0.0109375,"height":0.011111111},"role_description":"text"},{"role":"AXRadioButton","text":"Bookmarks","depth":17,"bounds":{"left":0.23867187,"top":0.07986111,"width":0.03984375,"height":0.02638889},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Bookmarks","depth":19,"bounds":{"left":0.24960938,"top":0.0875,"width":0.02578125,"height":0.011111111},"role_description":"text"},{"role":"AXRadioButton","text":"Open Positions","depth":17,"bounds":{"left":0.2796875,"top":0.07986111,"width":0.04921875,"height":0.02638889},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Open Positions","depth":19,"bounds":{"left":0.290625,"top":0.0875,"width":0.03515625,"height":0.011111111},"role_description":"text"},{"role":"AXRadioButton","text":"Benefits","depth":17,"bounds":{"left":0.33007812,"top":0.07986111,"width":0.033203125,"height":0.02638889},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Benefits","depth":19,"bounds":{"left":0.34101564,"top":0.0875,"width":0.019140625,"height":0.011111111},"role_description":"text"},{"role":"AXRadioButton","text":"Pins","depth":17,"bounds":{"left":0.36445314,"top":0.07986111,"width":0.023828125,"height":0.02638889},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Pins","depth":19,"bounds":{"left":0.37539062,"top":0.0875,"width":0.009765625,"height":0.011111111},"role_description":"text"},{"role":"AXPopUpButton","text":"Add and Edit Channel Tabs","depth":17,"bounds":{"left":0.3894531,"top":0.07986111,"width":0.012890625,"height":0.02638889},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Canvas","depth":17,"bounds":{"left":0.13671875,"top":0.045138888,"width":0.01875,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"List","depth":17,"bounds":{"left":0.13671875,"top":0.045138888,"width":0.009375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":17,"bounds":{"left":0.13671875,"top":0.045138888,"width":0.01640625,"height":0.00069444446},"role_description":"text"},{"role":"AXPopUpButton","text":"Jump to date","depth":23,"bounds":{"left":0.28632814,"top":0.10069445,"width":0.0625,"height":0.00069444446},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Nikolay Yankov","depth":24,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.040625,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":24,"bounds":{"left":0.20234375,"top":0.10069445,"width":0.003515625,"height":0.00069444446},"role_description":"text"},{"role":"AXLink","text":"Mar 31st at 3:49:10 PM","depth":24,"bounds":{"left":0.20546874,"top":0.10069445,"width":0.01796875,"height":0.00069444446},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"3:49 PM","depth":25,"bounds":{"left":0.20546874,"top":0.10069445,"width":0.01796875,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"It looks like we've hit the Fontawesome monthly limit for downloads...","depth":25,"bounds":{"left":0.16210938,"top":0.10069445,"width":0.17851563,"height":0.00069444446},"role_description":"text"}]...
|
-6787120221438246705
|
-4127133728266937591
|
app_switch
|
hybrid
|
NULL
|
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
Directories
jiminny-x-integration-app
platform-inner-team
ai-chapter
alerts
backend
confusion-clinic
curiosity_lab
engineering
frontend
general
infra-changes
jiminny-bg
platform-tickets
product_launches
random
releases
sofia-office
support
thank-yous
the_people_of_jiminny
Aneliya Angelova
,
Nikolay Yankov
,
Steliyan Georgiev
Galya Dimitrova
Nikolay Nikolov
Stoyan Tanev
Vasil Vasilev
Nikolay Ivanov
Aneliya Angelova
Ves
Steliyan Georgiev
Jira Cloud
Toast
Google Calendar
Messages
Messages
Canvas
Canvas
Files
Files
Bookmarks
Bookmarks
Open Positions
Open Positions
Benefits
Benefits
Pins
Pins
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
Nikolay Yankov
Mar 31st at 3:49:10 PM
3:49 PM
It looks like we've hit the Fontawesome monthly limit for downloads...
SackFileEditViewHistoryWindowHelpSearch Jiminny IncJiminny ...# engineering8 24QDMs= Unreads@ Threads6 HuddlesDrafts & sent:8 DirectoriesAchivityEh External connectionsFiles* Starred& jiminny-x-integrati...platform-inner-teamMore# Channels# ai-chapter# alerts# backends contlicion-clnid# curiosity lab# engineering# frontendi# general# infra-changes# jiminny-bg# platform-tickets# product_launchesac random#: releases# sofia-office# support# thank-yous# the people of iimi..0 Direct messages(3 Aneliya Angelova, ...®. Galya Dimitrova0. Nikolay Nikolov ED. Stoyan Tanev€. Vasil Vasilev. Nikolay IvanovP. Aneliya AngelovaP Ves&. Steliyan Georgiev#: AppsJira Cloudoast• MessagesCanvasC Files• BookmarksOpen PositionsBenefits& Pins +@here do not deploy within the next one hour. TWednesday, April 1st • 'e validated frst. Wait for confirmation beforedeployingDesislav Damyanov 12:58 PMRevert is up, can be validated0 2 replies Last reply 16 days agoNikolay Ivanov 1:19 PMyou can proceed with deployingMonday, April 6th~lliyana Netseva 10:30 AMwas added to #engineering by Mira.Yesterday~CircleCI APP 9:02 AM2 PRs with vulnerability fixes are ready for reviewPlease take a look at the code and confirm that everything works properly on your local machine or on a planet environment &*Pull requests (jiminny/app)• #11975 fix(security): npm dependency updates - 2026-04-16 ( secfix/npm-20260416)• #11970 fix(security): composer dependency updates - 2026-04-15 ( secfix/composer -20260415 )View workflow runStefka Stoyanova 11:20 AMHi team, thanks to @Nikolay Yankov when dependabot finds security vulnerabilities, PRs are open like the aboveI suggest that developers on SRD from both teams review and test them, so that they can be merged timely A#1Vasil Vasilev 12:01 PMPlease do not merge for a few minutes, there's a hotfix in the pipeline fixing a wrong class pathVasil Vasilev 12:43 PMyou can now merge againToday~urc ec APP 8.52 AMI2 PRs with vulnerability fixes are ready for review &Please take a look at the code and confirm that everything works properly on your local machine or on a planet environment f*Pull requests (jiminny/app)• #11975 fix(security): npm dependency updates - 2026-04-16 ( secfix/npm-20260416)• #11970 fix(security): composer dependency updates - 2026-04-15 ( secfix/composer-20260415)Vew worktlow runWan Kyuchukov 10:28 AMWe have a broken loop of suncing Stage changes for two clients, which results in non-stop requests to Prophet (which costsmonevHas there been any changes to the logic?opportunity_stagesODPOTUNll 8-974547nas 1040Tec0rsMessage #engineeringAa40 WoBackend Chapter • 48 m leftA100% CS•8 • Fri 17 Apr 10:42:15=Account ID: 4103-4619-5943United States (Ohio)MediaLive• Summarize results# Investigate +Share resultsExport resultsAdd to dashboard28 of 428 records matched Oscanned in 40.6s @ 8,052,583 records/s (2.1 GB/s)Hide histogram04/1304/1304/1404/1404/1404/1404/1504/15LIL04/1504/1504/16LLI04/1604/1604/1604/17®logStream64459&userEmail=[EMAIL]&userId=70437797 HT...php-app/php-app/f2dc7efdb4714f748a69327ebb9d90dc Lªuk&userId=70437797" 404 /home/jiminny/public/index.php 45.96.php-app/php-app/f2dc7efdb4714f748a69327ebb9d90dc Laccess token request {"portalId": "3364459"} {"correlation_id...php-app/php-app/f2dc7efdb4714f748a69327ebb9d90dc L2.uk&userId=24881200" 404 /home/jiminny/public/index.php 49.8... php-app/php-app/0d1f6209b8154f86b81acc932e95deb9 L2-3364459&userEmail=[EMAIL]&userId=2488120..php-app/php-app/0d1f6209b8154f86b81acc932e95deb9 L2access token request {"portalId": "3364459"} {"correlation_id...php-app/php-app/0d1f6209b8154f86b81acc932e95deb9364459&userEmail=[EMAIL]&userId=85823..php-app/php-app/9b5675687af240f5a35db483a751c030 Lt.co.ukauserld=85823914 404 /home/J1mlnny/publ1c/undex.php ..php-app/php-app/9b5675687af240f5a35db483a751c030access token request {"portalId": "3364459"} {"correlation_id...php-app/php-app/9b5675687af240f5a35db483a751c030 L64459&userEmail=[EMAIL]&userId=77621917 HTTP/1php-app/php-app/1ae5d5d18dd04e699bb44ea076fa41e9 LserId=77621917" 404 /home/iiminnv/oublic/index.oho 47.056 10php-app/php-app/1ae5d5d18dd04e699bb44ea076fa41e9 Laccess token request {"portalId":"3364459"} {"correlation_id...php-app/php-app/1ae5d5d18dd04e699bb44ea076fa41e9 L?o.uk&userId=86809575" 404 /home/jiminny/public/index.php 65.…php-app/php-app/e8c64b0049374a9a878046300888b638 L364459&userEmail=[EMAIL]&userId=86809575..php-app/php-app/e8c64b0049374a9a878046300888b638 L7occess token recuest portallo:3304459?3correlatzon-zo.php-app/php-app/e8c64b0063008886638 164459&userEmail=[EMAIL]&userId=86809575 .php-app/php-app/09de9de8c025499-1480dfc313a |Яo.uk&userId=86809575" 404 /home/jiminny/public/index.php 57.…..php-app/php-app/09de9de8c0254991af4051480dfc313a L?access token request {"portalId": "3364459"} {"correlation_id...php-app/php-app/09de9de8c0254991af4051480dfc313a L=33o445sxuseremoll=nonnon.cnlccevusensct.co.ukxuserl0=8o8vvs....php-app/php-app/e18f0a30a4294b3f846bfaafc243bce2 L2o.uk&userId=86809575" 404 /home/jiminny/public/index.php 58..php-app/php-app/e18f0a30a4294b3f846bfaafc243bce2 L?access token request {"portalId":"3364459"} {"correlation_id...php-app/php-app/e18f0a30a4294b3f846bfaafc243bce2 L64459&userEmail=[EMAIL]&userId=86809575 …php-app/php-app/8a6e72fb6a554411bd84b7ddOe4edbef L2o.uk&userId=86809575™ 404 /home/jiminny/public/index.php 49....php-app/php-app/8a6e72fb6a554411bd84b7ddOe4edbef L2access token request {"portalId": "3364459"} {"correlation_id...php-app/php-app/8a6e72fb6a554411bd84b7ddOe4edbefelog410346195943:php-app410346195943:php-app410346195943:php-app410346195943:php-app410346195943: php-app410346195943:php-app410346195943:php-app410346195943:php-app410346195943:php-app410346195943:php-app410346195943:php-app410346195943:php-app410346195943:php-app410346195943:php-app410346195943:php-app410346195943:php-app410346195943:php-app410346195943:php-app410346195943:php-app410346195943:php-app410346195943:php-app410346195943:010-000410346195943:php-app© 2026, Amazon Web Services, Inc. or its affiliates.PrivacyTermsCookie preferences...
|
NULL
|
|
42883
|
914
|
13
|
2026-04-17T07:42:17.351503+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776411737351_m1.jpg...
|
PhpStorm
|
faVsco.js – crm_configurations [EU]
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
305365489813454361
|
-4018125326210054687
|
app_switch
|
hybrid
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
iTerm2ShelliEditViewSessionScriptsProfilesWindowHelpBackend Chapter • 48 m left100% C47 8• Fri 17 Apr 10:42:16ec2-user@ip-10-20-6-111:~X41₴81DOCKER• ₴1DEV (-zsh)APP (-zsh)|X3-zsh• ₴5* Review screenp...• ₴6ec2-user@ip-10-30-...$7Days Active:3/3Daily Average: 265.33root@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook metrics -T 459 --from 2026-04-15 -DINFOManaging webhookmetrics for date range.Date RangeConfig IDIll Range SummaryDate RangeTotal DaysOldest Data AgeTotal WebhooksDaily AverageActive Companies#7 Daily Breakdown2026-04-15:335,647we2026-04-166712026.ДA-1258,35oksloksooks88 companies activePSpannestfttveConcails2026-04-15 to2026-04-173672026-04-15 to 2026-04-1732.0 days ago1,065,677355,225.6789NCompany 367 (SensatPhpStorm459)Total Webhooks: 796Days Active: 3/3Daily Average: 265.33company (114 total, avg: 38)association_change: 92 total, avg: 46, active: 2 dayscreation: 3 total, avg: 1.5, active: 2 daysproperty_change: 19 total, avg: 9.5, active: 2 daysUnique properties: 4Top properties: hubspot_owner_id(12), domain(3), name(3), phone(1)deal (164 total, avg: 54.67)property_change: 164 total, avg: 54.67, active: 3 daysUnique properties: 8Top properties: notes_last_updated(134), closedate(7), dealstage(5), hs_deal_stage_probability(5), hs_manual_forecast_category(5)contact (518 total, avg: 172.67)property_change:390 total, avg: 130, active: 3 daysUnique properties: 9Top properties: hubspot_owner_id(186), firstname(35), email(35), associatedcompanyid(33), country(33)creation: 36 total, avg: 18, active: 2 daysassociation_change: 92 total, avg: 46, active: 2 daysroote67e84f80b9d1:/home/jiminny# 0ec2-user@ip-10-20-...88...
|
NULL
|
|
42893
|
915
|
26
|
2026-04-17T07:43:07.926764+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776411787926_m2.jpg...
|
PhpStorm
|
faVsco.js – crm_configurations [EU]
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Component\ProphetAi\Exceptions\ProphetException;
use Jiminny\Component\ProphetAi\ProphetClient;
use Jiminny\Component\Queue\Constants;
use Jiminny\Exceptions\ApiResponseException;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Psr\Log\LoggerInterface;
use Throwable;
class RequestGenerateReportJob implements ShouldQueue, ShouldBeUnique
{
use InteractsWithQueue;
use Queueable;
/**
* Log prefix for all log messages from this job
*/
private const string LOG_PREFIX = '[Report:Generate]';
private const int MIN_ACTIVITIES_COUNT = 10;
/**
* The number of times the job may be attempted.
*
* @var int
*/
public int $tries = 2;
private readonly string $reportUuid;
private ?AutomatedReportResult $reportResult = null;
private ?AutomatedReportResult $reportResultPodcast = null;
public function __construct(string $reportUuid)
{
$this->reportUuid = $reportUuid;
$this->onQueue(Constants::QUEUE_ANALYTICS);
}
public function uniqueId(): string
{
return $this->reportUuid;
}
public function handle(
AutomatedReportsService $reportService,
ProphetClient $prophetClient,
LoggerInterface $logger
): void {
$logger->info(self::LOG_PREFIX . ' - Started', [
'automatedReportUuid' => $this->reportUuid,
]);
try {
$automatedReport = $reportService->getReport(uuid: $this->reportUuid);
if (! $this->validateReport($automatedReport, $logger)) {
return;
}
$this->createResults(automatedReport: $automatedReport, reportService: $reportService);
$payload = $reportService->getGenerateReportPayload(
automatedReport: $automatedReport,
reportResultUuid: $this->reportResult->getUuid()
);
$now = Carbon::now();
$this->reportResult->update([
'payload' => $payload,
'requested_at' => $now,
]);
if (isset($this->reportResultPodcast)) {
$this->reportResultPodcast->update([
'payload' => $payload,
'requested_at' => $now,
]);
}
if (! $this->checkActivityCount($prophetClient, $payload, $logger)) {
return;
}
$now = Carbon::now();
// send generate report request
$this->reportResult->update([
'name' => $reportService->getReportFileName($this->reportResult),
'status' => AutomatedReportResult::STATUS_REQUESTED,
'requested_at' => $now,
]);
if (isset($this->reportResultPodcast)) {
$this->reportResultPodcast->update([
'name' => $reportService->getReportFileName($this->reportResultPodcast),
'status' => AutomatedReportResult::STATUS_REQUESTED,
'requested_at' => $now,
]);
}
$logger->info(self::LOG_PREFIX . ' - Request sent', [
'automatedReportUuid' => $this->reportUuid,
'reportUuid' => $this->reportResult->getUuid(),
'payload' => $payload,
]);
$response = $prophetClient->sendRequest(
endpoint: ProphetClient::EXEC_REPORT,
requestArray: $payload
);
$logger->info(self::LOG_PREFIX . ' - Response received', ['response' => $response->getContent()]);
} catch (Throwable $exception) {
$reportUuid = null;
$reason = $exception instanceof ProphetException ?
AutomatedReportResult::REASON_PROPHET_API_ERROR : AutomatedReportResult::REASON_DEFAULT;
$this->failReport($reason);
$logger->error(
self::LOG_PREFIX . ' - Error',
[
'automatedReportUuid' => $this->reportUuid,
'reportUuid' => $reportUuid,
'code' => $exception->getCode(),
'message' => $exception->getMessage(),
]
);
if ($this->attempts() < $this->tries) {
$logger->info(self::LOG_PREFIX . ' - Retry scheduled', [
'attempts' => $this->attempts(),
]);
$this->release(30);
} else {
$this->fail($exception);
}
}
}
private function validateReport(AutomatedReport $automatedReport, LoggerInterface $logger): bool
{
if (! $automatedReport->getStatus()) {
$logger->info(self::LOG_PREFIX . ' - Skipped, report is not active', [
'automatedReportUuid' => $this->reportUuid,
]);
return false;
}
if ($automatedReport->getTeam()->getStatus() !== Team::STATUS_ACTIVE) {
$logger->info(self::LOG_PREFIX . ' - Skipped, team is inactive', [
'automatedReportUuid' => $this->reportUuid,
]);
return false;
}
return true;
}
private function createResults(
AutomatedReport $automatedReport,
AutomatedReportsService $reportService
): void {
$mediaTypes = $automatedReport->getMediaTypes();
// handle PDF or podcast
if (count($mediaTypes) === 1) {
$this->reportResult = $reportService->createReportResult(
automatedReport: $automatedReport,
data: [
'status' => AutomatedReportResult::STATUS_DEFAULT,
'media_type' => $mediaTypes[0],
]
);
return;
}
// handle multiple media types
// create PDF as primary result
$this->reportResult = $reportService->createReportResult(
automatedReport: $automatedReport,
data: [
'media_type' => AutomatedReportsService::MEDIA_TYPE_PDF,
]
);
if (in_array(AutomatedReportsService::MEDIA_TYPE_PODCAST, $mediaTypes, true)) {
$this->reportResultPodcast = $reportService->createReportResult(
automatedReport: $automatedReport,
data: [
'media_type' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'parent_id' => $this->reportResult->getId(),
]
);
}
}
private function checkActivityCount(ProphetClient $prophetClient, array $payload, LoggerInterface $logger): bool
{
$logger->info(self::LOG_PREFIX . ' - Request activities count', [
'automatedReportUuid' => $this->reportUuid,
'reportUuid' => $this->reportResult->getUuid(),
'payload' => $payload,
]);
// validate expected activities count before sending request
$response = $prophetClient->sendRequest(
endpoint: ProphetClient::EXEC_REPORT_COUNT,
requestArray: $payload
);
$content = $response->getContent();
if (! isset($content['response'])) {
throw new ApiResponseException('Error getting activities count');
}
$logger->info(self::LOG_PREFIX . ' - Get activities count', $content);
$count = (int) $content['response'];
if ($count < self::MIN_ACTIVITIES_COUNT) {
$this->failReport(AutomatedReportResult::REASON_NOT_ENOUGH_ACTIVITIES);
$logger->info(self::LOG_PREFIX . ' - Not enough activities, skipped', [
'automatedReportUuid' => $this->reportUuid,
'reportUuid' => $this->reportResult->getUuid(),
]);
return false;
}
return true;
}
private function failReport(int $reason): void
{
if (isset($this->reportResult)) {
$this->reportResult->update([
'status' => AutomatedReportResult::STATUS_FAILED,
'reason' => $reason,
]);
}
if (isset($this->reportResultPodcast)) {
$this->reportResultPodcast->update([
'status' => AutomatedReportResult::STATUS_FAILED,
'reason' => $reason,
]);
}
}
}
367
367
0
fa0cf643-d30e-43b8-ab3d-1fe18254609c
fa0cf643-d30e-43b8-ab3d-1fe18254609c
2026-04-17 07:14:23
459
459
0
<null>
<null>
0
hubspot
hubspot
2026-04-17 07:14:23
<null>
<null>
2026-04-17 07:14:23
<null>
<null>
2026-04-17 07:14:23
0
0
0
2
2
2026-04-17 07:14:23
<null>
<null>
2026-04-17 07:14:23
1
1...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.03046875,"top":0.017361112,"width":0.0453125,"height":0.022222223},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"bounds":{"left":0.07578125,"top":0.017361112,"width":0.14960937,"height":0.022222223},"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.78515625,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsCommandTest","depth":6,"bounds":{"left":0.803125,"top":0.017361112,"width":0.09765625,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsCommandTest'","depth":6,"bounds":{"left":0.9007813,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsCommandTest'","depth":6,"bounds":{"left":0.9140625,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9273437,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96015626,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9734375,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9867188,"top":0.017361112,"width":0.013281226,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.049609374,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.4265625,"top":0.19513889,"width":0.009375,"height":0.013194445},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.43789062,"top":0.19375,"width":0.00859375,"height":0.015972223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4464844,"top":0.19375,"width":0.008203125,"height":0.015972223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Contracts\\Queue\\ShouldBeUnique;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Illuminate\\Bus\\Queueable;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Jiminny\\Component\\ProphetAi\\Exceptions\\ProphetException;\nuse Jiminny\\Component\\ProphetAi\\ProphetClient;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Exceptions\\ApiResponseException;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\nclass RequestGenerateReportJob implements ShouldQueue, ShouldBeUnique\n{\n use InteractsWithQueue;\n use Queueable;\n\n /**\n * Log prefix for all log messages from this job\n */\n private const string LOG_PREFIX = '[Report:Generate]';\n\n private const int MIN_ACTIVITIES_COUNT = 10;\n\n /**\n * The number of times the job may be attempted.\n *\n * @var int\n */\n public int $tries = 2;\n\n private readonly string $reportUuid;\n private ?AutomatedReportResult $reportResult = null;\n private ?AutomatedReportResult $reportResultPodcast = null;\n\n public function __construct(string $reportUuid)\n {\n $this->reportUuid = $reportUuid;\n $this->onQueue(Constants::QUEUE_ANALYTICS);\n }\n\n public function uniqueId(): string\n {\n return $this->reportUuid;\n }\n\n public function handle(\n AutomatedReportsService $reportService,\n ProphetClient $prophetClient,\n LoggerInterface $logger\n ): void {\n $logger->info(self::LOG_PREFIX . ' - Started', [\n 'automatedReportUuid' => $this->reportUuid,\n ]);\n\n try {\n $automatedReport = $reportService->getReport(uuid: $this->reportUuid);\n\n if (! $this->validateReport($automatedReport, $logger)) {\n return;\n }\n\n $this->createResults(automatedReport: $automatedReport, reportService: $reportService);\n\n $payload = $reportService->getGenerateReportPayload(\n automatedReport: $automatedReport,\n reportResultUuid: $this->reportResult->getUuid()\n );\n\n $now = Carbon::now();\n\n $this->reportResult->update([\n 'payload' => $payload,\n 'requested_at' => $now,\n ]);\n if (isset($this->reportResultPodcast)) {\n $this->reportResultPodcast->update([\n 'payload' => $payload,\n 'requested_at' => $now,\n ]);\n }\n\n if (! $this->checkActivityCount($prophetClient, $payload, $logger)) {\n return;\n }\n\n $now = Carbon::now();\n\n // send generate report request\n $this->reportResult->update([\n 'name' => $reportService->getReportFileName($this->reportResult),\n 'status' => AutomatedReportResult::STATUS_REQUESTED,\n 'requested_at' => $now,\n ]);\n\n if (isset($this->reportResultPodcast)) {\n $this->reportResultPodcast->update([\n 'name' => $reportService->getReportFileName($this->reportResultPodcast),\n 'status' => AutomatedReportResult::STATUS_REQUESTED,\n 'requested_at' => $now,\n ]);\n }\n\n $logger->info(self::LOG_PREFIX . ' - Request sent', [\n 'automatedReportUuid' => $this->reportUuid,\n 'reportUuid' => $this->reportResult->getUuid(),\n 'payload' => $payload,\n ]);\n\n $response = $prophetClient->sendRequest(\n endpoint: ProphetClient::EXEC_REPORT,\n requestArray: $payload\n );\n $logger->info(self::LOG_PREFIX . ' - Response received', ['response' => $response->getContent()]);\n } catch (Throwable $exception) {\n $reportUuid = null;\n\n $reason = $exception instanceof ProphetException ?\n AutomatedReportResult::REASON_PROPHET_API_ERROR : AutomatedReportResult::REASON_DEFAULT;\n\n $this->failReport($reason);\n\n $logger->error(\n self::LOG_PREFIX . ' - Error',\n [\n 'automatedReportUuid' => $this->reportUuid,\n 'reportUuid' => $reportUuid,\n 'code' => $exception->getCode(),\n 'message' => $exception->getMessage(),\n ]\n );\n\n if ($this->attempts() < $this->tries) {\n $logger->info(self::LOG_PREFIX . ' - Retry scheduled', [\n 'attempts' => $this->attempts(),\n ]);\n\n $this->release(30);\n } else {\n $this->fail($exception);\n }\n }\n }\n\n private function validateReport(AutomatedReport $automatedReport, LoggerInterface $logger): bool\n {\n if (! $automatedReport->getStatus()) {\n $logger->info(self::LOG_PREFIX . ' - Skipped, report is not active', [\n 'automatedReportUuid' => $this->reportUuid,\n ]);\n\n return false;\n }\n\n if ($automatedReport->getTeam()->getStatus() !== Team::STATUS_ACTIVE) {\n $logger->info(self::LOG_PREFIX . ' - Skipped, team is inactive', [\n 'automatedReportUuid' => $this->reportUuid,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n private function createResults(\n AutomatedReport $automatedReport,\n AutomatedReportsService $reportService\n ): void {\n $mediaTypes = $automatedReport->getMediaTypes();\n\n // handle PDF or podcast\n if (count($mediaTypes) === 1) {\n $this->reportResult = $reportService->createReportResult(\n automatedReport: $automatedReport,\n data: [\n 'status' => AutomatedReportResult::STATUS_DEFAULT,\n 'media_type' => $mediaTypes[0],\n ]\n );\n\n return;\n }\n\n // handle multiple media types\n // create PDF as primary result\n $this->reportResult = $reportService->createReportResult(\n automatedReport: $automatedReport,\n data: [\n 'media_type' => AutomatedReportsService::MEDIA_TYPE_PDF,\n ]\n );\n\n if (in_array(AutomatedReportsService::MEDIA_TYPE_PODCAST, $mediaTypes, true)) {\n $this->reportResultPodcast = $reportService->createReportResult(\n automatedReport: $automatedReport,\n data: [\n 'media_type' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'parent_id' => $this->reportResult->getId(),\n ]\n );\n }\n }\n\n private function checkActivityCount(ProphetClient $prophetClient, array $payload, LoggerInterface $logger): bool\n {\n $logger->info(self::LOG_PREFIX . ' - Request activities count', [\n 'automatedReportUuid' => $this->reportUuid,\n 'reportUuid' => $this->reportResult->getUuid(),\n 'payload' => $payload,\n ]);\n // validate expected activities count before sending request\n $response = $prophetClient->sendRequest(\n endpoint: ProphetClient::EXEC_REPORT_COUNT,\n requestArray: $payload\n );\n $content = $response->getContent();\n\n if (! isset($content['response'])) {\n throw new ApiResponseException('Error getting activities count');\n }\n\n $logger->info(self::LOG_PREFIX . ' - Get activities count', $content);\n\n $count = (int) $content['response'];\n if ($count < self::MIN_ACTIVITIES_COUNT) {\n $this->failReport(AutomatedReportResult::REASON_NOT_ENOUGH_ACTIVITIES);\n\n $logger->info(self::LOG_PREFIX . ' - Not enough activities, skipped', [\n 'automatedReportUuid' => $this->reportUuid,\n 'reportUuid' => $this->reportResult->getUuid(),\n ]);\n\n return false;\n }\n\n return true;\n }\n\n private function failReport(int $reason): void\n {\n if (isset($this->reportResult)) {\n $this->reportResult->update([\n 'status' => AutomatedReportResult::STATUS_FAILED,\n 'reason' => $reason,\n ]);\n }\n\n if (isset($this->reportResultPodcast)) {\n $this->reportResultPodcast->update([\n 'status' => AutomatedReportResult::STATUS_FAILED,\n 'reason' => $reason,\n ]);\n }\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Contracts\\Queue\\ShouldBeUnique;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Illuminate\\Bus\\Queueable;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Jiminny\\Component\\ProphetAi\\Exceptions\\ProphetException;\nuse Jiminny\\Component\\ProphetAi\\ProphetClient;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Exceptions\\ApiResponseException;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\nclass RequestGenerateReportJob implements ShouldQueue, ShouldBeUnique\n{\n use InteractsWithQueue;\n use Queueable;\n\n /**\n * Log prefix for all log messages from this job\n */\n private const string LOG_PREFIX = '[Report:Generate]';\n\n private const int MIN_ACTIVITIES_COUNT = 10;\n\n /**\n * The number of times the job may be attempted.\n *\n * @var int\n */\n public int $tries = 2;\n\n private readonly string $reportUuid;\n private ?AutomatedReportResult $reportResult = null;\n private ?AutomatedReportResult $reportResultPodcast = null;\n\n public function __construct(string $reportUuid)\n {\n $this->reportUuid = $reportUuid;\n $this->onQueue(Constants::QUEUE_ANALYTICS);\n }\n\n public function uniqueId(): string\n {\n return $this->reportUuid;\n }\n\n public function handle(\n AutomatedReportsService $reportService,\n ProphetClient $prophetClient,\n LoggerInterface $logger\n ): void {\n $logger->info(self::LOG_PREFIX . ' - Started', [\n 'automatedReportUuid' => $this->reportUuid,\n ]);\n\n try {\n $automatedReport = $reportService->getReport(uuid: $this->reportUuid);\n\n if (! $this->validateReport($automatedReport, $logger)) {\n return;\n }\n\n $this->createResults(automatedReport: $automatedReport, reportService: $reportService);\n\n $payload = $reportService->getGenerateReportPayload(\n automatedReport: $automatedReport,\n reportResultUuid: $this->reportResult->getUuid()\n );\n\n $now = Carbon::now();\n\n $this->reportResult->update([\n 'payload' => $payload,\n 'requested_at' => $now,\n ]);\n if (isset($this->reportResultPodcast)) {\n $this->reportResultPodcast->update([\n 'payload' => $payload,\n 'requested_at' => $now,\n ]);\n }\n\n if (! $this->checkActivityCount($prophetClient, $payload, $logger)) {\n return;\n }\n\n $now = Carbon::now();\n\n // send generate report request\n $this->reportResult->update([\n 'name' => $reportService->getReportFileName($this->reportResult),\n 'status' => AutomatedReportResult::STATUS_REQUESTED,\n 'requested_at' => $now,\n ]);\n\n if (isset($this->reportResultPodcast)) {\n $this->reportResultPodcast->update([\n 'name' => $reportService->getReportFileName($this->reportResultPodcast),\n 'status' => AutomatedReportResult::STATUS_REQUESTED,\n 'requested_at' => $now,\n ]);\n }\n\n $logger->info(self::LOG_PREFIX . ' - Request sent', [\n 'automatedReportUuid' => $this->reportUuid,\n 'reportUuid' => $this->reportResult->getUuid(),\n 'payload' => $payload,\n ]);\n\n $response = $prophetClient->sendRequest(\n endpoint: ProphetClient::EXEC_REPORT,\n requestArray: $payload\n );\n $logger->info(self::LOG_PREFIX . ' - Response received', ['response' => $response->getContent()]);\n } catch (Throwable $exception) {\n $reportUuid = null;\n\n $reason = $exception instanceof ProphetException ?\n AutomatedReportResult::REASON_PROPHET_API_ERROR : AutomatedReportResult::REASON_DEFAULT;\n\n $this->failReport($reason);\n\n $logger->error(\n self::LOG_PREFIX . ' - Error',\n [\n 'automatedReportUuid' => $this->reportUuid,\n 'reportUuid' => $reportUuid,\n 'code' => $exception->getCode(),\n 'message' => $exception->getMessage(),\n ]\n );\n\n if ($this->attempts() < $this->tries) {\n $logger->info(self::LOG_PREFIX . ' - Retry scheduled', [\n 'attempts' => $this->attempts(),\n ]);\n\n $this->release(30);\n } else {\n $this->fail($exception);\n }\n }\n }\n\n private function validateReport(AutomatedReport $automatedReport, LoggerInterface $logger): bool\n {\n if (! $automatedReport->getStatus()) {\n $logger->info(self::LOG_PREFIX . ' - Skipped, report is not active', [\n 'automatedReportUuid' => $this->reportUuid,\n ]);\n\n return false;\n }\n\n if ($automatedReport->getTeam()->getStatus() !== Team::STATUS_ACTIVE) {\n $logger->info(self::LOG_PREFIX . ' - Skipped, team is inactive', [\n 'automatedReportUuid' => $this->reportUuid,\n ]);\n\n return false;\n }\n\n return true;\n }\n\n private function createResults(\n AutomatedReport $automatedReport,\n AutomatedReportsService $reportService\n ): void {\n $mediaTypes = $automatedReport->getMediaTypes();\n\n // handle PDF or podcast\n if (count($mediaTypes) === 1) {\n $this->reportResult = $reportService->createReportResult(\n automatedReport: $automatedReport,\n data: [\n 'status' => AutomatedReportResult::STATUS_DEFAULT,\n 'media_type' => $mediaTypes[0],\n ]\n );\n\n return;\n }\n\n // handle multiple media types\n // create PDF as primary result\n $this->reportResult = $reportService->createReportResult(\n automatedReport: $automatedReport,\n data: [\n 'media_type' => AutomatedReportsService::MEDIA_TYPE_PDF,\n ]\n );\n\n if (in_array(AutomatedReportsService::MEDIA_TYPE_PODCAST, $mediaTypes, true)) {\n $this->reportResultPodcast = $reportService->createReportResult(\n automatedReport: $automatedReport,\n data: [\n 'media_type' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'parent_id' => $this->reportResult->getId(),\n ]\n );\n }\n }\n\n private function checkActivityCount(ProphetClient $prophetClient, array $payload, LoggerInterface $logger): bool\n {\n $logger->info(self::LOG_PREFIX . ' - Request activities count', [\n 'automatedReportUuid' => $this->reportUuid,\n 'reportUuid' => $this->reportResult->getUuid(),\n 'payload' => $payload,\n ]);\n // validate expected activities count before sending request\n $response = $prophetClient->sendRequest(\n endpoint: ProphetClient::EXEC_REPORT_COUNT,\n requestArray: $payload\n );\n $content = $response->getContent();\n\n if (! isset($content['response'])) {\n throw new ApiResponseException('Error getting activities count');\n }\n\n $logger->info(self::LOG_PREFIX . ' - Get activities count', $content);\n\n $count = (int) $content['response'];\n if ($count < self::MIN_ACTIVITIES_COUNT) {\n $this->failReport(AutomatedReportResult::REASON_NOT_ENOUGH_ACTIVITIES);\n\n $logger->info(self::LOG_PREFIX . ' - Not enough activities, skipped', [\n 'automatedReportUuid' => $this->reportUuid,\n 'reportUuid' => $this->reportResult->getUuid(),\n ]);\n\n return false;\n }\n\n return true;\n }\n\n private function failReport(int $reason): void\n {\n if (isset($this->reportResult)) {\n $this->reportResult->update([\n 'status' => AutomatedReportResult::STATUS_FAILED,\n 'reason' => $reason,\n ]);\n }\n\n if (isset($this->reportResultPodcast)) {\n $this->reportResultPodcast->update([\n 'status' => AutomatedReportResult::STATUS_FAILED,\n 'reason' => $reason,\n ]);\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCell","text":"367","depth":6,"bounds":{"left":0.62226564,"top":0.16527778,"width":0.11757813,"height":0.018055556},"role_description":"cell"},{"role":"AXStaticText","text":"367","depth":7,"bounds":{"left":0.62226564,"top":0.16527778,"width":0.11757813,"height":0.018055556},"role_description":"text"},{"role":"AXTextArea","text":"0","depth":8,"bounds":{"left":0.23320313,"top":1.0,"width":0.115234375,"height":0.0},"value":"0","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCell","text":"fa0cf643-d30e-43b8-ab3d-1fe18254609c","depth":6,"bounds":{"left":0.62226564,"top":0.18402778,"width":0.11757813,"height":0.018055556},"role_description":"cell"},{"role":"AXStaticText","text":"fa0cf643-d30e-43b8-ab3d-1fe18254609c","depth":7,"bounds":{"left":0.62226564,"top":0.18402778,"width":0.11757813,"height":0.018055556},"role_description":"text"},{"role":"AXTextArea","text":"2026-04-17 07:14:23","depth":8,"bounds":{"left":0.23320313,"top":1.0,"width":0.115234375,"height":0.0},"value":"2026-04-17 07:14:23","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCell","text":"459","depth":6,"bounds":{"left":0.62226564,"top":0.20277777,"width":0.11757813,"height":0.018055556},"role_description":"cell"},{"role":"AXStaticText","text":"459","depth":7,"bounds":{"left":0.62226564,"top":0.20277777,"width":0.11757813,"height":0.018055556},"role_description":"text"},{"role":"AXTextArea","text":"0","depth":8,"bounds":{"left":0.23320313,"top":1.0,"width":0.115234375,"height":0.0},"value":"0","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCell","text":"<null>","depth":6,"bounds":{"left":0.62226564,"top":0.22152779,"width":0.11757813,"height":0.018055556},"role_description":"cell"},{"role":"AXStaticText","text":"<null>","depth":7,"bounds":{"left":0.62226564,"top":0.22152779,"width":0.11757813,"height":0.018055556},"role_description":"text"},{"role":"AXTextArea","text":"0","depth":8,"bounds":{"left":0.23320313,"top":1.0,"width":0.115234375,"height":0.0},"value":"0","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCell","text":"hubspot","depth":6,"bounds":{"left":0.62226564,"top":0.24027778,"width":0.11757813,"height":0.018055556},"role_description":"cell"},{"role":"AXStaticText","text":"hubspot","depth":7,"bounds":{"left":0.62226564,"top":0.24027778,"width":0.11757813,"height":0.018055556},"role_description":"text"},{"role":"AXTextArea","text":"2026-04-17 07:14:23","depth":8,"bounds":{"left":0.23320313,"top":1.0,"width":0.115234375,"height":0.0},"value":"2026-04-17 07:14:23","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCell","text":"<null>","depth":6,"bounds":{"left":0.62226564,"top":0.25902778,"width":0.11757813,"height":0.018055556},"role_description":"cell"},{"role":"AXStaticText","text":"<null>","depth":7,"bounds":{"left":0.62226564,"top":0.25902778,"width":0.11757813,"height":0.018055556},"role_description":"text"},{"role":"AXTextArea","text":"2026-04-17 07:14:23","depth":8,"bounds":{"left":0.23320313,"top":1.0,"width":0.115234375,"height":0.0},"value":"2026-04-17 07:14:23","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCell","text":"<null>","depth":6,"bounds":{"left":0.62226564,"top":0.2777778,"width":0.11757813,"height":0.018055556},"role_description":"cell"},{"role":"AXStaticText","text":"<null>","depth":7,"bounds":{"left":0.62226564,"top":0.2777778,"width":0.11757813,"height":0.018055556},"role_description":"text"},{"role":"AXTextArea","text":"2026-04-17 07:14:23","depth":8,"bounds":{"left":0.23320313,"top":1.0,"width":0.115234375,"height":0.0},"value":"2026-04-17 07:14:23","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCell","text":"0","depth":6,"bounds":{"left":0.62226564,"top":0.29652777,"width":0.11757813,"height":0.018055556},"role_description":"cell"},{"role":"AXStaticText","text":"0","depth":7,"bounds":{"left":0.62226564,"top":0.29652777,"width":0.11757813,"height":0.018055556},"role_description":"text"},{"role":"AXTextArea","text":"0","depth":8,"bounds":{"left":0.23320313,"top":1.0,"width":0.115234375,"height":0.0},"value":"0","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCell","text":"2","depth":6,"bounds":{"left":0.62226564,"top":0.31527779,"width":0.11757813,"height":0.018055556},"role_description":"cell"},{"role":"AXStaticText","text":"2","depth":7,"bounds":{"left":0.62226564,"top":0.31527779,"width":0.11757813,"height":0.018055556},"role_description":"text"},{"role":"AXTextArea","text":"2026-04-17 07:14:23","depth":8,"bounds":{"left":0.23320313,"top":1.0,"width":0.115234375,"height":0.0},"value":"2026-04-17 07:14:23","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCell","text":"<null>","depth":6,"bounds":{"left":0.62226564,"top":0.33402777,"width":0.11757813,"height":0.018055556},"role_description":"cell"},{"role":"AXStaticText","text":"<null>","depth":7,"bounds":{"left":0.62226564,"top":0.33402777,"width":0.11757813,"height":0.018055556},"role_description":"text"},{"role":"AXTextArea","text":"2026-04-17 07:14:23","depth":8,"bounds":{"left":0.23320313,"top":1.0,"width":0.115234375,"height":0.0},"value":"2026-04-17 07:14:23","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCell","text":"1","depth":6,"bounds":{"left":0.62226564,"top":0.35277778,"width":0.11757813,"height":0.018055556},"role_description":"cell"},{"role":"AXStaticText","text":"1","depth":7,"bounds":{"left":0.62226564,"top":0.35277778,"width":0.11757813,"height":0.018055556},"role_description":"text"}]...
|
4187842298995721417
|
-5975965832303121842
|
app_switch
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Component\ProphetAi\Exceptions\ProphetException;
use Jiminny\Component\ProphetAi\ProphetClient;
use Jiminny\Component\Queue\Constants;
use Jiminny\Exceptions\ApiResponseException;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Psr\Log\LoggerInterface;
use Throwable;
class RequestGenerateReportJob implements ShouldQueue, ShouldBeUnique
{
use InteractsWithQueue;
use Queueable;
/**
* Log prefix for all log messages from this job
*/
private const string LOG_PREFIX = '[Report:Generate]';
private const int MIN_ACTIVITIES_COUNT = 10;
/**
* The number of times the job may be attempted.
*
* @var int
*/
public int $tries = 2;
private readonly string $reportUuid;
private ?AutomatedReportResult $reportResult = null;
private ?AutomatedReportResult $reportResultPodcast = null;
public function __construct(string $reportUuid)
{
$this->reportUuid = $reportUuid;
$this->onQueue(Constants::QUEUE_ANALYTICS);
}
public function uniqueId(): string
{
return $this->reportUuid;
}
public function handle(
AutomatedReportsService $reportService,
ProphetClient $prophetClient,
LoggerInterface $logger
): void {
$logger->info(self::LOG_PREFIX . ' - Started', [
'automatedReportUuid' => $this->reportUuid,
]);
try {
$automatedReport = $reportService->getReport(uuid: $this->reportUuid);
if (! $this->validateReport($automatedReport, $logger)) {
return;
}
$this->createResults(automatedReport: $automatedReport, reportService: $reportService);
$payload = $reportService->getGenerateReportPayload(
automatedReport: $automatedReport,
reportResultUuid: $this->reportResult->getUuid()
);
$now = Carbon::now();
$this->reportResult->update([
'payload' => $payload,
'requested_at' => $now,
]);
if (isset($this->reportResultPodcast)) {
$this->reportResultPodcast->update([
'payload' => $payload,
'requested_at' => $now,
]);
}
if (! $this->checkActivityCount($prophetClient, $payload, $logger)) {
return;
}
$now = Carbon::now();
// send generate report request
$this->reportResult->update([
'name' => $reportService->getReportFileName($this->reportResult),
'status' => AutomatedReportResult::STATUS_REQUESTED,
'requested_at' => $now,
]);
if (isset($this->reportResultPodcast)) {
$this->reportResultPodcast->update([
'name' => $reportService->getReportFileName($this->reportResultPodcast),
'status' => AutomatedReportResult::STATUS_REQUESTED,
'requested_at' => $now,
]);
}
$logger->info(self::LOG_PREFIX . ' - Request sent', [
'automatedReportUuid' => $this->reportUuid,
'reportUuid' => $this->reportResult->getUuid(),
'payload' => $payload,
]);
$response = $prophetClient->sendRequest(
endpoint: ProphetClient::EXEC_REPORT,
requestArray: $payload
);
$logger->info(self::LOG_PREFIX . ' - Response received', ['response' => $response->getContent()]);
} catch (Throwable $exception) {
$reportUuid = null;
$reason = $exception instanceof ProphetException ?
AutomatedReportResult::REASON_PROPHET_API_ERROR : AutomatedReportResult::REASON_DEFAULT;
$this->failReport($reason);
$logger->error(
self::LOG_PREFIX . ' - Error',
[
'automatedReportUuid' => $this->reportUuid,
'reportUuid' => $reportUuid,
'code' => $exception->getCode(),
'message' => $exception->getMessage(),
]
);
if ($this->attempts() < $this->tries) {
$logger->info(self::LOG_PREFIX . ' - Retry scheduled', [
'attempts' => $this->attempts(),
]);
$this->release(30);
} else {
$this->fail($exception);
}
}
}
private function validateReport(AutomatedReport $automatedReport, LoggerInterface $logger): bool
{
if (! $automatedReport->getStatus()) {
$logger->info(self::LOG_PREFIX . ' - Skipped, report is not active', [
'automatedReportUuid' => $this->reportUuid,
]);
return false;
}
if ($automatedReport->getTeam()->getStatus() !== Team::STATUS_ACTIVE) {
$logger->info(self::LOG_PREFIX . ' - Skipped, team is inactive', [
'automatedReportUuid' => $this->reportUuid,
]);
return false;
}
return true;
}
private function createResults(
AutomatedReport $automatedReport,
AutomatedReportsService $reportService
): void {
$mediaTypes = $automatedReport->getMediaTypes();
// handle PDF or podcast
if (count($mediaTypes) === 1) {
$this->reportResult = $reportService->createReportResult(
automatedReport: $automatedReport,
data: [
'status' => AutomatedReportResult::STATUS_DEFAULT,
'media_type' => $mediaTypes[0],
]
);
return;
}
// handle multiple media types
// create PDF as primary result
$this->reportResult = $reportService->createReportResult(
automatedReport: $automatedReport,
data: [
'media_type' => AutomatedReportsService::MEDIA_TYPE_PDF,
]
);
if (in_array(AutomatedReportsService::MEDIA_TYPE_PODCAST, $mediaTypes, true)) {
$this->reportResultPodcast = $reportService->createReportResult(
automatedReport: $automatedReport,
data: [
'media_type' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'parent_id' => $this->reportResult->getId(),
]
);
}
}
private function checkActivityCount(ProphetClient $prophetClient, array $payload, LoggerInterface $logger): bool
{
$logger->info(self::LOG_PREFIX . ' - Request activities count', [
'automatedReportUuid' => $this->reportUuid,
'reportUuid' => $this->reportResult->getUuid(),
'payload' => $payload,
]);
// validate expected activities count before sending request
$response = $prophetClient->sendRequest(
endpoint: ProphetClient::EXEC_REPORT_COUNT,
requestArray: $payload
);
$content = $response->getContent();
if (! isset($content['response'])) {
throw new ApiResponseException('Error getting activities count');
}
$logger->info(self::LOG_PREFIX . ' - Get activities count', $content);
$count = (int) $content['response'];
if ($count < self::MIN_ACTIVITIES_COUNT) {
$this->failReport(AutomatedReportResult::REASON_NOT_ENOUGH_ACTIVITIES);
$logger->info(self::LOG_PREFIX . ' - Not enough activities, skipped', [
'automatedReportUuid' => $this->reportUuid,
'reportUuid' => $this->reportResult->getUuid(),
]);
return false;
}
return true;
}
private function failReport(int $reason): void
{
if (isset($this->reportResult)) {
$this->reportResult->update([
'status' => AutomatedReportResult::STATUS_FAILED,
'reason' => $reason,
]);
}
if (isset($this->reportResultPodcast)) {
$this->reportResultPodcast->update([
'status' => AutomatedReportResult::STATUS_FAILED,
'reason' => $reason,
]);
}
}
}
367
367
0
fa0cf643-d30e-43b8-ab3d-1fe18254609c
fa0cf643-d30e-43b8-ab3d-1fe18254609c
2026-04-17 07:14:23
459
459
0
<null>
<null>
0
hubspot
hubspot
2026-04-17 07:14:23
<null>
<null>
2026-04-17 07:14:23
<null>
<null>
2026-04-17 07:14:23
0
0
0
2
2
2026-04-17 07:14:23
<null>
<null>
2026-04-17 07:14:23
1
1...
|
NULL
|
|
42894
|
914
|
17
|
2026-04-17T07:43:07.918427+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776411787918_m1.jpg...
|
PhpStorm
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
iTerm2ShellEdit|ViewSessionScriptsProfilesWindowHe iTerm2ShellEdit|ViewSessionScriptsProfilesWindowHelpec2-user@ip-10-20-6-111:~X4lanl• ₴5• Backend Chapter • 47 m left100% 147 8• Fri 17 Apr 10:43:07DOCKER881DEV (-zsh)APP (-zsh)X3-zshDays Active: 3/3Daily Average: 265.33root@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook metrics -T 459 --from 2026-04-15 -DINFOManaging webhookmetrics for date range.Date RangeConfig IDIll Range SummaryDate RangeTotal DaysOldest Data AgeTotal WebhooksDaily AverageActive Companies* Review screenp...• ₴6ec2-user@ip-10-30-...$7ec2-user@ip-10-20-...1₴81882026-04-15 to2026-04-173672026-04-15 to 2026-04-1732.0 days ago1,065,677355,225.6789NPS$I31PhpStormTotal Webhooks: 796Days Active: 3/3Daily Average: 265.33company (114 total, avg:38)association_change: 92 total, avg:46, active: 2 dayscreation: 3 total, avg: 1.5, active: 2 daysproperty_change: 19 total, avg: 9.5, active: 2 daysUnique properties: 4Top properties: hubspot_owner_id(12), domain(3), name(3), phone(1)deal (164 total, avg: 54.67)property_change: 164 total, avg: 54.67, active: 3 daysUnique properties: 8Top properties: notes_last_updated(134), closedate(7), dealstage(5), hs_deal_stage_probability(5), hs_manual_forecast_category(5)contact (518 total, avg: 172.67)property_change:390 total, avg: 130, active: 3 daysUnique properties: 9Top properties: hubspot_owner_id(186), firstname(35), email(35), associatedcompanyid(33), country(33)creation: 36 total, avg: 18, active: 2 daysassociation_change: 92 total, avg: 46, active: 2 daysroote67e84f80b9d1:/home/jiminny# |...
|
NULL
|
8967416251005403036
|
NULL
|
app_switch
|
ocr
|
NULL
|
iTerm2ShellEdit|ViewSessionScriptsProfilesWindowHe iTerm2ShellEdit|ViewSessionScriptsProfilesWindowHelpec2-user@ip-10-20-6-111:~X4lanl• ₴5• Backend Chapter • 47 m left100% 147 8• Fri 17 Apr 10:43:07DOCKER881DEV (-zsh)APP (-zsh)X3-zshDays Active: 3/3Daily Average: 265.33root@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook metrics -T 459 --from 2026-04-15 -DINFOManaging webhookmetrics for date range.Date RangeConfig IDIll Range SummaryDate RangeTotal DaysOldest Data AgeTotal WebhooksDaily AverageActive Companies* Review screenp...• ₴6ec2-user@ip-10-30-...$7ec2-user@ip-10-20-...1₴81882026-04-15 to2026-04-173672026-04-15 to 2026-04-1732.0 days ago1,065,677355,225.6789NPS$I31PhpStormTotal Webhooks: 796Days Active: 3/3Daily Average: 265.33company (114 total, avg:38)association_change: 92 total, avg:46, active: 2 dayscreation: 3 total, avg: 1.5, active: 2 daysproperty_change: 19 total, avg: 9.5, active: 2 daysUnique properties: 4Top properties: hubspot_owner_id(12), domain(3), name(3), phone(1)deal (164 total, avg: 54.67)property_change: 164 total, avg: 54.67, active: 3 daysUnique properties: 8Top properties: notes_last_updated(134), closedate(7), dealstage(5), hs_deal_stage_probability(5), hs_manual_forecast_category(5)contact (518 total, avg: 172.67)property_change:390 total, avg: 130, active: 3 daysUnique properties: 9Top properties: hubspot_owner_id(186), firstname(35), email(35), associatedcompanyid(33), country(33)creation: 36 total, avg: 18, active: 2 daysassociation_change: 92 total, avg: 46, active: 2 daysroote67e84f80b9d1:/home/jiminny# |...
|
NULL
|
|
42895
|
915
|
27
|
2026-04-17T07:43:08.616465+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776411788616_m2.jpg...
|
Firefox
|
Meet - Backend Chapter — Work
|
1
|
meet.google.com/gjc-ikxu-wxu?authuser=lukas.kovali meet.google.com/gjc-ikxu-wxu?authuser=lukas.kovalik%40jiminny.com...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
Problem loading page
Problem loading page
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Symfony\Component\Debug\Exception\FatalThrowableError: 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
CloudWatch | us-east-2
CloudWatch | us-east-2
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
Console Home | Console Home | eu-west-1
Console Home | Console Home | eu-west-1
New Tab
New Tab
Meet - Backend Chapter
Mute tab
Meet - Backend Chapter
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Nikolay Nikolov (Presenting, annotating)
Nikolay Nikolov (Presenting, annotating)
People
3
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Unpin Nikolay Nikolov's presentation from your main screen
You can't unmute someone else's presentation
More options for Nikolay Nikolov
Zoom in
Open in new window
Enter Full Screen
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Pin Nikolay Nikolov to your main screen
Mute Nikolay Nikolov's microphone
More options for Nikolay Nikolov
Nikolay Nikolov
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
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:43
AM
Backend Chapter
Backend Chapter
Audio settings
Turn off microphone
Video settings
Turn off camera
Nikolay Nikolov is presenting
Send a reaction
Turn on captions
Raise hand (ctrl + ⌘ + h)
More options
Leave call
Meeting details
Chat with everyone
Meeting tools
The presentation by Nikolay Nikolov was added to the main screen. The presentation by Nikolay Nikolov is on the main screen....
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"bounds":{"left":0.00234375,"top":0.045138888,"width":0.0890625,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.08263889,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.015625,"top":0.09236111,"width":0.11875,"height":0.009722223},"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.11111111,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":5,"bounds":{"left":0.015625,"top":0.12083333,"width":0.11171875,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Problem loading page","depth":4,"bounds":{"left":0.0,"top":0.13958333,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Problem loading page","depth":5,"bounds":{"left":0.015625,"top":0.14930555,"width":0.04453125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Symfony\\Component\\Debug\\Exception\\FatalThrowableError: 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","depth":4,"bounds":{"left":0.0,"top":0.16805555,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Symfony\\Component\\Debug\\Exception\\FatalThrowableError: 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","depth":5,"bounds":{"left":0.015625,"top":0.17777778,"width":0.53398436,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0,"top":0.19652778,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015625,"top":0.20625,"width":0.0484375,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.225,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":5,"bounds":{"left":0.015625,"top":0.23472223,"width":0.1515625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Console Home | Console Home | eu-west-1","depth":4,"bounds":{"left":0.0,"top":0.2534722,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Console Home | Console Home | eu-west-1","depth":5,"bounds":{"left":0.015625,"top":0.26319444,"width":0.0875,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.28194445,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.015625,"top":0.29166666,"width":0.017578125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Meet - Backend Chapter","depth":4,"bounds":{"left":0.0,"top":0.31041667,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXButton","text":"Mute tab","depth":5,"bounds":{"left":0.01328125,"top":0.31666666,"width":0.009375,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Meet - Backend Chapter","depth":5,"bounds":{"left":0.0234375,"top":0.3201389,"width":0.05,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.07890625,"top":0.31666666,"width":0.009375,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.003125,"top":0.3402778,"width":0.08710937,"height":0.022222223},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.003125,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.01640625,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.029296875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.0421875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.05546875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Nikolay Nikolov (Presenting, annotating)","depth":12,"bounds":{"left":0.1171875,"top":0.063194446,"width":0.10078125,"height":0.013888889},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Nikolov (Presenting, annotating)","depth":13,"bounds":{"left":0.1171875,"top":0.06388889,"width":0.10078125,"height":0.013194445},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"People","depth":15,"bounds":{"left":0.93671876,"top":0.05625,"width":0.023046875,"height":0.025},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"3","depth":22,"bounds":{"left":0.95234376,"top":0.063194446,"width":0.002734375,"height":0.011111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Take notes with Gemini","depth":14,"bounds":{"left":0.9628906,"top":0.05625,"width":0.0140625,"height":0.025},"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":17,"bounds":{"left":0.9644531,"top":0.063194446,"width":0.0355469,"height":0.011111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini","depth":22,"bounds":{"left":0.98164064,"top":0.063194446,"width":0.016015625,"height":0.011111111},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Gemini","depth":21,"bounds":{"left":0.98046875,"top":0.056944445,"width":0.01328125,"height":0.023611112},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"bounds":{"left":0.6652344,"top":0.62083334,"width":0.0828125,"height":0.05625},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"bounds":{"left":0.7464844,"top":0.6298611,"width":0.045703124,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"bounds":{"left":0.73398435,"top":0.62777776,"width":0.0625,"height":0.035416666},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Unpin Nikolay Nikolov's presentation from your main screen","depth":13,"bounds":{"left":0.40234375,"top":0.50555557,"width":0.015625,"height":0.027777778},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"You can't unmute someone else's presentation","depth":13,"bounds":{"left":0.41796875,"top":0.50416666,"width":0.0171875,"height":0.030555556},"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":13,"bounds":{"left":0.43515626,"top":0.50555557,"width":0.015625,"height":0.027777778},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Zoom in","depth":13,"bounds":{"left":0.69453126,"top":0.8090278,"width":0.015625,"height":0.027777778},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open in new window","depth":13,"bounds":{"left":0.7132813,"top":0.8090278,"width":0.015625,"height":0.027777778},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Enter Full Screen","depth":13,"bounds":{"left":0.7320312,"top":0.8090278,"width":0.015625,"height":0.027777778},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"bounds":{"left":0.99960935,"top":0.36041668,"width":0.00039064884,"height":0.05625},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"bounds":{"left":1.0,"top":0.36944443,"width":-0.08085942,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"bounds":{"left":1.0,"top":0.3673611,"width":-0.068359375,"height":0.035416666},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Pin Nikolay Nikolov to your main screen","depth":13,"bounds":{"left":0.85234374,"top":0.29097223,"width":0.015625,"height":0.027777778},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Mute Nikolay Nikolov's microphone","depth":13,"bounds":{"left":0.86796874,"top":0.28958333,"width":0.0171875,"height":0.030555556},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"More options for Nikolay Nikolov","depth":13,"bounds":{"left":0.8851563,"top":0.29097223,"width":0.015625,"height":0.027777778},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Nikolay Nikolov","depth":17,"bounds":{"left":0.76484376,"top":0.49027777,"width":0.044140626,"height":0.014583333},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"bounds":{"left":0.6699219,"top":0.7895833,"width":0.0828125,"height":0.05625},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"bounds":{"left":0.62578124,"top":0.7986111,"width":0.045703124,"height":0.011805556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"bounds":{"left":0.6214844,"top":0.7965278,"width":0.0625,"height":0.035416666},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"You’re continuously framed","depth":13,"bounds":{"left":0.8515625,"top":0.71875,"width":0.0171875,"height":0.030555556},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Backgrounds and effects","depth":13,"bounds":{"left":0.86875,"top":0.71875,"width":0.0171875,"height":0.030555556},"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":13,"bounds":{"left":0.8859375,"top":0.7201389,"width":0.015625,"height":0.027777778},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Lukas Kovalik","depth":17,"bounds":{"left":0.76484376,"top":0.91944444,"width":0.038671874,"height":0.014583333},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Others might see more of your background. Click to view your full video.","depth":14,"bounds":{"left":0.9785156,"top":0.91736114,"width":0.0109375,"height":0.019444445},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"10:43","depth":12,"bounds":{"left":0.103125,"top":0.9652778,"width":0.016015625,"height":0.014583333},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AM","depth":12,"bounds":{"left":0.12109375,"top":0.9652778,"width":0.009765625,"height":0.014583333},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Backend Chapter","depth":12,"bounds":{"left":0.140625,"top":0.9444444,"width":0.05078125,"height":0.055555556},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Backend Chapter","depth":15,"bounds":{"left":0.140625,"top":0.9652778,"width":0.05078125,"height":0.014583333},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Audio settings","depth":13,"bounds":{"left":0.43710938,"top":0.95555556,"width":0.034375,"height":0.033333335},"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":13,"bounds":{"left":0.45273438,"top":0.95555556,"width":0.01875,"height":0.033333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Video settings","depth":13,"bounds":{"left":0.47460938,"top":0.95555556,"width":0.034375,"height":0.033333335},"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":13,"bounds":{"left":0.49023438,"top":0.95555556,"width":0.01875,"height":0.033333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Nikolay Nikolov is presenting","depth":12,"bounds":{"left":0.5121094,"top":0.95555556,"width":0.021875,"height":0.033333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Send a reaction","depth":12,"bounds":{"left":0.5371094,"top":0.95555556,"width":0.021875,"height":0.033333335},"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":13,"bounds":{"left":0.56210935,"top":0.95555556,"width":0.021875,"height":0.033333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Raise hand (ctrl + ⌘ + h)","depth":12,"bounds":{"left":0.5871094,"top":0.95555556,"width":0.021875,"height":0.033333335},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"More options","depth":12,"bounds":{"left":0.61210936,"top":0.95555556,"width":0.0140625,"height":0.033333335},"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":12,"bounds":{"left":0.6292969,"top":0.95555556,"width":0.028125,"height":0.033333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Meeting details","depth":12,"bounds":{"left":0.9394531,"top":0.95555556,"width":0.01875,"height":0.033333335},"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":12,"bounds":{"left":0.95820314,"top":0.95555556,"width":0.01875,"height":0.033333335},"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":12,"bounds":{"left":0.97695315,"top":0.95555556,"width":0.01875,"height":0.033333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"The presentation by Nikolay Nikolov was added to the main screen. The presentation by Nikolay Nikolov is on the main screen.","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-7901604361214333900
|
-5300614520125555920
|
app_switch
|
accessibility
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
Problem loading page
Problem loading page
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Symfony\Component\Debug\Exception\FatalThrowableError: 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
CloudWatch | us-east-2
CloudWatch | us-east-2
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
Console Home | Console Home | eu-west-1
Console Home | Console Home | eu-west-1
New Tab
New Tab
Meet - Backend Chapter
Mute tab
Meet - Backend Chapter
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Nikolay Nikolov (Presenting, annotating)
Nikolay Nikolov (Presenting, annotating)
People
3
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Unpin Nikolay Nikolov's presentation from your main screen
You can't unmute someone else's presentation
More options for Nikolay Nikolov
Zoom in
Open in new window
Enter Full Screen
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Pin Nikolay Nikolov to your main screen
Mute Nikolay Nikolov's microphone
More options for Nikolay Nikolov
Nikolay Nikolov
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
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:43
AM
Backend Chapter
Backend Chapter
Audio settings
Turn off microphone
Video settings
Turn off camera
Nikolay Nikolov is presenting
Send a reaction
Turn on captions
Raise hand (ctrl + ⌘ + h)
More options
Leave call
Meeting details
Chat with everyone
Meeting tools
The presentation by Nikolay Nikolov was added to the main screen. The presentation by Nikolay Nikolov is on the main screen....
|
NULL
|
|
42898
|
914
|
19
|
2026-04-17T07:43:12.701733+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776411792701_m1.jpg...
|
PhpStorm
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Term2ShellEditViewSessionScriptsProfilesWindowHelp Term2ShellEditViewSessionScriptsProfilesWindowHelpmeet.google.com/gjc-ikxu-wxu?authuser=lukas.kovalik%40jiminny.comNikolay Nikolov (Presenting, annotating)• Backend Chapter • 47 m left100% C8 • Fri 17 Apr 10:43:123Platform Sprint 2 Q2 - Platform Tea[SRD-6793] Les Mills activity typeProblem loading pageSymfony|Component|Debug\ExcepCloudWatch | us-east-2Configure SSH access to multiple eConsole Home | Console Home | elNew Tab•i Meet - Backend Chapter+ New TabG) Dacab X8 ProjesSOL Edtor Defabare_ Widon, JetoAло0'O Amoie bmmy: d.ae49[ *<PR00 US» jminey_nik(Jocal. sal x72nt:2 50 50 E-- 1872|-- 2026-04-16 16:00:23select * fron failed_jobs fj where fj-queue Like 'Nanalytics_low' order by id desc;REPORTSselect • fron opportunities where id = 7594349;select * fron opportunity_stages os where os-opportunity_id =7594349 and os.created_at > ^2026-04-17 04:26:31l order by os.created_at asc:Seon oppordunftteld, o-user_id, Outean,id, Uonare as cuner-nano, uostatus as ouner statusLEFT J0IN users u ON o.user_id = u.idMHERE o.wuid*8е9bbбdс-с6db-434d-b42b-c80609756986*;ee lat, aae, viow.setvittes.ol.seactivate.ssersepportunity stages tortunityc.jdNikolay Nikolov2026-04-16 10:16-54(2026-04-17 04-26116,352sartwottdt2012026-04-17 0429-2026-04-17 04-29-37- 0.093s (0.000s fetch), on 2026-04-17 at 1042 181080: 112:7902eSet 010Lukas Kovalik10:43 AM | Backend Chapter...
|
NULL
|
-2641296972850722359
|
NULL
|
app_switch
|
ocr
|
NULL
|
Term2ShellEditViewSessionScriptsProfilesWindowHelp Term2ShellEditViewSessionScriptsProfilesWindowHelpmeet.google.com/gjc-ikxu-wxu?authuser=lukas.kovalik%40jiminny.comNikolay Nikolov (Presenting, annotating)• Backend Chapter • 47 m left100% C8 • Fri 17 Apr 10:43:123Platform Sprint 2 Q2 - Platform Tea[SRD-6793] Les Mills activity typeProblem loading pageSymfony|Component|Debug\ExcepCloudWatch | us-east-2Configure SSH access to multiple eConsole Home | Console Home | elNew Tab•i Meet - Backend Chapter+ New TabG) Dacab X8 ProjesSOL Edtor Defabare_ Widon, JetoAло0'O Amoie bmmy: d.ae49[ *<PR00 US» jminey_nik(Jocal. sal x72nt:2 50 50 E-- 1872|-- 2026-04-16 16:00:23select * fron failed_jobs fj where fj-queue Like 'Nanalytics_low' order by id desc;REPORTSselect • fron opportunities where id = 7594349;select * fron opportunity_stages os where os-opportunity_id =7594349 and os.created_at > ^2026-04-17 04:26:31l order by os.created_at asc:Seon oppordunftteld, o-user_id, Outean,id, Uonare as cuner-nano, uostatus as ouner statusLEFT J0IN users u ON o.user_id = u.idMHERE o.wuid*8е9bbбdс-с6db-434d-b42b-c80609756986*;ee lat, aae, viow.setvittes.ol.seactivate.ssersepportunity stages tortunityc.jdNikolay Nikolov2026-04-16 10:16-54(2026-04-17 04-26116,352sartwottdt2012026-04-17 0429-2026-04-17 04-29-37- 0.093s (0.000s fetch), on 2026-04-17 at 1042 181080: 112:7902eSet 010Lukas Kovalik10:43 AM | Backend Chapter...
|
NULL
|
|
42901
|
915
|
30
|
2026-04-17T07:43:16.324549+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776411796324_m2.jpg...
|
PhpStorm
|
faVsco.js – crm_configurations [EU]
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.03046875,"top":0.017361112,"width":0.0453125,"height":0.022222223},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"bounds":{"left":0.07578125,"top":0.017361112,"width":0.14960937,"height":0.022222223},"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.78515625,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsCommandTest","depth":6,"bounds":{"left":0.803125,"top":0.017361112,"width":0.09765625,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsCommandTest'","depth":6,"bounds":{"left":0.9007813,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsCommandTest'","depth":6,"bounds":{"left":0.9140625,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-482216603274606444
|
1760908322497348928
|
app_switch
|
hybrid
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
PhpStormFileFditViewNavigateCodelaraveRefactonToolsWindowHelpFV ravsco.is#11894 on.lY-18909-automated-renorts-ask-liminnvProject v• dependency-checker.isonU dev.ison=ids.txt=infection.ison.distM+ INSTALL.mdM+ INTERNAL_WEBHOOK SETUPEjiminny_storageM1 licenses.moMI Makerileu package-lock.jsonE phostan.neon.distE phostan-baseline.neon<> phpunit.xmlle raw sal query.salM+ README.mdợ sonar-project.properties= test.py‹> Untitled Diagram.xmlus vetur.config.jsM.WEbHOOOK FILIERING IMPUE> rh External Librariesv = Scratches and Consolesv M Database ConsolesV AEU& console EUl4 DEAL RISKS EUIADI EUIA EU [EU]vd iminny@localhostd console liminny@localfd Di Liminny@localnost]4 HS_local [jiminny@locald sr yiminny@localhost)V APRODe zono_dev yiminny@loce4 console [PRODIL console 1 PRODIA DI PRODI> LQA› L QAi> L QAI PRODV & STAGNG& console S AGINGI¿ consoe AGINGI& uranus s AGINGIM Extensions• M Scratches= phostorm shortcuts.txtscratch.txtUr scratch 1.isonU scratch_2.sonU scratch_3.sorE scratch 4.txtphp scratch_5.phpphy scratch 6.phpir scratch_7.jsonIr stage2.ison= test</test.htmlC AutomatedReportsService.phpC) SendReportJob.phpC SendReportMailJob.php(©) ReportController.phplokenbullaer.onoC TeamSetupController.phppnp apl.ono1 Filesystem.pnpC AutomatedReportsCommand.pnp© AsKJiminnyReportsController.phpC AutomatedReportsCommandTest.php© AutomatedReportsSendCommand.phpC Team.php(C AutomatedReportsRepository.php© CreateHeldActivityEvent.php©) TrackProviderInstalledEvent.phpC CreateActivityLoggedEvent.php© UserPilotActivityListener.phpActivityLoagea.onpAutomatedRenortscallbackService.ono© RequestGenerateAskJiminnyReportJob.php© RequestGenerateReportJob.php x(C AutomatedReportResult.php(C AutomatedReport.phpclass RequestGenerateReportJob implements ShouldQueue, ShouldBeUniqueB2AY1931Y4199209210211213447200259260261262263private function createResultsohandle multiple media types// create PDF as primary resultsth1s->reportResult = sreportservice->createReportResultautomatedReport: sautomatedReport,data: l"media_type" => Automatedkeporusservice:.MEULA_lYPE_PUr,if Gin arravo needle: AutomatedReportsService::MEDTA TYPE PODCAST. SmediaTvpes..strict: true))sthis->reportResultPodcast = SreportService->createReportResultautomatedReport: $automatedReport.data:'medla_type' => AutomatedReportsService::MEDIA_TYPE_PODCAST.'parent_1d => Sthis->reportResult->getido,usaoeorivate Tunccion checkaccivicylount rroonecullent soropnecullent, array soayloaa,LoccerintertaceLusacesprivate function failReport(int $reason): void1f (1sset(sthis->reportResult)) isthis->reportresult->update(L"suacus = AucomatedkeporckesULt:.SIATUS_FALLEU."reason" = sreason,if isset(sthis->reportResultPodcast ‹sthis->reportResultPodcast->updated'status' => AutomatedReportResult::STATUS_ FATLED.'reason' = sreason,J9H: Backend Chanter . 47 m lefi100% z• Fri 17 Apr 10:43:15= custom.log= laravel.loge SF (iminny@localhostU scratch_1.isonV connect.vueV Onboard.vueHs local liminnyalocalnost< console LUi(iii crm configurations [EU] X¿ console IPRODIe console [STAGINGrow vX:Auto vDDLY- WHERE id = 367E- ORDER BY367Iuuid UUID with time-low and time-high swapped) ÷fa0cf643-d30e-43b8-ab3d-1fe1825460oe team_1dLonotifiable_user_idproviderI• editionW instance!_ is_sandboxI versionD installed_app_version•_ sync_metadatasync objectsauto svnc activity0 crm_provider_idW crm_base_uru• last synced atI leads svnced atMaccounts sunced atШ contacts_synced_atopportunities synced_atIn contact roles synced atw over_quota_atW ap1_disabled_atsettings! opportunity_assignment_rule! opportunity_max_value•_ opportunity_max_ageILo opportunity value field id! trigger_assignment_ruleso_ score_transcripu•_ softphone_override_prospect• default currencyIn created atW updacea_au459<null>hubspotSULL<nul<hULl3364459Cascade & 7lsensat..co2026-04-17 07:14:23ShULL<hU LE<nili><null>SnULl<hU LE<null>"opportunity sync_ period":"om","oopen-recently-upaaced6958000.0037K191ShULLe2025-04-02 11:33:462026-04-17 07:14-23CascadeAutomated Report Retishow me exactyl where do we set result as sent or failedThere are 3 places total:searcheastatus. FAILED and perormedl other queryFocusina onlv on AutomatedReportResult status transitions.STATUS SENT - set in SendReportMailJob after the email is delivered:M SendReportMailJob.php:135-14g1t srenort-sstatus le= AutomaredRenortResult.STATIS SENIT*cache:: lock("report-status-i$this->reportUuid,, 5)->block(2, function () use (Sreport) ‹prevort-ruvualerl'status' = AutomatedReportResult::STATUS SENT,"sent_at = nowl),STATUS_FAILED - set in 3 places:1. SendReportJob::failed() - when the dispatch/pre-send phase tails terminally (null path, Semissing, etc.):nort.00.nhn:175-17eCommand 98-únction failed (Throwable Sexception): voidAutomatedReportResult::whereRaw('uuid = uuid to bin(?)', [$this→>reportUuidl)→updatell status → AutomatedneporcResuLCi.SlAlUSFALLeOIe2. RequestGenerateReportJob - when report generation fails (before it even reaches sending):• RequestGenerateReportJob.php:252-264$this->reportResult->update(["status = Automatedkeporckesult::sIAlUs_FALLED,'reason" = $reason,J9HRequestcenerateaskaminnvkevorto —same, Tor ASk Jiminny vartant• RequestGenerateAskJiminnyReportJob.php:203-207sunus-> reportresursuodarel'status' = AutomatedReportResult::STATUS_FATLED,'reason" = $reason,J9HWnal "1 file +16>Ask anything (&+L)+ <> Code Claude Sonnet 4.6Reject allAccept allWinasun leams...
|
NULL
|
|
42944
|
914
|
44
|
2026-04-17T07:44:50.731771+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776411890731_m1.jpg...
|
Slack
|
engineering (Channel) - Jiminny Inc - Slack
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
Directories
jiminny-x-integration-app
platform-inner-team
ai-chapter
alerts
backend
confusion-clinic
curiosity_lab
engineering
frontend
general
infra-changes
jiminny-bg
platform-tickets
product_launches
random
releases
sofia-office
support
thank-yous
the_people_of_jiminny
Aneliya Angelova
,
Nikolay Yankov
,
Steliyan Georgiev
Galya Dimitrova...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Jiminny Inc","depth":12,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXRadioButton","text":"Jiminny (Staging)","depth":12,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Add workspaces","depth":12,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":16,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":16,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":16,"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":16,"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":16,"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":14,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":16,"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":20,"role_description":"text"},{"role":"AXStaticText","text":"jiminny-x-integration-app","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"curiosity_lab","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"frontend","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"general","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"infra-changes","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"platform-tickets","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"product_launches","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"random","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"releases","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"sofia-office","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"support","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"thank-yous","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"the_people_of_jiminny","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Aneliya Angelova","depth":22,"role_description":"text"},{"role":"AXStaticText","text":",","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":22,"role_description":"text"},{"role":"AXStaticText","text":",","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":22,"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":22,"role_description":"text"}]...
|
4683057601668073813
|
-4058599097821643736
|
app_switch
|
hybrid
|
NULL
|
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
Directories
jiminny-x-integration-app
platform-inner-team
ai-chapter
alerts
backend
confusion-clinic
curiosity_lab
engineering
frontend
general
infra-changes
jiminny-bg
platform-tickets
product_launches
random
releases
sofia-office
support
thank-yous
the_people_of_jiminny
Aneliya Angelova
,
Nikolay Yankov
,
Steliyan Georgiev
Galya Dimitrova
Term2ShellEditViewSession→ScriptsProfilesWindowHelp(alolBackend Chapter • 46 m leftmeet.google.com/gjc-ikxu-wxu?authuser=lukas.kovalik%40jiminny.comNikolay Nikolov (Presenting, annotating)Platform Sprint 2 Q2 - Platform Tea[SRD-6793] Les Mills activity typeProblem loading pageSymfony|Component|Debug\ExcepG) Dacab XCloudWatch | us-east-2Configure SSH access to multiple eConsole Home | Console Home | elNew TabMeet - Backend ChapterNew TabSOL Edtor Defabare Widon, J*loAмлoĐ- 8 4 1900t0 • 0 jniny • @• & • Q •[ *<P00 US» jminey_nikJocal. sal xa2nt+-- 1872|-- 2026-04-16 16:00:23select * fron failed_jobs fj where fj-queue Like "Nanalytics_low' order by id desc;- REPORTSselect • fron opportunities where id = 7594349;|select * fron opportunity_stages os where os.opportunity_id = 7594349 and os-created_at > '2026-04-17 84:20:00' order by 05.created_at desc;select count(») fron opportunity_stages os where os-opportunity_id = 7594349 and os-created_at > '2826-84-17 04:28:80' order by 05-created_at asc;select distinct os.stage_id fron opportunity_stages os where os.opportunity_id = 7594349 and os.created_at > '2026-04-17 04:28:00' order by os.created_at •Selee o.dos OebutesJ07NON o.user id = u.io2026-04-17 07:42-20• 0.002s (0.002s fetch), on 2026-04-17 at 10-46:411080: 140: 20064Se: 01010:44 AM | Backend Chapter100% C8 • Fri 17 Apr 10:44:50Nikolay NikolovLukas Kovalik...
|
NULL
|
|
42945
|
915
|
50
|
2026-04-17T07:44:50.773538+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776411890773_m2.jpg...
|
Slack
|
engineering (Channel) - Jiminny Inc - Slack
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
Directories
jiminny-x-integration-app
platform-inner-team
ai-chapter
alerts
backend
confusion-clinic
curiosity_lab
engineering
frontend
general
infra-changes
jiminny-bg
platform-tickets
product_launches
random
releases
sofia-office
support
thank-yous
the_people_of_jiminny
Aneliya Angelova
,
Nikolay Yankov
,
Steliyan Georgiev
Galya Dimitrova
Nikolay Nikolov
Stoyan Tanev
Vasil Vasilev
Nikolay Ivanov
Aneliya Angelova
Ves
Steliyan Georgiev...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Jiminny Inc","depth":12,"bounds":{"left":0.00546875,"top":0.05486111,"width":0.0125,"height":0.022222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXRadioButton","text":"Jiminny (Staging)","depth":12,"bounds":{"left":0.00546875,"top":0.09097222,"width":0.0125,"height":0.022222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Add workspaces","depth":12,"bounds":{"left":0.00546875,"top":0.12708333,"width":0.0125,"height":0.022222223},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":14,"bounds":{"left":0.026953125,"top":0.048611112,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":16,"bounds":{"left":0.03125,"top":0.08125,"width":0.012109375,"height":0.009027778},"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":14,"bounds":{"left":0.026953125,"top":0.09583333,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":16,"bounds":{"left":0.032421876,"top":0.12847222,"width":0.009765625,"height":0.009027778},"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":14,"bounds":{"left":0.026953125,"top":0.14305556,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":16,"bounds":{"left":0.0296875,"top":0.17569445,"width":0.015234375,"height":0.009027778},"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":14,"bounds":{"left":0.026953125,"top":0.19027779,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":16,"bounds":{"left":0.0328125,"top":0.22291666,"width":0.008984375,"height":0.009027778},"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":14,"bounds":{"left":0.026953125,"top":0.2375,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":16,"bounds":{"left":0.03203125,"top":0.2701389,"width":0.010546875,"height":0.009027778},"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":14,"bounds":{"left":0.026953125,"top":0.2847222,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":16,"bounds":{"left":0.03203125,"top":0.31736112,"width":0.010546875,"height":0.009027778},"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":20,"bounds":{"left":0.06679688,"top":0.0875,"width":0.022265624,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":20,"bounds":{"left":0.06679688,"top":0.10694444,"width":0.020703126,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":20,"bounds":{"left":0.06679688,"top":0.12638889,"width":0.021484375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":20,"bounds":{"left":0.06679688,"top":0.14583333,"width":0.034375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":20,"bounds":{"left":0.06679688,"top":0.16527778,"width":0.028515626,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"jiminny-x-integration-app","depth":22,"bounds":{"left":0.07304688,"top":0.24722221,"width":0.0515625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":22,"bounds":{"left":0.07304688,"top":0.26666668,"width":0.05234375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":22,"bounds":{"left":0.07304688,"top":0.3125,"width":0.026171874,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":22,"bounds":{"left":0.07304688,"top":0.33194444,"width":0.014453125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":22,"bounds":{"left":0.07304688,"top":0.3513889,"width":0.021484375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":22,"bounds":{"left":0.07304688,"top":0.37083334,"width":0.040625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"curiosity_lab","depth":22,"bounds":{"left":0.07304688,"top":0.39027777,"width":0.032421876,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":22,"bounds":{"left":0.07304688,"top":0.4097222,"width":0.03046875,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"frontend","depth":22,"bounds":{"left":0.07304688,"top":0.42916667,"width":0.02265625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"general","depth":22,"bounds":{"left":0.07304688,"top":0.4486111,"width":0.019140625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"infra-changes","depth":22,"bounds":{"left":0.07304688,"top":0.46805555,"width":0.034765624,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":22,"bounds":{"left":0.07304688,"top":0.4875,"width":0.02734375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"platform-tickets","depth":22,"bounds":{"left":0.07304688,"top":0.5069444,"width":0.041015625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"product_launches","depth":22,"bounds":{"left":0.07304688,"top":0.5263889,"width":0.0453125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"random","depth":22,"bounds":{"left":0.07304688,"top":0.54583335,"width":0.019921875,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"releases","depth":22,"bounds":{"left":0.07304688,"top":0.56527776,"width":0.020703126,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"sofia-office","depth":22,"bounds":{"left":0.07304688,"top":0.5847222,"width":0.02890625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"support","depth":22,"bounds":{"left":0.07304688,"top":0.6041667,"width":0.020703126,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"thank-yous","depth":22,"bounds":{"left":0.07304688,"top":0.6236111,"width":0.02890625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"the_people_of_jiminny","depth":22,"bounds":{"left":0.07304688,"top":0.64305556,"width":0.053125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Aneliya Angelova","depth":22,"bounds":{"left":0.07304688,"top":0.6888889,"width":0.044140626,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":",","depth":22,"bounds":{"left":0.11679687,"top":0.6888889,"width":0.0078125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":22,"bounds":{"left":0.11992188,"top":0.6888889,"width":0.016796876,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":",","depth":22,"bounds":{"left":0.13632813,"top":0.70416665,"width":0.000390625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":22,"bounds":{"left":0.13632813,"top":0.70416665,"width":0.000390625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":22,"bounds":{"left":0.07304688,"top":0.7083333,"width":0.04140625,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Nikolov","depth":22,"bounds":{"left":0.07304688,"top":0.7277778,"width":0.040234376,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Stoyan Tanev","depth":22,"bounds":{"left":0.07304688,"top":0.74722224,"width":0.033984374,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":22,"bounds":{"left":0.07304688,"top":0.76666665,"width":0.03125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Ivanov","depth":22,"bounds":{"left":0.07304688,"top":0.7861111,"width":0.037890624,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Aneliya Angelova","depth":22,"bounds":{"left":0.07304688,"top":0.8055556,"width":0.044140626,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Ves","depth":22,"bounds":{"left":0.07304688,"top":0.825,"width":0.009375,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":22,"bounds":{"left":0.07304688,"top":0.84444445,"width":0.044921875,"height":0.0125},"role_description":"text"}]...
|
-2614572194735315463
|
-4060426290718958583
|
app_switch
|
hybrid
|
NULL
|
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
Directories
jiminny-x-integration-app
platform-inner-team
ai-chapter
alerts
backend
confusion-clinic
curiosity_lab
engineering
frontend
general
infra-changes
jiminny-bg
platform-tickets
product_launches
random
releases
sofia-office
support
thank-yous
the_people_of_jiminny
Aneliya Angelova
,
Nikolay Yankov
,
Steliyan Georgiev
Galya Dimitrova
Nikolay Nikolov
Stoyan Tanev
Vasil Vasilev
Nikolay Ivanov
Aneliya Angelova
Ves
Steliyan Georgiev
PhpStormFileEditFV faVsco.s vProjectvM-INTERNAL WEBHOOKSEIUPEjiminny_storageM+ Icenses.moM Makefile0 package-lock.jsonphostan.neon.dist= phpstan-baseline.neon<phpunit.xmlTe raw_sql_query.sqlM+ README.mdsonar-project.properties=test.py‹> Untitled Diagram.xmlus vetur.config.jsM-+ WEbnOOK HILIEKING_IMPLE› ih External Librariesv E° Scratches and Consolesv D Database ConsolesV AEUA console [EU]A DEAL RISKS [EU]A DI [EU]ViewNavigateCodeLaravelRefactorToolsWindowHelp#11894 on JY-18909-automated-reports-ask-jiminny k ~© AutomatedReportsService.php© SendReportJob.php© SendReportMailJob.php ReportController.php© TokenBuilder.php© AskJiminnyReportsController.php© AutomatedReportsCommandTest.php© AutomatedReportsSendCommand.php© Team.php© CreateActivityLoggedEvent.php© UserPilotActivityListener.php©ActivityLogged.php© AutomatedReportsCallbackService.php€ OpportunitySyncTrait.phpx© AutomatedReportResult.php© AutomatedReport.phptrait OpportunitySyncTrait2 usages838839840841844843private function build0pportunityData(array $properties,?int $accountId,?BusinessProcess $businessProcess.roade ostaoe): array {$ownerId = null;845846847848849850$profile = null;if (! empty($properties['hubspot_owner_id'])) {$ownerId = $properties['hubspot_owner_id'];$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);© TeamSetupController.phpphp api.phpAutomatedReportsRepository.php© RequestGenerateAskJiminnyReportJob.phpFilesystem.php© AutomatedReportsCommand.php© CreateHeldActivityEvent.php• TrackProviderInstalledEvent.php© RequestGenerateReportJob.php© SyncOpportunity.php432 ×2 M19 ^shame = "unknown";if (isset($properties['dealname'])) {$name = mb_strimwidth($propertiesl'dealname'],start:0, width:|128);ServicesOutputfb jiminny.opportunitiesv D DatabaseV AEUc consoefị crm_configurations 1 s 504 msv Ajiminny@localhost4 SFA HS_localV A PROD4, console 1 s 806 msV A STAGINGA console,, DockerTrowvTx: Auto vQEEA®uvid (UUID)[ team_idD crm_configuration_idE account_idOE stage_idI stage_updated_atE record_type_idcrm_provider_idEuser_idowner_idnameI valueI currency_codeD is_closed0 is_wonI close_dateI probability• forecast_categoryI deleted_atI created_atI remotely_created_at• updated_atI type7594349491b6904-7b7d-5b8b-ab90-9f229293a03745936797945312026-04-17 07:44:374150580297582411489352480053National Grid UK - SI Digital Lands...70700.002026-07-03Best CasesnuLl2026-03-16 16:53:142026-05-16 16:191452026-04-17 07:44:37SNULLlabl• Backend Chapter • 46 m left100% C•8 • Fri 17 Apr 10:44:50U AutomatedReportsCommandTestv= custom.log= laravel.logA SF ljiminny@localhost]V connect.vueV Onboard.vueHs local liminnyalocalnosti crm_configurations (EU]¿ console IPRODI& consoe STAGINGC" scratch_1.jsonA console (EU] X157815791580E1581108415831584=15851586=1587158815891590X:Auto vPlaygroundseLect * Trom teams where 1d = 550; # OW mselect * tron crn contlourarions where 10 = 47.SELECT * FROM users WHERE id = 18101;Ma lminnvv026 49 421 X3 X 102 ^u.email,sa.*,t.owner_id FROM social_accounts saJOIN users u on u.id = sa.sociable_idJOIN teams t1..n<->1: on t.id = u.team_idWHERE u.team_id = 556 and sa.provider = 'integration-app';select * from opportunities where id = 7594349;• :-CsVvI row retrieved startind trom 1 in b3z ms eution: 8o ms, Telchina: 546 mswinasun leamsloso.l ult-o4 spaces...
|
NULL
|
|
42946
|
914
|
45
|
2026-04-17T07:44:51.962743+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776411891962_m1.jpg...
|
PhpStorm
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Term2ShellEditViewSession→ScriptsProfilesWindowHel Term2ShellEditViewSession→ScriptsProfilesWindowHelp(alolBackend Chapter • 46 m leftmeet.google.com/gjc-ikxu-wxu?authuser=lukas.kovalik%40jiminny.comNikolay Nikolov (Presenting, annotating)Platform Sprint 2 Q2 - Platform Tea[SRD-6793] Les Mills activity typeProblem loading pageSymfony|Component|Debug\ExcepG) Dacab XCloudWatch | us-east-2Configure SSH access to multiple eConsole Home | Console Home | elNew TabMeet - Backend ChapterNew TabSOL Edtor Defabare Widon, J*loAмлoĐ- 8 4 1900t0 • 0 jniny • @• & • Q •[ *<PR00 US» jminey_nikJocal. sal xa2nt+-- 1872|-- 2026-04-16 16:00:23select * fron failed_jobs fj where fj-queue Like "Nanalytics_low' order by id desc;- REPORTSselect • fron opportunities where id = 7594349;|select • fron opportunity_stages os where os.opportunity_id = 7594349 and os-created_at > '2026-04-17 84:20:00' order by 05.created_at desc;select count(») fron opportunity_stages os where os-opportunity_id = 7594349 and os-created_at > '2826-84-17 04:28:80' order by 05-created_at asc;select distinct os.stage_id fron opportunity_stages os where os.opportunity_id = 7594349 and os.created_at > '2026-04-17 04:28:00' order by os.created_at •SeLEL O.don OebunoJ07NON o.user id = u.io2026-04-17 07:42-20• 0.002s (0.002s fetch), on 2026-04-17 at 10-46:411080: 140: 20064Se: 01010:44 AM | Backend Chapter100% C8 • Fri 17 Apr 10:44:51Nikolay NikolovLukas Kovalik...
|
NULL
|
6304280953717149092
|
NULL
|
app_switch
|
ocr
|
NULL
|
Term2ShellEditViewSession→ScriptsProfilesWindowHel Term2ShellEditViewSession→ScriptsProfilesWindowHelp(alolBackend Chapter • 46 m leftmeet.google.com/gjc-ikxu-wxu?authuser=lukas.kovalik%40jiminny.comNikolay Nikolov (Presenting, annotating)Platform Sprint 2 Q2 - Platform Tea[SRD-6793] Les Mills activity typeProblem loading pageSymfony|Component|Debug\ExcepG) Dacab XCloudWatch | us-east-2Configure SSH access to multiple eConsole Home | Console Home | elNew TabMeet - Backend ChapterNew TabSOL Edtor Defabare Widon, J*loAмлoĐ- 8 4 1900t0 • 0 jniny • @• & • Q •[ *<PR00 US» jminey_nikJocal. sal xa2nt+-- 1872|-- 2026-04-16 16:00:23select * fron failed_jobs fj where fj-queue Like "Nanalytics_low' order by id desc;- REPORTSselect • fron opportunities where id = 7594349;|select • fron opportunity_stages os where os.opportunity_id = 7594349 and os-created_at > '2026-04-17 84:20:00' order by 05.created_at desc;select count(») fron opportunity_stages os where os-opportunity_id = 7594349 and os-created_at > '2826-84-17 04:28:80' order by 05-created_at asc;select distinct os.stage_id fron opportunity_stages os where os.opportunity_id = 7594349 and os.created_at > '2026-04-17 04:28:00' order by os.created_at •SeLEL O.don OebunoJ07NON o.user id = u.io2026-04-17 07:42-20• 0.002s (0.002s fetch), on 2026-04-17 at 10-46:411080: 140: 20064Se: 01010:44 AM | Backend Chapter100% C8 • Fri 17 Apr 10:44:51Nikolay NikolovLukas Kovalik...
|
NULL
|
|
42982
|
916
|
10
|
2026-04-17T07:46:53.989131+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776412013989_m1.jpg...
|
Firefox
|
Meet - Backend Chapter — Work
|
1
|
meet.google.com/gjc-ikxu-wxu?authuser=lukas.kovali meet.google.com/gjc-ikxu-wxu?authuser=lukas.kovalik%40jiminny.com...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
Problem loading page
Problem loading page
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Symfony\Component\Debug\Exception\FatalThrowableError: 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
CloudWatch | us-east-2
CloudWatch | us-east-2
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
Console Home | Console Home | eu-west-1
Console Home | Console Home | eu-west-1
New Tab
New Tab
Meet - Backend Chapter
Mute tab
Meet - Backend Chapter
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Nikolay Nikolov (Presenting, annotating)
Nikolay Nikolov (Presenting, annotating)
People
3
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Zoom in
Open in new window
Enter Full Screen
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Nikolay Nikolov
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Lukas Kovalik
Others might see more of your background. Click to view your full video.
10:46
AM
Backend Chapter
Backend Chapter
Audio settings
Turn off microphone
Video settings
Turn off camera
Nikolay Nikolov is presenting
Send a reaction
Turn on captions
Raise hand (ctrl + ⌘ + h)
More options
Leave call
Meeting details...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"bounds":{"left":0.0038194444,"top":0.072222225,"width":0.15868056,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.13222222,"width":0.16631944,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.027777778,"top":0.14777778,"width":0.21111111,"height":0.015},"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.17777778,"width":0.16631944,"height":0.045555554},"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.027777778,"top":0.19333333,"width":0.19895834,"height":0.015},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Problem loading page","depth":4,"bounds":{"left":0.0,"top":0.22333333,"width":0.16631944,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Problem loading page","depth":5,"bounds":{"left":0.027777778,"top":0.23888889,"width":0.079166666,"height":0.015},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Symfony\\Component\\Debug\\Exception\\FatalThrowableError: 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","depth":4,"bounds":{"left":0.0,"top":0.2688889,"width":0.16631944,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Symfony\\Component\\Debug\\Exception\\FatalThrowableError: 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","depth":5,"bounds":{"left":0.027777778,"top":0.28444445,"width":0.9496528,"height":0.015},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0,"top":0.31444445,"width":0.16631944,"height":0.045555554},"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.027777778,"top":0.33,"width":0.08611111,"height":0.015},"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.36,"width":0.16631944,"height":0.045555554},"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.027777778,"top":0.37555555,"width":0.26944444,"height":0.015},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Console Home | Console Home | eu-west-1","depth":4,"bounds":{"left":0.0,"top":0.40555555,"width":0.16631944,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Console Home | Console Home | eu-west-1","depth":5,"bounds":{"left":0.027777778,"top":0.4211111,"width":0.15520833,"height":0.015},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.4511111,"width":0.16631944,"height":0.045555554},"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.027777778,"top":0.46666667,"width":0.03125,"height":0.015},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Meet - Backend Chapter","depth":4,"bounds":{"left":0.0,"top":0.49666667,"width":0.16631944,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXButton","text":"Mute tab","depth":5,"bounds":{"left":0.023958333,"top":0.50666666,"width":0.016666668,"height":0.026666667},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Meet - Backend Chapter","depth":5,"bounds":{"left":0.042013887,"top":0.51222223,"width":0.08888889,"height":0.015},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.140625,"top":0.50666666,"width":0.016666668,"height":0.026666667},"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.54444444,"width":0.15486111,"height":0.035555556},"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.005902778,"top":0.9583333,"width":0.022222223,"height":0.035555556},"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.028819444,"top":0.9583333,"width":0.022222223,"height":0.035555556},"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.052083332,"top":0.9583333,"width":0.022222223,"height":0.035555556},"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.07534722,"top":0.9583333,"width":0.022222223,"height":0.035555556},"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.09861111,"top":0.9583333,"width":0.022222223,"height":0.035555556},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Nikolay Nikolov (Presenting, annotating)","depth":12,"bounds":{"left":0.20798612,"top":0.101111114,"width":0.17916666,"height":0.022222223},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Nikolov (Presenting, annotating)","depth":13,"bounds":{"left":0.20798612,"top":0.10222222,"width":0.17916666,"height":0.020555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"People","depth":15,"bounds":{"left":0.88680553,"top":0.08944444,"width":0.04097222,"height":0.04},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"3","depth":22,"bounds":{"left":0.9145833,"top":0.101111114,"width":0.0048611113,"height":0.017222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Take notes with Gemini","depth":14,"bounds":{"left":0.93333334,"top":0.08944444,"width":0.025,"height":0.04},"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":17,"bounds":{"left":0.9361111,"top":0.101111114,"width":0.06388891,"height":0.017222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini","depth":22,"bounds":{"left":0.96666664,"top":0.101111114,"width":0.028125,"height":0.017222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Gemini","depth":21,"bounds":{"left":0.96458334,"top":0.090555556,"width":0.023611112,"height":0.037777778},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"bounds":{"left":0.6159722,"top":0.5922222,"width":0.14652778,"height":0.08888889},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"bounds":{"left":0.7604167,"top":0.6072222,"width":0.08090278,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"bounds":{"left":0.7378472,"top":0.6027778,"width":0.11076389,"height":0.05666667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Zoom in","depth":13,"bounds":{"left":0.66770834,"top":0.74,"width":0.027777778,"height":0.044444446},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open in new window","depth":13,"bounds":{"left":0.70104164,"top":0.74,"width":0.027777778,"height":0.044444446},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Enter Full Screen","depth":13,"bounds":{"left":0.734375,"top":0.74,"width":0.027777778,"height":0.044444446},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"bounds":{"left":0.93819445,"top":0.36666667,"width":0.061805546,"height":0.07722222},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"bounds":{"left":1.0,"top":0.38166666,"width":-0.08229172,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"bounds":{"left":1.0,"top":0.3772222,"width":-0.06006944,"height":0.045},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Nikolov","depth":17,"bounds":{"left":0.79270834,"top":0.485,"width":0.07847222,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"bounds":{"left":1.0,"top":0.75333333,"width":-0.018749952,"height":0.07722222},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"bounds":{"left":0.9454861,"top":0.7683333,"width":0.05451387,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"bounds":{"left":0.92604166,"top":0.7638889,"width":0.07395834,"height":0.045},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Lukas Kovalik","depth":17,"bounds":{"left":0.79270834,"top":0.87166667,"width":0.06875,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Others might see more of your background. Click to view your full video.","depth":14,"bounds":{"left":0.9614583,"top":0.86722225,"width":0.019444445,"height":0.031111112},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"10:46","depth":12,"bounds":{"left":0.18298611,"top":0.9444444,"width":0.028472222,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AM","depth":12,"bounds":{"left":0.21493055,"top":0.9444444,"width":0.017708333,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Backend Chapter","depth":12,"bounds":{"left":0.25,"top":0.9111111,"width":0.090277776,"height":0.08888888},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Backend Chapter","depth":15,"bounds":{"left":0.25,"top":0.9444444,"width":0.090277776,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Audio settings","depth":13,"bounds":{"left":0.3875,"top":0.9288889,"width":0.06111111,"height":0.053333335},"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":13,"bounds":{"left":0.41527778,"top":0.9288889,"width":0.033333335,"height":0.053333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Video settings","depth":13,"bounds":{"left":0.45416668,"top":0.9288889,"width":0.06111111,"height":0.053333335},"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":13,"bounds":{"left":0.48194444,"top":0.9288889,"width":0.033333335,"height":0.053333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Nikolay Nikolov is presenting","depth":12,"bounds":{"left":0.5208333,"top":0.9288889,"width":0.03888889,"height":0.053333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Send a reaction","depth":12,"bounds":{"left":0.56527776,"top":0.9288889,"width":0.03888889,"height":0.053333335},"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":13,"bounds":{"left":0.6097222,"top":0.9288889,"width":0.03888889,"height":0.053333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Raise hand (ctrl + ⌘ + h)","depth":12,"bounds":{"left":0.65416664,"top":0.9288889,"width":0.03888889,"height":0.053333335},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"More options","depth":12,"bounds":{"left":0.69861114,"top":0.9288889,"width":0.025,"height":0.053333335},"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":12,"bounds":{"left":0.7291667,"top":0.9288889,"width":0.05,"height":0.053333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Meeting details","depth":12,"bounds":{"left":0.89166665,"top":0.9288889,"width":0.033333335,"height":0.053333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-4211429070587540851
|
-5300473782502999244
|
app_switch
|
accessibility
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
Problem loading page
Problem loading page
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Symfony\Component\Debug\Exception\FatalThrowableError: 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
CloudWatch | us-east-2
CloudWatch | us-east-2
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
Console Home | Console Home | eu-west-1
Console Home | Console Home | eu-west-1
New Tab
New Tab
Meet - Backend Chapter
Mute tab
Meet - Backend Chapter
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Nikolay Nikolov (Presenting, annotating)
Nikolay Nikolov (Presenting, annotating)
People
3
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Zoom in
Open in new window
Enter Full Screen
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Nikolay Nikolov
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Lukas Kovalik
Others might see more of your background. Click to view your full video.
10:46
AM
Backend Chapter
Backend Chapter
Audio settings
Turn off microphone
Video settings
Turn off camera
Nikolay Nikolov is presenting
Send a reaction
Turn on captions
Raise hand (ctrl + ⌘ + h)
More options
Leave call
Meeting details...
|
NULL
|
|
42983
|
917
|
6
|
2026-04-17T07:46:52.489129+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776412012489_m2.jpg...
|
Slack
|
* engineering (Channel) - Jiminny Inc - Slack
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Home
DMs...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Jiminny Inc","depth":12,"bounds":{"left":0.00546875,"top":0.05486111,"width":0.0125,"height":0.022222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXRadioButton","text":"Jiminny (Staging)","depth":12,"bounds":{"left":0.00546875,"top":0.09097222,"width":0.0125,"height":0.022222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Add workspaces","depth":12,"bounds":{"left":0.00546875,"top":0.12708333,"width":0.0125,"height":0.022222223},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":14,"bounds":{"left":0.026953125,"top":0.048611112,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":16,"bounds":{"left":0.03125,"top":0.08125,"width":0.012109375,"height":0.009027778},"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":14,"bounds":{"left":0.026953125,"top":0.09583333,"width":0.020703126,"height":0.047222223},"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-713526364605942244
|
-4193080434398119705
|
app_switch
|
hybrid
|
NULL
|
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Jiminny Inc
Jiminny (Staging)
Add workspaces
Home
Home
DMs
PhpStormFileFV faVsco.jsProject vEdit> D Redisv D ServiceTraits€ OpportunitySy& SyncCrmEntiti© SyncFieldsTra© WriteCrmTrait.→IUTIS•Weohook© BatchSyncCollect© BatchSyncRedisS© Client.php© ClosedDealStage6 DealFieldsService© DecorateActivity.© FieldDefinitions.p© FieldTypeConveri© HubspotClientInte© HubspotTokenMa© PayloadBuilder.pt© RemoteCrmObjec(C) ResponseNormali© Service.php© SyncFieldAction.f© SyncRelatedActiv© WebhookSyncBa1v D IntegrationApp> D Accessors~ D Api© ActionUrl.php• EnumUrllnterfa© FlowUrl.php© PageResult.phe Proxyun.php(c) recuestsuloeLa kequestcxecu• RequestExecuSystemEvents© SystemUrl.phpC TokenBuilder.f• TokenBuilderir© UrlBuilder.php> D Config> DDTO> MFilters>MJobs› _ ProspectSearchS> D ServiceTraits© DataClient.php© DecorateActivity.© LocalSearch.php• LocalSearchlntert© RemoteSearch.pr© Service.phpM ListenersM MetadateD MigrationPipedriveD SalesforceD TraitsViewNavigateCodeLaravelRefactor#11894 on JY-18909-automated-reports-ask-jiminny k ~© AutomatedReportsService.php© TeamSetupController.phppnp apl.onp© AutomatedReportsCommandTest.php© TrackProviderInstalledEvent.phpC AutomatedReportsCallbackService.php+ OpportunitySyncTrait.php XToolsWindowHelp© SendReportJob.php• Filesystem.php© SendReportMailJob.phpC AutomatedReportsCommand.php© ReportController.php© TokenBuilder.phpAskJiminnykeponscontroller.ono© AutomatedReportsSendCommand.php© Team.php© CreateActivityLoggedEvent.php© UserPilotActivityListener.phpC RequestGenerateAskJiminnyReportJob.phpC RequestGenerateReportJob.phpsyncopoortunity.ono© AutomatedReportResult.php"podcast aualo unl© AutomatedReport.phpx § ccw.*0 results798811814815014815816817818819820857185885Y8408418428438448458468478488498538558568578588598608618628638648658668678688698788/5trait OpportunitySyncTraitprivate function update0pportunity(string $crmId, array $properties,array $associations): Opportunity+Values- array_mergelsaccribuces, paata),$opportunity = $this->crmEntityRepository->upsert0pportunity($attributes, $values);$this->importExternalFieldData($properties, $opportunity->getId());$this->update0pportunityAssociations($opportunity, $associations);return $opportunity;2 usagesprivate function resolveAccountId(array $associations): ?int{...}2 usagesprivate function buildOpportunityData(array soropercles.?int $accountId,?BusinessProcess $businessProcess,?Stage $stage): array {$ownerId = null;$profile = null;if (! empty($properties['hubspot_owner_id'])) {$ownerId = $properties['hubspot_owner_id'];$profile = $this->crmEntityRepository->findProfileByExternalId($this->config,(string) $ownerId);$name = 'Unknown';if (isset($properties['dealname'])) {$name = mb_strimwidth($properties['dealname'],start:0,width: 128);$amount = $this->resolveAmount($properties);$currency = $properties['deal_currency_code'] ?? null;$closeDate = null;if (! empty(Spropertiesl'closedate'])) €$closeDate = Carbon: :parse($properties['alosedate']) ->format ( format: 'Y-m-d');$remotelyCreatedAt = null;if (! empty($properties['greatedate']) && strtotime($properties['greatedate' ])) {$date = $this->parseCleanDatetime($properties[ 'createdate']):$remotelyCreatedAt = $date?->format( format:'Y-m-d H:i:s');ncloseostades = ums->oer.losedvealstadeso.$isWon = in_array($properties['dealstage'], $closedStages[ 'won']);$isLost = in_array($properties['dealstage'], $closedStages['lost']);Helper Code will help IDE to understand your Laravel app code. // Generate // Don't Show Anymore (today 8:59)CreateHeldActivityEvent.phpB32 M2M19 ^= custom.logC° scratch_1.json= laravel.logV connect.vueA HS_local jiminny@localhost]fi crm_configurations [EU]A SF [jiminny@localhost]V Onboard.vueA console [EU] x|Al console [PROD]& console SlAGiNG15421543-154415451546=104,-15481549=[CREDIT_CARD]=155415551556155715581559E156015611562156315641565=1566156715681569157015711572157315741575157615771578157915801584158715881590Ix. Aulo vfa jiminny~oElee* rrom cem026 Д9 Д2 2102SELECT * FROM crm_layout_entities WHERE crm_layoutSELECT * FROM teams WHERE id = 575;select * from opportunities where team_id = 575;=SELECT * FROM activities WHERE uvid_to_bin('96b126select * from contacts cwhere c.crm_configuration_id = 370 order by c.updaSELECT * FROM participants where activity_id = 39gSELECT * FROM participants where activity_id = 32SELECT * FROM activity_summary_logs where activitySELECT * FROM activities WHERE uvid_to_bin('c7d99FSELECT * FROM activities WHERE uuid_to_bin('2e6ff4select * from crm_profiles where crm_configurationselect * from opportunities where crm_configuratioselect * from accounts where crm_configuration_idselect * from contacts where crm_configuration_id# owner 13236 525785080# contact 116779180 665587441856 - activity - Al# contact 219247563 742723347700 - ash@supportro# company 4176133 47150650569# deal 7100953 410150124747SELECTCONCAT(u.id, CASE WHEN U.id = t.owner_id THENu.email,sa.*,t.owner_id FROM social_accounts saJOIN users u on u.id = sa.sociable_idJOIN teams t 1.n<->1: on t.id = u.team_idWHERE u.team_id = 400 and sa.provider = 'hubspot:select * from features;select * from team_features where feature_id = 40,select * from teams where id = 556; # owner: 18101select * from crm_configurations where id = 477;SELECT * FROM users WHERE id = 18101;SELECTCONCAT(u.id, LASE WHEN u.id = t.owner_id THENu.email,sa.*,t.owner_id FROM social_accounts saJulr users u on u.l = sa.soctaote oINULN teallsTl.lk">l.on t.id = u.team_idWHERE u.team_id = 556 and sa.provider = 'integratiselect * from opportunities where id = 7594349;40lablj Backend Chapter • 44 m leftAAutomatedReportsCommandTestv100% CS•8 • Fri 17 Apr 10:46:52CascadeAutomated Report Reti• HubSpot Opportunit+D ..revewi in hubspot when do we populate opportunity_stages6o Fast context populate opportunity_stages Hubspot sync in 3.01sFloating.Ask anything (24L)+ ‹› CodelClaude Sonnet 4.6W Windsurf Teams859:27UTF-84 spaces...
|
NULL
|
|
42986
|
916
|
12
|
2026-04-17T07:46:57.033284+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776412017033_m1.jpg...
|
PhpStorm
|
faVsco.js – OpportunitySyncTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
FirefoxFileEditViewHistory→BookmarksProfilesToolsW FirefoxFileEditViewHistory→BookmarksProfilesToolsWindowHelp>•.(alolBackend Chapter • 44 m leftmeet.google.com/gjc-ikxu-wxu?authuser=lukas.kovalik%40jiminny.comNikolay Nikolov (Presenting, annotating)Platform Sprint 2 Q2 - Platform Tea[SRD-6793] Les Mills activity typeProblem loading pageSymfony|Component|Debug\ExcepCloudWatch | us-east-2Configure SSH access to multiple e*• FM17 Aor 10:40Jiminny Ine~= backendcsure tiekets= confusion-clinic= curiosity. lab# engineeringO fies8 BockomarksE Open Positions8 Benehts$2 Pins₴rikeovanoreNesterday -Hi team, thanks to @Nikolay Yarkov when dependabot finds security vulnerabilities. PRs are open like the above SI sugpest that developers on SRD from both teams review and test them, so that they can be merged timelyaPS$ISlack= prophet-dev= random= releases= support= thank-yous= the.people_of_jiminny6 Direct messagesA Aneliya Angelova, Ilan Kyachukov #2. Lukas Kovalik +N2. Mario GeongieyP,M• #11970 fudsecurityl: composer dependency updates - 2026-04-15 (secfix/composer- 28260415)|llian Kyuchukov 1028We have a broken loop of syncing Stage changes for two cfents, which results in non-stop requests to Prophet (which costs moneyHas there been any changes to the logic?opportunity_stagesopportunity_id - 7594349 hax 10267 recordsMessage #enginceringAa10:46 AM ||Backend Chapter100% (42• 8• Fri 17 Apr 10:46:56Lukas Kovalik...
|
NULL
|
-8992347828549896598
|
NULL
|
app_switch
|
ocr
|
NULL
|
FirefoxFileEditViewHistory→BookmarksProfilesToolsW FirefoxFileEditViewHistory→BookmarksProfilesToolsWindowHelp>•.(alolBackend Chapter • 44 m leftmeet.google.com/gjc-ikxu-wxu?authuser=lukas.kovalik%40jiminny.comNikolay Nikolov (Presenting, annotating)Platform Sprint 2 Q2 - Platform Tea[SRD-6793] Les Mills activity typeProblem loading pageSymfony|Component|Debug\ExcepCloudWatch | us-east-2Configure SSH access to multiple e*• FM17 Aor 10:40Jiminny Ine~= backendcsure tiekets= confusion-clinic= curiosity. lab# engineeringO fies8 BockomarksE Open Positions8 Benehts$2 Pins₴rikeovanoreNesterday -Hi team, thanks to @Nikolay Yarkov when dependabot finds security vulnerabilities. PRs are open like the above SI sugpest that developers on SRD from both teams review and test them, so that they can be merged timelyaPS$ISlack= prophet-dev= random= releases= support= thank-yous= the.people_of_jiminny6 Direct messagesA Aneliya Angelova, Ilan Kyachukov #2. Lukas Kovalik +N2. Mario GeongieyP,M• #11970 fudsecurityl: composer dependency updates - 2026-04-15 (secfix/composer- 28260415)|llian Kyuchukov 1028We have a broken loop of syncing Stage changes for two cfents, which results in non-stop requests to Prophet (which costs moneyHas there been any changes to the logic?opportunity_stagesopportunity_id - 7594349 hax 10267 recordsMessage #enginceringAa10:46 AM ||Backend Chapter100% (42• 8• Fri 17 Apr 10:46:56Lukas Kovalik...
|
NULL
|
|
42987
|
916
|
13
|
2026-04-17T07:46:58.360518+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776412018360_m1.jpg...
|
Firefox
|
Meet - Backend Chapter — Work
|
1
|
meet.google.com/gjc-ikxu-wxu?authuser=lukas.kovali meet.google.com/gjc-ikxu-wxu?authuser=lukas.kovalik%40jiminny.com...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
Problem loading page
Problem loading page
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Symfony\Component\Debug\Exception\FatalThrowableError: 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
CloudWatch | us-east-2
CloudWatch | us-east-2
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
Console Home | Console Home | eu-west-1
Console Home | Console Home | eu-west-1
New Tab
New Tab
Meet - Backend Chapter
Mute tab
Meet - Backend Chapter
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Nikolay Nikolov (Presenting, annotating)
Nikolay Nikolov (Presenting, annotating)
People
3...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"bounds":{"left":0.0038194444,"top":0.072222225,"width":0.15868056,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.13222222,"width":0.16631944,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.027777778,"top":0.14777778,"width":0.21111111,"height":0.015},"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.17777778,"width":0.16631944,"height":0.045555554},"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.027777778,"top":0.19333333,"width":0.19895834,"height":0.015},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Problem loading page","depth":4,"bounds":{"left":0.0,"top":0.22333333,"width":0.16631944,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Problem loading page","depth":5,"bounds":{"left":0.027777778,"top":0.23888889,"width":0.079166666,"height":0.015},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Symfony\\Component\\Debug\\Exception\\FatalThrowableError: 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","depth":4,"bounds":{"left":0.0,"top":0.2688889,"width":0.16631944,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Symfony\\Component\\Debug\\Exception\\FatalThrowableError: 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","depth":5,"bounds":{"left":0.027777778,"top":0.28444445,"width":0.9496528,"height":0.015},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0,"top":0.31444445,"width":0.16631944,"height":0.045555554},"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.027777778,"top":0.33,"width":0.08611111,"height":0.015},"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.36,"width":0.16631944,"height":0.045555554},"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.027777778,"top":0.37555555,"width":0.26944444,"height":0.015},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Console Home | Console Home | eu-west-1","depth":4,"bounds":{"left":0.0,"top":0.40555555,"width":0.16631944,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Console Home | Console Home | eu-west-1","depth":5,"bounds":{"left":0.027777778,"top":0.4211111,"width":0.15520833,"height":0.015},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.4511111,"width":0.16631944,"height":0.045555554},"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.027777778,"top":0.46666667,"width":0.03125,"height":0.015},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Meet - Backend Chapter","depth":4,"bounds":{"left":0.0,"top":0.49666667,"width":0.16631944,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXButton","text":"Mute tab","depth":5,"bounds":{"left":0.023958333,"top":0.50666666,"width":0.016666668,"height":0.026666667},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Meet - Backend Chapter","depth":5,"bounds":{"left":0.042013887,"top":0.51222223,"width":0.08888889,"height":0.015},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.140625,"top":0.50666666,"width":0.016666668,"height":0.026666667},"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.54444444,"width":0.15486111,"height":0.035555556},"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.005902778,"top":0.9583333,"width":0.022222223,"height":0.035555556},"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.028819444,"top":0.9583333,"width":0.022222223,"height":0.035555556},"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.052083332,"top":0.9583333,"width":0.022222223,"height":0.035555556},"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.07534722,"top":0.9583333,"width":0.022222223,"height":0.035555556},"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.09861111,"top":0.9583333,"width":0.022222223,"height":0.035555556},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Nikolay Nikolov (Presenting, annotating)","depth":12,"bounds":{"left":0.20798612,"top":0.101111114,"width":0.17916666,"height":0.022222223},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Nikolov (Presenting, annotating)","depth":13,"bounds":{"left":0.20798612,"top":0.10222222,"width":0.17916666,"height":0.020555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"People","depth":15,"bounds":{"left":0.88680553,"top":0.08944444,"width":0.04097222,"height":0.04},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"3","depth":22,"bounds":{"left":0.9145833,"top":0.101111114,"width":0.0048611113,"height":0.017222222},"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-7788137454794859027
|
9092906192663677636
|
app_switch
|
accessibility
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
Problem loading page
Problem loading page
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Symfony\Component\Debug\Exception\FatalThrowableError: 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
CloudWatch | us-east-2
CloudWatch | us-east-2
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
Console Home | Console Home | eu-west-1
Console Home | Console Home | eu-west-1
New Tab
New Tab
Meet - Backend Chapter
Mute tab
Meet - Backend Chapter
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Nikolay Nikolov (Presenting, annotating)
Nikolay Nikolov (Presenting, annotating)
People
3...
|
NULL
|
|
42988
|
917
|
8
|
2026-04-17T07:46:57.022938+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776412017022_m2.jpg...
|
PhpStorm
|
faVsco.js – OpportunitySyncTrait.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileFV faVsco.jsProject vEdit> D Redisv PhpStormFileFV faVsco.jsProject vEdit> D Redisv D ServiceTraits€ OpportunitySy& SyncCrmEntiti© SyncFieldsTra© WriteCrmTrait.→IUTIS•Weohook© BatchSyncCollect© BatchSyncRedisS© Client.php© ClosedDealStage6 DealFieldsService© DecorateActivity.© FieldDefinitions.p© FieldTypeConveri© HubspotClientInte© HubspotTokenMa© PayloadBuilder.pt© RemoteCrmObjec(C) ResponseNormali© Service.php© SyncFieldAction.f© SyncRelatedActiv© WebhookSyncBa1v D IntegrationApp> D Accessors~ D Api© ActionUrl.php• EnumUrllnterfa© FlowUrl.php© PageResult.phe Proxyun.php(c) recuestsuloeLa kequestcxecu• RequestExecuSystemEvents© SystemUrl.phpC TokenBuilder.f• TokenBuilderir© UrlBuilder.php> D Config> DDTO> MFilters>MJobs› _ ProspectSearchS> D ServiceTraits© DataClient.php© DecorateActivity.© LocalSearch.php• LocalSearchlntert© RemoteSearch.pr© Service.phpM ListenersMeradaraD MigrationPipedriveD SalesforceD TraitsViewNavigateCodeLaravelRefactor#11894 on JY-18909-automated-reports-ask-jiminny k ~© AutomatedReportsService.php© TeamSetupController.phppnp apl.onp© AutomatedReportsCommandTest.php© TrackProviderInstalledEvent.phpC AutomatedReportsCallbackService.php+ OpportunitySyncTrait.php XToolsWindowHelp© SendReportJob.php• Filesystem.php© SendReportMailJob.phpC AutomatedReportsCommand.php© ReportController.php© TokenBuilder.phpAskJiminnykeponscontroller.ono© AutomatedReportsSendCommand.php© Team.php© CreateActivityLoggedEvent.php© UserPilotActivityListener.phpC RequestGenerateAskJiminnyReportJob.phpC RequestGenerateReportJob.phpsyncopoortunity.ono© AutomatedReportResult.php"podcast aualo unl© AutomatedReport.phpx 5 ccw.*0 results798811814815014815816817818819820857185885Y8408418428438448458468478488498538558568578588598608618628638648658668678688698788/5trait OpportunitySyncTraitprivate function update0pportunity(string $crmId, array $properties,array $associations): Opportunity+Values- array_mergelsaccribuces, paata),$opportunity = $this->crmEntityRepository->upsert0pportunity($attributes, $values);$this->importExternalFieldData($properties, $opportunity->getId());$this->update0pportunityAssociations($opportunity, $associations);return $opportunity;2 usagesprivate function resolveAccountId(array $associations): ?int{...}2 usagesprivate function buildOpportunityData(array soropercles.?int $accountId,?BusinessProcess $businessProcess,?Stage $stage): array {$ownerId = null;$profile = null;if (! empty($properties['hubspot_owner_id'])) {$ownerId = $properties['hubspot_owner_id'];$profile = $this->crmEntityRepository->findProfileByExternalId($this->config,(string) $ownerId);$name = 'Unknown';if (isset($properties['dealname'])) {$name = mb_strimwidth($properties['dealname'],start:0,width: 128);$amount = $this->resolveAmount($properties);$currency = $properties['deal_currency_code'] ?? null;$closeDate = null;if (! empty(Spropertiesl'closedate'])) €$closeDate = Carbon: :parse($properties['alosedate']) ->format ( format: 'Y-m-d');$remotelyCreatedAt = null;if (! empty($properties['greatedate']) && strtotime($properties['greatedate' ])) {$date = $this->parseCleanDatetime($properties[ 'createdate']):$remotelyCreatedAt = $date?->format( format:'Y-m-d H:i:s');ncloseostades = ums->oer.loseveaustades o.$isWon = in_array($properties['dealstage'], $closedStages[ 'won']);$isLost = in_array($properties['dealstage'], $closedStages['lost']);Helper Code will help IDE to understand your Laravel app code. // Generate // Don't Show Anymore (today 8:59)CreateHeldActivityEvent.phpB32 M2M19 ^= custom.logC° scratch_1.json= laravel.logV connect.vueA HS_local jiminny@localhost]fi crm_configurations [EU]A SF ljiminny@localhost]V Onboard.vueA console [EU] x|Al console [PROD]& console SlAGiNG15421543-154415451546=104,-15481549=[CREDIT_CARD]=155415551556155715581559=156015611562156315641565=156615671568156915701571157215731574157515761577157815791580158415881590Ix. Aulo vfa jiminny~oElee* rrom cem026 A9 Д23 2102SELECT * FROM crm_layout_entities WHERE crm_layoutSELECT * FROM teams WHERE id = 575;select * from opportunities where team_id = 575;=SELECT * FROM activities WHERE uvid_to_bin('96b126select * from contacts cwhere c.crm_configuration_id = 370 order by c.updaSELECT * FROM participants where activity_id = 39gSELECT * FROM participants where activity_id = 32SELECT * FROM activity_summary_logs where activitySELECT * FROM activities WHERE uvid_to_bin('c7d99FSELECT * FROM activities WHERE uuid_to_bin('2e6ff4select * from crm_profiles where crm_configurationselect * from opportunities where crm_configuratioselect * from accounts where crm_configuration_idselect * from contacts where crm_configuration_id# owner 13236 525785080# contact 116779180 665587441856 - activity - Al# contact 219247563 742723347700 - ash@supportro# company 4176133 47150650569# deal 7100953 410150124747SELECTCONCAT(u.id, CASE WHEN U.id = t.owner_id THENu.email,sa.*,t.owner_id FROM social_accounts saJOIN users u on u.id = sa.sociable_idJOIN teams t 1.n<->1: on t.id = u.team_idWHERE u.team_id = 400 and sa.provider = 'hubspot:select * from features;select * from team_features where feature_id = 40,select * from teams where id = 556; # owner: 18101select * from crm_configurations where id = 477;SELECT * FROM users WHERE id = 18101;SELECTCONCAT(u.id, LASE WHEN u.id = t.owner_id THENu.email,sa.*,t.owner_id FROM social_accounts saJOIN users u on u.id = sa.sociable_idJUlN Tealll'sTl.lk">l.on t.id = u.team_idWHERE u.team_id = 556 and sa.provider = 'integratiselect * from opportunities where id = 7594349;labl# Backend Chapter • 44 m leftAAutomatedReportsCommandTest-100% CS•8 • Fri 17 Apr 10:46:56CascadeAutomated Report Reti• HubSpot Opportunitrevewi in hubspot when do we populate opportunity_stagesnan rast conexoooulale ocvonunily staces -uosoor svnc insolsSearched stage_id|stage\b|opportunity_stage in app/Services/Crm/Hubspot/ServiceTraits/OpportunityS... ›Navigating.Ask anything (24L)+ ‹› CodelClaude Sonnet 4.6W Windsurf Teams859:27UTF-84 spaces...
|
NULL
|
6464779754145287367
|
NULL
|
app_switch
|
ocr
|
NULL
|
PhpStormFileFV faVsco.jsProject vEdit> D Redisv PhpStormFileFV faVsco.jsProject vEdit> D Redisv D ServiceTraits€ OpportunitySy& SyncCrmEntiti© SyncFieldsTra© WriteCrmTrait.→IUTIS•Weohook© BatchSyncCollect© BatchSyncRedisS© Client.php© ClosedDealStage6 DealFieldsService© DecorateActivity.© FieldDefinitions.p© FieldTypeConveri© HubspotClientInte© HubspotTokenMa© PayloadBuilder.pt© RemoteCrmObjec(C) ResponseNormali© Service.php© SyncFieldAction.f© SyncRelatedActiv© WebhookSyncBa1v D IntegrationApp> D Accessors~ D Api© ActionUrl.php• EnumUrllnterfa© FlowUrl.php© PageResult.phe Proxyun.php(c) recuestsuloeLa kequestcxecu• RequestExecuSystemEvents© SystemUrl.phpC TokenBuilder.f• TokenBuilderir© UrlBuilder.php> D Config> DDTO> MFilters>MJobs› _ ProspectSearchS> D ServiceTraits© DataClient.php© DecorateActivity.© LocalSearch.php• LocalSearchlntert© RemoteSearch.pr© Service.phpM ListenersMeradaraD MigrationPipedriveD SalesforceD TraitsViewNavigateCodeLaravelRefactor#11894 on JY-18909-automated-reports-ask-jiminny k ~© AutomatedReportsService.php© TeamSetupController.phppnp apl.onp© AutomatedReportsCommandTest.php© TrackProviderInstalledEvent.phpC AutomatedReportsCallbackService.php+ OpportunitySyncTrait.php XToolsWindowHelp© SendReportJob.php• Filesystem.php© SendReportMailJob.phpC AutomatedReportsCommand.php© ReportController.php© TokenBuilder.phpAskJiminnykeponscontroller.ono© AutomatedReportsSendCommand.php© Team.php© CreateActivityLoggedEvent.php© UserPilotActivityListener.phpC RequestGenerateAskJiminnyReportJob.phpC RequestGenerateReportJob.phpsyncopoortunity.ono© AutomatedReportResult.php"podcast aualo unl© AutomatedReport.phpx 5 ccw.*0 results798811814815014815816817818819820857185885Y8408418428438448458468478488498538558568578588598608618628638648658668678688698788/5trait OpportunitySyncTraitprivate function update0pportunity(string $crmId, array $properties,array $associations): Opportunity+Values- array_mergelsaccribuces, paata),$opportunity = $this->crmEntityRepository->upsert0pportunity($attributes, $values);$this->importExternalFieldData($properties, $opportunity->getId());$this->update0pportunityAssociations($opportunity, $associations);return $opportunity;2 usagesprivate function resolveAccountId(array $associations): ?int{...}2 usagesprivate function buildOpportunityData(array soropercles.?int $accountId,?BusinessProcess $businessProcess,?Stage $stage): array {$ownerId = null;$profile = null;if (! empty($properties['hubspot_owner_id'])) {$ownerId = $properties['hubspot_owner_id'];$profile = $this->crmEntityRepository->findProfileByExternalId($this->config,(string) $ownerId);$name = 'Unknown';if (isset($properties['dealname'])) {$name = mb_strimwidth($properties['dealname'],start:0,width: 128);$amount = $this->resolveAmount($properties);$currency = $properties['deal_currency_code'] ?? null;$closeDate = null;if (! empty(Spropertiesl'closedate'])) €$closeDate = Carbon: :parse($properties['alosedate']) ->format ( format: 'Y-m-d');$remotelyCreatedAt = null;if (! empty($properties['greatedate']) && strtotime($properties['greatedate' ])) {$date = $this->parseCleanDatetime($properties[ 'createdate']):$remotelyCreatedAt = $date?->format( format:'Y-m-d H:i:s');ncloseostades = ums->oer.loseveaustades o.$isWon = in_array($properties['dealstage'], $closedStages[ 'won']);$isLost = in_array($properties['dealstage'], $closedStages['lost']);Helper Code will help IDE to understand your Laravel app code. // Generate // Don't Show Anymore (today 8:59)CreateHeldActivityEvent.phpB32 M2M19 ^= custom.logC° scratch_1.json= laravel.logV connect.vueA HS_local jiminny@localhost]fi crm_configurations [EU]A SF ljiminny@localhost]V Onboard.vueA console [EU] x|Al console [PROD]& console SlAGiNG15421543-154415451546=104,-15481549=[CREDIT_CARD]=155415551556155715581559=156015611562156315641565=156615671568156915701571157215731574157515761577157815791580158415881590Ix. Aulo vfa jiminny~oElee* rrom cem026 A9 Д23 2102SELECT * FROM crm_layout_entities WHERE crm_layoutSELECT * FROM teams WHERE id = 575;select * from opportunities where team_id = 575;=SELECT * FROM activities WHERE uvid_to_bin('96b126select * from contacts cwhere c.crm_configuration_id = 370 order by c.updaSELECT * FROM participants where activity_id = 39gSELECT * FROM participants where activity_id = 32SELECT * FROM activity_summary_logs where activitySELECT * FROM activities WHERE uvid_to_bin('c7d99FSELECT * FROM activities WHERE uuid_to_bin('2e6ff4select * from crm_profiles where crm_configurationselect * from opportunities where crm_configuratioselect * from accounts where crm_configuration_idselect * from contacts where crm_configuration_id# owner 13236 525785080# contact 116779180 665587441856 - activity - Al# contact 219247563 742723347700 - ash@supportro# company 4176133 47150650569# deal 7100953 410150124747SELECTCONCAT(u.id, CASE WHEN U.id = t.owner_id THENu.email,sa.*,t.owner_id FROM social_accounts saJOIN users u on u.id = sa.sociable_idJOIN teams t 1.n<->1: on t.id = u.team_idWHERE u.team_id = 400 and sa.provider = 'hubspot:select * from features;select * from team_features where feature_id = 40,select * from teams where id = 556; # owner: 18101select * from crm_configurations where id = 477;SELECT * FROM users WHERE id = 18101;SELECTCONCAT(u.id, LASE WHEN u.id = t.owner_id THENu.email,sa.*,t.owner_id FROM social_accounts saJOIN users u on u.id = sa.sociable_idJUlN Tealll'sTl.lk">l.on t.id = u.team_idWHERE u.team_id = 556 and sa.provider = 'integratiselect * from opportunities where id = 7594349;labl# Backend Chapter • 44 m leftAAutomatedReportsCommandTest-100% CS•8 • Fri 17 Apr 10:46:56CascadeAutomated Report Reti• HubSpot Opportunitrevewi in hubspot when do we populate opportunity_stagesnan rast conexoooulale ocvonunily staces -uosoor svnc insolsSearched stage_id|stage\b|opportunity_stage in app/Services/Crm/Hubspot/ServiceTraits/OpportunityS... ›Navigating.Ask anything (24L)+ ‹› CodelClaude Sonnet 4.6W Windsurf Teams859:27UTF-84 spaces...
|
NULL
|
|
43006
|
916
|
24
|
2026-04-17T07:47:31.205829+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776412051205_m1.jpg...
|
PhpStorm
|
faVsco.js – OpportunitySyncTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsCommandTest","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsCommandTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
418894514931306631
|
-7462463714357427776
|
app_switch
|
hybrid
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelpBackend Chapter • 43 m leftec2-user@ip-10-20-6-111:~DOCKER881DEV (-zsh)APP (-zsh)|X3-zshX4Days Active: 3/3Daily Average: 265.33root@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook metrics -T 459 --from 2026-04-15 -D• ₴5* Review screenp...• X6ec2-user@ip-10-30-...X7INFOManaging webhookmetrics for date range.Date RangeConfig ID2026-04-15 to2026-04-17367Ill Range SummaryDate RangeTotal DaysOldest Data AgeTotal WebhooksDaily AverageActive Companies2026-04-15 to 2026-04-1732.0 days ago1,065,677355,225.6789iz Daily Breakdown2026-04-15:335,647 webhooks, 88 companies active2026-04-16: 671,679 webhooks, 88 companies active2026-04-17: 58,351 webhooks, 68 companies activel Company DetailsCompany 367 (Sensat - 459)Total Webhooks: 796Days Active: 3/3Daily Average: 265.33company (114 total, avg: 38)association_change: 92 total, avg: 46, active: 2 dayscreation: 3 total, avg: 1.5, active: 2 daysproperty_change: 19 total, avg: 9.5, active: 2 daysUnique properties: 4Top properties: hubspot_owner_id(12), domain(3), name(3), phone(1)deal (164 total, avg: 54.67)property_change: 164 total, avg: 54.67, active: 3 daysUnique properties: 8Top properties: notes_last_updated(134), closedate(7), dealstage(5), hs_deal_stage_probability(5), hs_manual_forecast_category(5)contact (518 total, avg: 172.67)property_change:390 total, avg: 130, active: 3 daysUnique properties: 9Top properties: hubspot_owner_id(186), firstname(35), email(35),associatedcompanyid(33), country(33)creation: 36 total, avg: 18, active: 2 daysassociation_change: 92 total, avg: 46, active: 2 daysroote67e84f80b9d1:/home/jiminny# |100% C47 8• Fri 17 Apr 10:47:311₴81ec2-user@ip-10-20-......
|
NULL
|
|
43013
|
916
|
27
|
2026-04-17T07:47:55.990093+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776412075990_m1.jpg...
|
Firefox
|
CloudWatch | us-east-2 — Work
|
1
|
us-east-2.console.aws.amazon.com/cloudwatch/home?r us-east-2.console.aws.amazon.com/cloudwatch/home?region=us-east-2#logsV2:logs-insights$3FqueryDetail$3D~(end~0~start~-3600~timeType~'RELATIVE~tz~'UTC~unit~'seconds~editorString~'fields*20*40timestamp*2c*20*40message*2c*20*40logStream*2c*20*40log*0a*7c*20filter*20*40message*20like*20*2f3364459*2f*20*0a*7c*20filter*20*40message*20not*20like*20*2fAnalytic*2f*20*7c*20filter*20*40message*20not*20like*20*2fSend*2f*0a*7c*20filter*20*40message*20not*20like*20*2fWebhook*2f*20*7c*20filter*20*40message*20not*20like*20*2fMeetingBot*2f*20*0a*7c*20limit*2010000~queryId~'0551e814-f51a-4339-8372-80d7ba4cef27~source~(~'worker~'worker-analytics~'worker-app~'worker-audio~'worker-calendar~'worker-conferences~'worker-crm-sync~'worker-default~'worker-delayed~'worker-dialers~'worker-dialers-fifo~'worker-download~'worker-emails~'worker-meeting-bot~'worker-nudges~'worker-processing-1~'worker-processing-2~'worker-processing-3~'worker-processing-4~'worker-processing-5~'worker-processing-delayed~'worker-softphone~'worker-video~'worker-video-app~'php~'php-app)~lang~'CWLI~logClass~'STANDARD~queryBy~'logGroupName)...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
Problem loading page
Problem loading page
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Symfony\Component\Debug\Exception\FatalThrowableError: 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
CloudWatch | us-east-2
CloudWatch | us-east-2
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
Console Home | Console Home | eu-west-1
Console Home | Console Home | eu-west-1
New Tab
New Tab
Meet - Backend Chapter
Mute tab
Meet - Backend Chapter
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
AWS Console Home
Skip to Main Content
Skip to Main Content
Amazon Q
Services
Search
Ask Amazon Q
[Option+S]
CloudShell
Notifications (none available)
Help & support
Settings
United States (Ohio)
United States (Ohio)
PROD
Account ID: 4103-4619-5943
PROD
EC2 EC2
EC2
Elastic Container Service Elastic Container Service
Elastic Container Service
S3 S3
S3
CodeDeploy CodeDeploy
CodeDeploy
CloudWatch CloudWatch
CloudWatch
ElastiCache ElastiCache
ElastiCache
Aurora and RDS Aurora and RDS
Aurora and RDS
Amazon OpenSearch Service Amazon OpenSearch Service
Amazon OpenSearch Service
CloudFront CloudFront
CloudFront
MediaLive MediaLive
MediaLive
Open side navigation
CloudWatch
CloudWatch
Logs Insights
Logs Insights
Query definition
Query definition
Info : Query definition
5m (5 Minutes)
30m (30 Minutes)
1h (1 Hour)
3h (3 Hours)
12h (12 Hours)
Custom
Custom
Compare (Off)
Compare
(
Off
)
Time zone UTC timezone
UTC timezone...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":4,"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,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Problem loading page","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Problem loading page","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Symfony\\Component\\Debug\\Exception\\FatalThrowableError: 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","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Symfony\\Component\\Debug\\Exception\\FatalThrowableError: 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","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Console Home | Console Home | eu-west-1","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Console Home | Console Home | eu-west-1","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Meet - Backend Chapter","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Mute tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Meet - Backend Chapter","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"AWS Console Home","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to Main Content","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to Main Content","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Amazon Q","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Services","depth":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXComboBox","text":"Search","depth":16,"role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Ask Amazon Q","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[Option+S]","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CloudShell","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Notifications (none available)","depth":16,"help_text":"Notifications","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Help & support","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Settings","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXComboBox","text":"United States (Ohio)","depth":15,"value":"United States (Ohio)","help_text":"United States (Ohio)","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"United States (Ohio)","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"PROD","depth":15,"help_text":"Production_View_Only @ jiminny","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Account ID: 4103-4619-5943","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PROD","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"EC2 EC2","depth":16,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"EC2","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Elastic Container Service Elastic Container Service","depth":16,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Elastic Container Service","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"S3 S3","depth":16,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"S3","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"CodeDeploy CodeDeploy","depth":16,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CodeDeploy","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"CloudWatch CloudWatch","depth":16,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"ElastiCache ElastiCache","depth":16,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"ElastiCache","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Aurora and RDS Aurora and RDS","depth":16,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Aurora and RDS","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Amazon OpenSearch Service Amazon OpenSearch Service","depth":16,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Amazon OpenSearch Service","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"CloudFront CloudFront","depth":16,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudFront","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"MediaLive MediaLive","depth":16,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"MediaLive","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Open side navigation","depth":13,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"CloudWatch","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Logs Insights","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Logs Insights","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Query definition","depth":26,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXButton","text":"Query definition","depth":28,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Info : Query definition","depth":27,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"5m (5 Minutes)","depth":27,"help_text":"5 Minutes","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"30m (30 Minutes)","depth":27,"help_text":"30 Minutes","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"1h (1 Hour)","depth":27,"help_text":"1 Hour","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"3h (3 Hours)","depth":27,"help_text":"3 Hours","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"12h (12 Hours)","depth":27,"help_text":"12 Hours","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Custom","depth":27,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Custom","depth":29,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Compare (Off)","depth":26,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Compare","depth":27,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":27,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Off","depth":27,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":27,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Time zone UTC timezone","depth":26,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"UTC timezone","depth":28,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-2919672317175301953
|
8806838109114970839
|
app_switch
|
accessibility
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
Problem loading page
Problem loading page
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Symfony\Component\Debug\Exception\FatalThrowableError: 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
CloudWatch | us-east-2
CloudWatch | us-east-2
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
Console Home | Console Home | eu-west-1
Console Home | Console Home | eu-west-1
New Tab
New Tab
Meet - Backend Chapter
Mute tab
Meet - Backend Chapter
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
AWS Console Home
Skip to Main Content
Skip to Main Content
Amazon Q
Services
Search
Ask Amazon Q
[Option+S]
CloudShell
Notifications (none available)
Help & support
Settings
United States (Ohio)
United States (Ohio)
PROD
Account ID: 4103-4619-5943
PROD
EC2 EC2
EC2
Elastic Container Service Elastic Container Service
Elastic Container Service
S3 S3
S3
CodeDeploy CodeDeploy
CodeDeploy
CloudWatch CloudWatch
CloudWatch
ElastiCache ElastiCache
ElastiCache
Aurora and RDS Aurora and RDS
Aurora and RDS
Amazon OpenSearch Service Amazon OpenSearch Service
Amazon OpenSearch Service
CloudFront CloudFront
CloudFront
MediaLive MediaLive
MediaLive
Open side navigation
CloudWatch
CloudWatch
Logs Insights
Logs Insights
Query definition
Query definition
Info : Query definition
5m (5 Minutes)
30m (30 Minutes)
1h (1 Hour)
3h (3 Hours)
12h (12 Hours)
Custom
Custom
Compare (Off)
Compare
(
Off
)
Time zone UTC timezone
UTC timezone...
|
NULL
|
|
43014
|
917
|
20
|
2026-04-17T07:47:55.990120+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776412075990_m2.jpg...
|
Firefox
|
CloudWatch | us-east-2 — Work
|
1
|
us-east-2.console.aws.amazon.com/cloudwatch/home?r us-east-2.console.aws.amazon.com/cloudwatch/home?region=us-east-2#logsV2:logs-insights$3FqueryDetail$3D~(end~0~start~-3600~timeType~'RELATIVE~tz~'UTC~unit~'seconds~editorString~'fields*20*40timestamp*2c*20*40message*2c*20*40logStream*2c*20*40log*0a*7c*20filter*20*40message*20like*20*2f3364459*2f*20*0a*7c*20filter*20*40message*20not*20like*20*2fAnalytic*2f*20*7c*20filter*20*40message*20not*20like*20*2fSend*2f*0a*7c*20filter*20*40message*20not*20like*20*2fWebhook*2f*20*7c*20filter*20*40message*20not*20like*20*2fMeetingBot*2f*20*0a*7c*20limit*2010000~queryId~'0551e814-f51a-4339-8372-80d7ba4cef27~source~(~'worker~'worker-analytics~'worker-app~'worker-audio~'worker-calendar~'worker-conferences~'worker-crm-sync~'worker-default~'worker-delayed~'worker-dialers~'worker-dialers-fifo~'worker-download~'worker-emails~'worker-meeting-bot~'worker-nudges~'worker-processing-1~'worker-processing-2~'worker-processing-3~'worker-processing-4~'worker-processing-5~'worker-processing-delayed~'worker-softphone~'worker-video~'worker-video-app~'php~'php-app)~lang~'CWLI~logClass~'STANDARD~queryBy~'logGroupName)...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
Problem loading page
Problem loading page
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Symfony\Component\Debug\Exception\FatalThrowableError: 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
CloudWatch | us-east-2
CloudWatch | us-east-2
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
Console Home | Console Home | eu-west-1
Console Home | Console Home | eu-west-1
New Tab
New Tab
Meet - Backend Chapter
Mute tab
Meet - Backend Chapter
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
AWS Console Home
Skip to Main Content
Skip to Main Content
Amazon Q
Services
Search
Ask Amazon Q
[Option+S]
CloudShell
Notifications (none available)
Help & support
Settings
United States (Ohio)
United States (Ohio)
PROD
Account ID: 4103-4619-5943
PROD
EC2 EC2
EC2
Elastic Container Service Elastic Container Service
Elastic Container Service
S3 S3
S3
CodeDeploy CodeDeploy
CodeDeploy
CloudWatch CloudWatch
CloudWatch
ElastiCache ElastiCache
ElastiCache
Aurora and RDS Aurora and RDS
Aurora and RDS
Amazon OpenSearch Service Amazon OpenSearch Service
Amazon OpenSearch Service
CloudFront CloudFront
CloudFront
MediaLive MediaLive
MediaLive
Open side navigation
CloudWatch
CloudWatch
Logs Insights
Logs Insights
Query definition
Query definition
Info : Query definition
5m (5 Minutes)
30m (30 Minutes)
1h (1 Hour)
3h (3 Hours)
12h (12 Hours)
Custom
Custom
Compare (Off)
Compare
(
Off
)
Time zone UTC timezone...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"bounds":{"left":0.00234375,"top":0.045138888,"width":0.0890625,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.08263889,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.015625,"top":0.09236111,"width":0.11875,"height":0.009722223},"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.11111111,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":5,"bounds":{"left":0.015625,"top":0.12083333,"width":0.11171875,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Problem loading page","depth":4,"bounds":{"left":0.0,"top":0.13958333,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Problem loading page","depth":5,"bounds":{"left":0.015625,"top":0.14930555,"width":0.04453125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Symfony\\Component\\Debug\\Exception\\FatalThrowableError: 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","depth":4,"bounds":{"left":0.0,"top":0.16805555,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Symfony\\Component\\Debug\\Exception\\FatalThrowableError: 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","depth":5,"bounds":{"left":0.015625,"top":0.17777778,"width":0.53398436,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0,"top":0.19652778,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015625,"top":0.20625,"width":0.0484375,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.07890625,"top":0.20277777,"width":0.009375,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.225,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":5,"bounds":{"left":0.015625,"top":0.23472223,"width":0.1515625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Console Home | Console Home | eu-west-1","depth":4,"bounds":{"left":0.0,"top":0.2534722,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Console Home | Console Home | eu-west-1","depth":5,"bounds":{"left":0.015625,"top":0.26319444,"width":0.0875,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.28194445,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.015625,"top":0.29166666,"width":0.017578125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Meet - Backend Chapter","depth":4,"bounds":{"left":0.0,"top":0.31041667,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Mute tab","depth":5,"bounds":{"left":0.01328125,"top":0.31666666,"width":0.009375,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Meet - Backend Chapter","depth":5,"bounds":{"left":0.0234375,"top":0.3201389,"width":0.05,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.003125,"top":0.3402778,"width":0.08710937,"height":0.022222223},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.003125,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.01640625,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.029296875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.0421875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.05546875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"AWS Console Home","depth":13,"bounds":{"left":0.09375,"top":0.047916666,"width":0.025390625,"height":0.033333335},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to Main Content","depth":13,"bounds":{"left":0.09335937,"top":0.047222223,"width":0.0015625,"height":0.0013888889},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to Main Content","depth":14,"bounds":{"left":0.09414063,"top":0.047916666,"width":0.01953125,"height":0.045138888},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Amazon Q","depth":14,"bounds":{"left":0.11953125,"top":0.047916666,"width":0.01953125,"height":0.033333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Services","depth":13,"bounds":{"left":0.1390625,"top":0.047916666,"width":0.01953125,"height":0.033333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXComboBox","text":"Search","depth":16,"bounds":{"left":0.15859374,"top":0.054166667,"width":0.2109375,"height":0.020833334},"role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Ask Amazon Q","depth":15,"bounds":{"left":0.35390624,"top":0.05625,"width":0.01171875,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[Option+S]","depth":16,"bounds":{"left":0.32851562,"top":0.058333334,"width":0.02734375,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CloudShell","depth":14,"bounds":{"left":0.78046876,"top":0.047916666,"width":0.01875,"height":0.033333335},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Notifications (none available)","depth":16,"bounds":{"left":0.7992188,"top":0.050694443,"width":0.01953125,"height":0.027777778},"help_text":"Notifications","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Help & support","depth":15,"bounds":{"left":0.81875,"top":0.047916666,"width":0.01953125,"height":0.033333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Settings","depth":15,"bounds":{"left":0.8382813,"top":0.047916666,"width":0.01953125,"height":0.033333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXComboBox","text":"United States (Ohio)","depth":15,"bounds":{"left":0.8578125,"top":0.047916666,"width":0.06328125,"height":0.033333335},"value":"United States (Ohio)","help_text":"United States (Ohio)","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"United States (Ohio)","depth":17,"bounds":{"left":0.86445314,"top":0.059722222,"width":0.04375,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"PROD","depth":15,"bounds":{"left":0.92109376,"top":0.047916666,"width":0.07890624,"height":0.033333335},"help_text":"Production_View_Only @ jiminny","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Account ID: 4103-4619-5943","depth":19,"bounds":{"left":0.92460936,"top":0.05,"width":0.06367187,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PROD","depth":18,"bounds":{"left":0.97929686,"top":0.065972224,"width":0.0125,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"EC2 EC2","depth":16,"bounds":{"left":0.096875,"top":0.083333336,"width":0.023828125,"height":0.019444445},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"EC2","depth":18,"bounds":{"left":0.109375,"top":0.088194445,"width":0.008203125,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Elastic Container Service Elastic Container Service","depth":16,"bounds":{"left":0.12070312,"top":0.083333336,"width":0.06757812,"height":0.019444445},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Elastic Container Service","depth":18,"bounds":{"left":0.13320312,"top":0.088194445,"width":0.051953126,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"S3 S3","depth":16,"bounds":{"left":0.18828125,"top":0.083333336,"width":0.02109375,"height":0.019444445},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"S3","depth":18,"bounds":{"left":0.20078126,"top":0.088194445,"width":0.00546875,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"CodeDeploy CodeDeploy","depth":16,"bounds":{"left":0.209375,"top":0.083333336,"width":0.04140625,"height":0.019444445},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CodeDeploy","depth":18,"bounds":{"left":0.221875,"top":0.088194445,"width":0.02578125,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"CloudWatch CloudWatch","depth":16,"bounds":{"left":0.25078124,"top":0.083333336,"width":0.04140625,"height":0.019444445},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch","depth":18,"bounds":{"left":0.26328126,"top":0.088194445,"width":0.02578125,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"ElastiCache ElastiCache","depth":16,"bounds":{"left":0.2921875,"top":0.083333336,"width":0.03984375,"height":0.019444445},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"ElastiCache","depth":18,"bounds":{"left":0.3046875,"top":0.088194445,"width":0.02421875,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Aurora and RDS Aurora and RDS","depth":16,"bounds":{"left":0.33203125,"top":0.083333336,"width":0.048828125,"height":0.019444445},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Aurora and RDS","depth":18,"bounds":{"left":0.34453124,"top":0.088194445,"width":0.033203125,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Amazon OpenSearch Service Amazon OpenSearch Service","depth":16,"bounds":{"left":0.38085938,"top":0.083333336,"width":0.07421875,"height":0.019444445},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Amazon OpenSearch Service","depth":18,"bounds":{"left":0.39335936,"top":0.088194445,"width":0.0609375,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"CloudFront CloudFront","depth":16,"bounds":{"left":0.45507812,"top":0.083333336,"width":0.039453126,"height":0.019444445},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudFront","depth":18,"bounds":{"left":0.4675781,"top":0.088194445,"width":0.023828125,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"MediaLive MediaLive","depth":16,"bounds":{"left":0.49453124,"top":0.083333336,"width":0.037109375,"height":0.019444445},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"MediaLive","depth":18,"bounds":{"left":0.50703126,"top":0.088194445,"width":0.021484375,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Open side navigation","depth":13,"bounds":{"left":0.1,"top":0.108333334,"width":0.01171875,"height":0.020833334},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"CloudWatch","depth":14,"bounds":{"left":0.11640625,"top":0.11111111,"width":0.031640626,"height":0.015277778},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch","depth":16,"bounds":{"left":0.1171875,"top":0.1125,"width":0.030078124,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Logs Insights","depth":14,"bounds":{"left":0.16054687,"top":0.11180556,"width":0.03359375,"height":0.013888889},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Logs Insights","depth":16,"bounds":{"left":0.16054687,"top":0.1125,"width":0.03359375,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Query definition","depth":26,"bounds":{"left":0.1125,"top":0.14513889,"width":0.059375,"height":0.017361112},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXButton","text":"Query definition","depth":28,"bounds":{"left":0.10234375,"top":0.14513889,"width":0.06953125,"height":0.017361112},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Info : Query definition","depth":27,"bounds":{"left":0.175,"top":0.15069444,"width":0.008984375,"height":0.010416667},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"5m (5 Minutes)","depth":27,"bounds":{"left":0.66445315,"top":0.14652778,"width":0.014453125,"height":0.013888889},"help_text":"5 Minutes","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"30m (30 Minutes)","depth":27,"bounds":{"left":0.68789065,"top":0.14652778,"width":0.017578125,"height":0.013888889},"help_text":"30 Minutes","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"1h (1 Hour)","depth":27,"bounds":{"left":0.7140625,"top":0.14652778,"width":0.012890625,"height":0.013888889},"help_text":"1 Hour","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"3h (3 Hours)","depth":27,"bounds":{"left":0.7359375,"top":0.14652778,"width":0.012890625,"height":0.013888889},"help_text":"3 Hours","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"12h (12 Hours)","depth":27,"bounds":{"left":0.7578125,"top":0.14652778,"width":0.015625,"height":0.013888889},"help_text":"12 Hours","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Custom","depth":27,"bounds":{"left":0.7824219,"top":0.14652778,"width":0.028515626,"height":0.013888889},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Custom","depth":29,"bounds":{"left":0.7824219,"top":0.14722222,"width":0.019140625,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Compare (Off)","depth":26,"bounds":{"left":0.81875,"top":0.14305556,"width":0.0546875,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Compare","depth":27,"bounds":{"left":0.82734376,"top":0.14791666,"width":0.023046875,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":27,"bounds":{"left":0.8503906,"top":0.14791666,"width":0.003515625,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Off","depth":27,"bounds":{"left":0.8539063,"top":0.14791666,"width":0.008984375,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":27,"bounds":{"left":0.8628906,"top":0.14791666,"width":0.001953125,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Time zone UTC timezone","depth":26,"bounds":{"left":0.8761719,"top":0.14305556,"width":0.054296874,"height":0.022222223},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
5695627724677395585
|
8806838126294840023
|
app_switch
|
accessibility
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
Problem loading page
Problem loading page
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Symfony\Component\Debug\Exception\FatalThrowableError: 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
CloudWatch | us-east-2
CloudWatch | us-east-2
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
Console Home | Console Home | eu-west-1
Console Home | Console Home | eu-west-1
New Tab
New Tab
Meet - Backend Chapter
Mute tab
Meet - Backend Chapter
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
AWS Console Home
Skip to Main Content
Skip to Main Content
Amazon Q
Services
Search
Ask Amazon Q
[Option+S]
CloudShell
Notifications (none available)
Help & support
Settings
United States (Ohio)
United States (Ohio)
PROD
Account ID: 4103-4619-5943
PROD
EC2 EC2
EC2
Elastic Container Service Elastic Container Service
Elastic Container Service
S3 S3
S3
CodeDeploy CodeDeploy
CodeDeploy
CloudWatch CloudWatch
CloudWatch
ElastiCache ElastiCache
ElastiCache
Aurora and RDS Aurora and RDS
Aurora and RDS
Amazon OpenSearch Service Amazon OpenSearch Service
Amazon OpenSearch Service
CloudFront CloudFront
CloudFront
MediaLive MediaLive
MediaLive
Open side navigation
CloudWatch
CloudWatch
Logs Insights
Logs Insights
Query definition
Query definition
Info : Query definition
5m (5 Minutes)
30m (30 Minutes)
1h (1 Hour)
3h (3 Hours)
12h (12 Hours)
Custom
Custom
Compare (Off)
Compare
(
Off
)
Time zone UTC timezone...
|
NULL
|
|
43015
|
916
|
28
|
2026-04-17T07:47:56.747175+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776412076747_m1.jpg...
|
PhpStorm
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
FirefoxFileEditViewHistoryBookmarksProfilesToolsWi FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelpBackend Chapter • 43 m leftec2-user@ip-10-20-6-111:~DOCKER- 881DEV (-zsh)APP (-zsh)|X3-zshX4Days Active: 3/3Daily Average: 265.33root@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook metrics -T 459 --from 2026-04-15 -D• ₴5* Review screenp...• X6ec2-user@ip-10-30-...X7INFOManaging webhookmetrics for date range.Date RangeConfig ID2026-04-15 to2026-04-17367Ill Range SummaryDate RangeTotal DaysOldest Data AgeTotal WebhooksDaily AverageActive Companies2026-04-15 to 2026-04-1732.0 days ago1,065,677355,225.6789iz Daily Breakdown2026-04-15:335,647 webhooks, 88 companies active2026-04-16: 671,679 webhooks, 88 companies active2026-04-17: 58,351 webhooks, 68 companies activel Company DetailsCompany 367 (Sensat - 459)Total Webhooks: 796Days Active: 3/3Daily Average: 265.33company (114 total, avg: 38)association_change: 92 total, avg: 46, active: 2 dayscreation: 3 total, avg: 1.5, active: 2 daysproperty_change: 19 total, avg: 9.5, active: 2 daysUnique properties: 4Top properties: hubspot_owner_id(12), domain(3), name(3), phone(1)deal (164 total, avg: 54.67)property_change: 164 total, avg: 54.67, active: 3 daysUnique properties: 8Top properties: notes_last_updated(134), closedate(7), dealstage(5), hs_deal_stage_probability(5), hs_manual_forecast_category(5)contact (518 total, avg: 172.67)property_change:390 total, avg: 130, active: 3 daysUnique properties: 9Top properties: hubspot_owner_id(186), firstname(35), email(35),associatedcompanyid(33), country(33)creation: 36 total, avg: 18, active: 2 daysassociation_change: 92 total, avg: 46, active: 2 daysroote67e84f80b9d1:/home/jiminny# |100% 147 8• Fri 17 Apr 10:47:561₴81ec2-user@ip-10-20-......
|
NULL
|
3040188451571525082
|
NULL
|
app_switch
|
ocr
|
NULL
|
FirefoxFileEditViewHistoryBookmarksProfilesToolsWi FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelpBackend Chapter • 43 m leftec2-user@ip-10-20-6-111:~DOCKER- 881DEV (-zsh)APP (-zsh)|X3-zshX4Days Active: 3/3Daily Average: 265.33root@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook metrics -T 459 --from 2026-04-15 -D• ₴5* Review screenp...• X6ec2-user@ip-10-30-...X7INFOManaging webhookmetrics for date range.Date RangeConfig ID2026-04-15 to2026-04-17367Ill Range SummaryDate RangeTotal DaysOldest Data AgeTotal WebhooksDaily AverageActive Companies2026-04-15 to 2026-04-1732.0 days ago1,065,677355,225.6789iz Daily Breakdown2026-04-15:335,647 webhooks, 88 companies active2026-04-16: 671,679 webhooks, 88 companies active2026-04-17: 58,351 webhooks, 68 companies activel Company DetailsCompany 367 (Sensat - 459)Total Webhooks: 796Days Active: 3/3Daily Average: 265.33company (114 total, avg: 38)association_change: 92 total, avg: 46, active: 2 dayscreation: 3 total, avg: 1.5, active: 2 daysproperty_change: 19 total, avg: 9.5, active: 2 daysUnique properties: 4Top properties: hubspot_owner_id(12), domain(3), name(3), phone(1)deal (164 total, avg: 54.67)property_change: 164 total, avg: 54.67, active: 3 daysUnique properties: 8Top properties: notes_last_updated(134), closedate(7), dealstage(5), hs_deal_stage_probability(5), hs_manual_forecast_category(5)contact (518 total, avg: 172.67)property_change:390 total, avg: 130, active: 3 daysUnique properties: 9Top properties: hubspot_owner_id(186), firstname(35), email(35),associatedcompanyid(33), country(33)creation: 36 total, avg: 18, active: 2 daysassociation_change: 92 total, avg: 46, active: 2 daysroote67e84f80b9d1:/home/jiminny# |100% 147 8• Fri 17 Apr 10:47:561₴81ec2-user@ip-10-20-......
|
NULL
|
|
43027
|
916
|
34
|
2026-04-17T07:48:13.455964+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776412093455_m1.jpg...
|
Firefox
|
CloudWatch | us-east-2 — Work
|
1
|
us-east-2.console.aws.amazon.com/cloudwatch/home?r us-east-2.console.aws.amazon.com/cloudwatch/home?region=us-east-2#logsV2:logs-insights$3FqueryDetail$3D~(end~0~start~-3600~timeType~'RELATIVE~tz~'UTC~unit~'seconds~editorString~'fields*20*40timestamp*2c*20*40message*2c*20*40logStream*2c*20*40log*0a*7c*20filter*20*40message*20like*20*2f3364459*2f*20*0a*7c*20filter*20*40message*20not*20like*20*2fAnalytic*2f*20*7c*20filter*20*40message*20not*20like*20*2fSend*2f*0a*7c*20filter*20*40message*20not*20like*20*2fWebhook*2f*20*7c*20filter*20*40message*20not*20like*20*2fMeetingBot*2f*20*0a*7c*20limit*2010000~queryId~'0551e814-f51a-4339-8372-80d7ba4cef27~source~(~'worker~'worker-analytics~'worker-app~'worker-audio~'worker-calendar~'worker-conferences~'worker-crm-sync~'worker-default~'worker-delayed~'worker-dialers~'worker-dialers-fifo~'worker-download~'worker-emails~'worker-meeting-bot~'worker-nudges~'worker-processing-1~'worker-processing-2~'worker-processing-3~'worker-processing-4~'worker-processing-5~'worker-processing-delayed~'worker-softphone~'worker-video~'worker-video-app~'php~'php-app)~lang~'CWLI~logClass~'STANDARD~queryBy~'logGroupName)...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
Problem loading page
Problem loading page
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Symfony\Component\Debug\Exception\FatalThrowableError: 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
CloudWatch | us-east-2
CloudWatch | us-east-2
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
Console Home | Console Home | eu-west-1
Console Home | Console Home | eu-west-1
New Tab
New Tab
Meet - Backend Chapter
Mute tab
Meet - Backend Chapter
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
AWS Console Home
Skip to Main Content
Skip to Main Content
Amazon Q
Services
Search
Ask Amazon Q
[Option+S]
CloudShell
Notifications (none available)
Help & support
Settings
United States (Ohio)
United States (Ohio)
PROD
Account ID: 4103-4619-5943
PROD
EC2 EC2
EC2
Elastic Container Service Elastic Container Service
Elastic Container Service
S3 S3
S3
CodeDeploy CodeDeploy
CodeDeploy
CloudWatch CloudWatch
CloudWatch
ElastiCache ElastiCache
ElastiCache
Aurora and RDS Aurora and RDS
Aurora and RDS
Amazon OpenSearch Service Amazon OpenSearch Service
Amazon OpenSearch Service
CloudFront CloudFront
CloudFront
MediaLive MediaLive
MediaLive
Open side navigation
CloudWatch
CloudWatch...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":4,"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,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Problem loading page","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Problem loading page","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Symfony\\Component\\Debug\\Exception\\FatalThrowableError: 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","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Symfony\\Component\\Debug\\Exception\\FatalThrowableError: 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","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Console Home | Console Home | eu-west-1","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Console Home | Console Home | eu-west-1","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Meet - Backend Chapter","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Mute tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Meet - Backend Chapter","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"AWS Console Home","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to Main Content","depth":13,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to Main Content","depth":14,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Amazon Q","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Services","depth":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXComboBox","text":"Search","depth":16,"role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Ask Amazon Q","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[Option+S]","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CloudShell","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Notifications (none available)","depth":16,"help_text":"Notifications","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Help & support","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Settings","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXComboBox","text":"United States (Ohio)","depth":15,"value":"United States (Ohio)","help_text":"United States (Ohio)","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"United States (Ohio)","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"PROD","depth":15,"help_text":"Production_View_Only @ jiminny","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Account ID: 4103-4619-5943","depth":19,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PROD","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"EC2 EC2","depth":16,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"EC2","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Elastic Container Service Elastic Container Service","depth":16,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Elastic Container Service","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"S3 S3","depth":16,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"S3","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"CodeDeploy CodeDeploy","depth":16,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CodeDeploy","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"CloudWatch CloudWatch","depth":16,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"ElastiCache ElastiCache","depth":16,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"ElastiCache","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Aurora and RDS Aurora and RDS","depth":16,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Aurora and RDS","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Amazon OpenSearch Service Amazon OpenSearch Service","depth":16,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Amazon OpenSearch Service","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"CloudFront CloudFront","depth":16,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudFront","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"MediaLive MediaLive","depth":16,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"MediaLive","depth":18,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Open side navigation","depth":13,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"CloudWatch","depth":14,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
3156837798831877001
|
-416534030819020073
|
app_switch
|
accessibility
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
Problem loading page
Problem loading page
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Symfony\Component\Debug\Exception\FatalThrowableError: 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
CloudWatch | us-east-2
CloudWatch | us-east-2
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
Console Home | Console Home | eu-west-1
Console Home | Console Home | eu-west-1
New Tab
New Tab
Meet - Backend Chapter
Mute tab
Meet - Backend Chapter
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
AWS Console Home
Skip to Main Content
Skip to Main Content
Amazon Q
Services
Search
Ask Amazon Q
[Option+S]
CloudShell
Notifications (none available)
Help & support
Settings
United States (Ohio)
United States (Ohio)
PROD
Account ID: 4103-4619-5943
PROD
EC2 EC2
EC2
Elastic Container Service Elastic Container Service
Elastic Container Service
S3 S3
S3
CodeDeploy CodeDeploy
CodeDeploy
CloudWatch CloudWatch
CloudWatch
ElastiCache ElastiCache
ElastiCache
Aurora and RDS Aurora and RDS
Aurora and RDS
Amazon OpenSearch Service Amazon OpenSearch Service
Amazon OpenSearch Service
CloudFront CloudFront
CloudFront
MediaLive MediaLive
MediaLive
Open side navigation
CloudWatch
CloudWatch...
|
NULL
|
|
43028
|
917
|
27
|
2026-04-17T07:48:13.479343+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776412093479_m2.jpg...
|
Firefox
|
CloudWatch | us-east-2 — Work
|
1
|
us-east-2.console.aws.amazon.com/cloudwatch/home?r us-east-2.console.aws.amazon.com/cloudwatch/home?region=us-east-2#logsV2:logs-insights$3FqueryDetail$3D~(end~0~start~-3600~timeType~'RELATIVE~tz~'UTC~unit~'seconds~editorString~'fields*20*40timestamp*2c*20*40message*2c*20*40logStream*2c*20*40log*0a*7c*20filter*20*40message*20like*20*2f3364459*2f*20*0a*7c*20filter*20*40message*20not*20like*20*2fAnalytic*2f*20*7c*20filter*20*40message*20not*20like*20*2fSend*2f*0a*7c*20filter*20*40message*20not*20like*20*2fWebhook*2f*20*7c*20filter*20*40message*20not*20like*20*2fMeetingBot*2f*20*0a*7c*20limit*2010000~queryId~'0551e814-f51a-4339-8372-80d7ba4cef27~source~(~'worker~'worker-analytics~'worker-app~'worker-audio~'worker-calendar~'worker-conferences~'worker-crm-sync~'worker-default~'worker-delayed~'worker-dialers~'worker-dialers-fifo~'worker-download~'worker-emails~'worker-meeting-bot~'worker-nudges~'worker-processing-1~'worker-processing-2~'worker-processing-3~'worker-processing-4~'worker-processing-5~'worker-processing-delayed~'worker-softphone~'worker-video~'worker-video-app~'php~'php-app)~lang~'CWLI~logClass~'STANDARD~queryBy~'logGroupName)...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
Problem loading page
Problem loading page
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Symfony\Component\Debug\Exception\FatalThrowableError: 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
CloudWatch | us-east-2
CloudWatch | us-east-2
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
Console Home | Console Home | eu-west-1
Console Home | Console Home | eu-west-1
New Tab
New Tab
Meet - Backend Chapter
Mute tab
Meet - Backend Chapter
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
AWS Console Home
Skip to Main Content
Skip to Main Content
Amazon Q
Services
Search
Ask Amazon Q
[Option+S]
CloudShell
Notifications (none available)
Help & support
Settings
United States (Ohio)
United States (Ohio)
PROD
Account ID: 4103-4619-5943
PROD
EC2 EC2
EC2
Elastic Container Service Elastic Container Service
Elastic Container Service
S3 S3
S3
CodeDeploy CodeDeploy
CodeDeploy
CloudWatch CloudWatch
CloudWatch
ElastiCache ElastiCache
ElastiCache
Aurora and RDS Aurora and RDS
Aurora and RDS
Amazon OpenSearch Service Amazon OpenSearch Service
Amazon OpenSearch Service
CloudFront CloudFront
CloudFront
MediaLive MediaLive
MediaLive
Open side navigation
CloudWatch
CloudWatch
Logs Insights
Logs Insights
Query definition
Query definition
Info : Query definition
5m (5 Minutes)
30m (30 Minutes)
1h (1 Hour)
3h (3 Hours)
12h (12 Hours)
Custom
Custom
Compare (Off)
Compare
(
Off
)
Time zone UTC timezone
UTC timezone...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"bounds":{"left":0.00234375,"top":0.045138888,"width":0.0890625,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.08263889,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.015625,"top":0.09236111,"width":0.11875,"height":0.009722223},"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.11111111,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":5,"bounds":{"left":0.015625,"top":0.12083333,"width":0.11171875,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Problem loading page","depth":4,"bounds":{"left":0.0,"top":0.13958333,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Problem loading page","depth":5,"bounds":{"left":0.015625,"top":0.14930555,"width":0.04453125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Symfony\\Component\\Debug\\Exception\\FatalThrowableError: 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","depth":4,"bounds":{"left":0.0,"top":0.16805555,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Symfony\\Component\\Debug\\Exception\\FatalThrowableError: 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","depth":5,"bounds":{"left":0.015625,"top":0.17777778,"width":0.53398436,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0,"top":0.19652778,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015625,"top":0.20625,"width":0.0484375,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.07890625,"top":0.20277777,"width":0.009375,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.225,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":5,"bounds":{"left":0.015625,"top":0.23472223,"width":0.1515625,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Console Home | Console Home | eu-west-1","depth":4,"bounds":{"left":0.0,"top":0.2534722,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Console Home | Console Home | eu-west-1","depth":5,"bounds":{"left":0.015625,"top":0.26319444,"width":0.0875,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.28194445,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.015625,"top":0.29166666,"width":0.017578125,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Meet - Backend Chapter","depth":4,"bounds":{"left":0.0,"top":0.31041667,"width":0.09375,"height":0.028472222},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Mute tab","depth":5,"bounds":{"left":0.01328125,"top":0.31666666,"width":0.009375,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Meet - Backend Chapter","depth":5,"bounds":{"left":0.0234375,"top":0.3201389,"width":0.05,"height":0.009722223},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.003125,"top":0.3402778,"width":0.08710937,"height":0.022222223},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.003125,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.01640625,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.029296875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.0421875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.05546875,"top":0.97430557,"width":0.0125,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"AWS Console Home","depth":13,"bounds":{"left":0.09375,"top":0.047916666,"width":0.025390625,"height":0.033333335},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to Main Content","depth":13,"bounds":{"left":0.09335937,"top":0.047222223,"width":0.0015625,"height":0.0013888889},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to Main Content","depth":14,"bounds":{"left":0.09414063,"top":0.047916666,"width":0.01953125,"height":0.045138888},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Amazon Q","depth":14,"bounds":{"left":0.11953125,"top":0.047916666,"width":0.01953125,"height":0.033333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Services","depth":13,"bounds":{"left":0.1390625,"top":0.047916666,"width":0.01953125,"height":0.033333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXComboBox","text":"Search","depth":16,"bounds":{"left":0.15859374,"top":0.054166667,"width":0.2109375,"height":0.020833334},"role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Ask Amazon Q","depth":15,"bounds":{"left":0.35390624,"top":0.05625,"width":0.01171875,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[Option+S]","depth":16,"bounds":{"left":0.32851562,"top":0.058333334,"width":0.02734375,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CloudShell","depth":14,"bounds":{"left":0.78046876,"top":0.047916666,"width":0.01875,"height":0.033333335},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Notifications (none available)","depth":16,"bounds":{"left":0.7992188,"top":0.050694443,"width":0.01953125,"height":0.027777778},"help_text":"Notifications","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Help & support","depth":15,"bounds":{"left":0.81875,"top":0.047916666,"width":0.01953125,"height":0.033333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Settings","depth":15,"bounds":{"left":0.8382813,"top":0.047916666,"width":0.01953125,"height":0.033333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXComboBox","text":"United States (Ohio)","depth":15,"bounds":{"left":0.8578125,"top":0.047916666,"width":0.06328125,"height":0.033333335},"value":"United States (Ohio)","help_text":"United States (Ohio)","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"United States (Ohio)","depth":17,"bounds":{"left":0.86445314,"top":0.059722222,"width":0.04375,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"PROD","depth":15,"bounds":{"left":0.92109376,"top":0.047916666,"width":0.07890624,"height":0.033333335},"help_text":"Production_View_Only @ jiminny","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Account ID: 4103-4619-5943","depth":19,"bounds":{"left":0.92460936,"top":0.05,"width":0.06367187,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PROD","depth":18,"bounds":{"left":0.97929686,"top":0.065972224,"width":0.0125,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"EC2 EC2","depth":16,"bounds":{"left":0.096875,"top":0.083333336,"width":0.023828125,"height":0.019444445},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"EC2","depth":18,"bounds":{"left":0.109375,"top":0.088194445,"width":0.008203125,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Elastic Container Service Elastic Container Service","depth":16,"bounds":{"left":0.12070312,"top":0.083333336,"width":0.06757812,"height":0.019444445},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Elastic Container Service","depth":18,"bounds":{"left":0.13320312,"top":0.088194445,"width":0.051953126,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"S3 S3","depth":16,"bounds":{"left":0.18828125,"top":0.083333336,"width":0.02109375,"height":0.019444445},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"S3","depth":18,"bounds":{"left":0.20078126,"top":0.088194445,"width":0.00546875,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"CodeDeploy CodeDeploy","depth":16,"bounds":{"left":0.209375,"top":0.083333336,"width":0.04140625,"height":0.019444445},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CodeDeploy","depth":18,"bounds":{"left":0.221875,"top":0.088194445,"width":0.02578125,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"CloudWatch CloudWatch","depth":16,"bounds":{"left":0.25078124,"top":0.083333336,"width":0.04140625,"height":0.019444445},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch","depth":18,"bounds":{"left":0.26328126,"top":0.088194445,"width":0.02578125,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"ElastiCache ElastiCache","depth":16,"bounds":{"left":0.2921875,"top":0.083333336,"width":0.03984375,"height":0.019444445},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"ElastiCache","depth":18,"bounds":{"left":0.3046875,"top":0.088194445,"width":0.02421875,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Aurora and RDS Aurora and RDS","depth":16,"bounds":{"left":0.33203125,"top":0.083333336,"width":0.048828125,"height":0.019444445},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Aurora and RDS","depth":18,"bounds":{"left":0.34453124,"top":0.088194445,"width":0.033203125,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Amazon OpenSearch Service Amazon OpenSearch Service","depth":16,"bounds":{"left":0.38085938,"top":0.083333336,"width":0.07421875,"height":0.019444445},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Amazon OpenSearch Service","depth":18,"bounds":{"left":0.39335936,"top":0.088194445,"width":0.0609375,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"CloudFront CloudFront","depth":16,"bounds":{"left":0.45507812,"top":0.083333336,"width":0.039453126,"height":0.019444445},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudFront","depth":18,"bounds":{"left":0.4675781,"top":0.088194445,"width":0.023828125,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"MediaLive MediaLive","depth":16,"bounds":{"left":0.49453124,"top":0.083333336,"width":0.037109375,"height":0.019444445},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"MediaLive","depth":18,"bounds":{"left":0.50703126,"top":0.088194445,"width":0.021484375,"height":0.010416667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Open side navigation","depth":13,"bounds":{"left":0.1,"top":0.108333334,"width":0.01171875,"height":0.020833334},"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"CloudWatch","depth":14,"bounds":{"left":0.11640625,"top":0.11111111,"width":0.031640626,"height":0.015277778},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch","depth":16,"bounds":{"left":0.1171875,"top":0.1125,"width":0.030078124,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Logs Insights","depth":14,"bounds":{"left":0.16054687,"top":0.11180556,"width":0.03359375,"height":0.013888889},"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Logs Insights","depth":16,"bounds":{"left":0.16054687,"top":0.1125,"width":0.03359375,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Query definition","depth":26,"bounds":{"left":0.1125,"top":0.14513889,"width":0.059375,"height":0.017361112},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXButton","text":"Query definition","depth":28,"bounds":{"left":0.10234375,"top":0.14513889,"width":0.06953125,"height":0.017361112},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Info : Query definition","depth":27,"bounds":{"left":0.175,"top":0.15069444,"width":0.008984375,"height":0.010416667},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"5m (5 Minutes)","depth":27,"bounds":{"left":0.66445315,"top":0.14652778,"width":0.014453125,"height":0.013888889},"help_text":"5 Minutes","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"30m (30 Minutes)","depth":27,"bounds":{"left":0.68789065,"top":0.14652778,"width":0.017578125,"height":0.013888889},"help_text":"30 Minutes","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"1h (1 Hour)","depth":27,"bounds":{"left":0.7140625,"top":0.14652778,"width":0.012890625,"height":0.013888889},"help_text":"1 Hour","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"3h (3 Hours)","depth":27,"bounds":{"left":0.7359375,"top":0.14652778,"width":0.012890625,"height":0.013888889},"help_text":"3 Hours","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"12h (12 Hours)","depth":27,"bounds":{"left":0.7578125,"top":0.14652778,"width":0.015625,"height":0.013888889},"help_text":"12 Hours","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Custom","depth":27,"bounds":{"left":0.7824219,"top":0.14652778,"width":0.028515626,"height":0.013888889},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Custom","depth":29,"bounds":{"left":0.7824219,"top":0.14722222,"width":0.019140625,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Compare (Off)","depth":26,"bounds":{"left":0.81875,"top":0.14305556,"width":0.0546875,"height":0.022222223},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Compare","depth":27,"bounds":{"left":0.82734376,"top":0.14791666,"width":0.023046875,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":27,"bounds":{"left":0.8503906,"top":0.14791666,"width":0.003515625,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Off","depth":27,"bounds":{"left":0.8539063,"top":0.14791666,"width":0.008984375,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":27,"bounds":{"left":0.8628906,"top":0.14791666,"width":0.001953125,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Time zone UTC timezone","depth":26,"bounds":{"left":0.8761719,"top":0.14305556,"width":0.054296874,"height":0.022222223},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"UTC timezone","depth":28,"bounds":{"left":0.88125,"top":0.14791666,"width":0.034765624,"height":0.0125},"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-2919672317175301953
|
8806838109114970839
|
app_switch
|
accessibility
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
Problem loading page
Problem loading page
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Symfony\Component\Debug\Exception\FatalThrowableError: 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
CloudWatch | us-east-2
CloudWatch | us-east-2
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Configure SSH access to multiple environment - Engineering - Confluence
Console Home | Console Home | eu-west-1
Console Home | Console Home | eu-west-1
New Tab
New Tab
Meet - Backend Chapter
Mute tab
Meet - Backend Chapter
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
AWS Console Home
Skip to Main Content
Skip to Main Content
Amazon Q
Services
Search
Ask Amazon Q
[Option+S]
CloudShell
Notifications (none available)
Help & support
Settings
United States (Ohio)
United States (Ohio)
PROD
Account ID: 4103-4619-5943
PROD
EC2 EC2
EC2
Elastic Container Service Elastic Container Service
Elastic Container Service
S3 S3
S3
CodeDeploy CodeDeploy
CodeDeploy
CloudWatch CloudWatch
CloudWatch
ElastiCache ElastiCache
ElastiCache
Aurora and RDS Aurora and RDS
Aurora and RDS
Amazon OpenSearch Service Amazon OpenSearch Service
Amazon OpenSearch Service
CloudFront CloudFront
CloudFront
MediaLive MediaLive
MediaLive
Open side navigation
CloudWatch
CloudWatch
Logs Insights
Logs Insights
Query definition
Query definition
Info : Query definition
5m (5 Minutes)
30m (30 Minutes)
1h (1 Hour)
3h (3 Hours)
12h (12 Hours)
Custom
Custom
Compare (Off)
Compare
(
Off
)
Time zone UTC timezone
UTC timezone...
|
NULL
|
|
43135
|
918
|
42
|
2026-04-17T07:54:38.499522+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776412478499_m1.jpg...
|
Firefox
|
SQLite Web: db.sqlite — Personal
|
1
|
http://100.73.206.126:8767/frames_fts/
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
DXP4800PLUS-B5F8
Steam Account Verification - [EMA DXP4800PLUS-B5F8
Steam Account Verification - [EMAIL] - Gmail
Western Digital Red Plus 3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX) от 238,97 € (467,38 лв.) Вътрешен хард диск Western Digital - Pazaruvaj.com
Western Digital Red Plus 3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX) от 238,97 € (467,38 лв.) Вътрешен хард диск Western Digital - Pazaruvaj.com
| Senetic
| Senetic
Твърд диск, Western Digital Red 6TB Plus ( 3.5", 256MB, 5400
Твърд диск, Western Digital Red 6TB Plus ( 3.5", 256MB, 5400
SQLite Web: db.sqlite
SQLite Web: db.sqlite
Close tab
Screenpipe Dashboard
Screenpipe Dashboard
Welcome to Steam
Welcome to Steam
YouTube
YouTube
New Tab
New Tab
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
Temporary chat
PLUS
PLUS
Conversation with Gemini
Conversation with Gemini
Hi Lukáš
Where should we start?
Where should we start?
🖼️ Create image, button, tap to use tool
🖼️ Create image
🎸 Create music, button, tap to use tool
🎸 Create music
Boost my day, button, tap to use tool
Boost my day
Help me learn, button, tap to use tool...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"DXP4800PLUS-B5F8","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Steam Account Verification - kovaliklukas@gmail.com - Gmail","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Western Digital Red Plus 3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX) от 238,97 € (467,38 лв.) Вътрешен хард диск Western Digital - Pazaruvaj.com","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Western Digital Red Plus 3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX) от 238,97 € (467,38 лв.) Вътрешен хард диск Western Digital - Pazaruvaj.com","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"| Senetic","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"| Senetic","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Твърд диск, Western Digital Red 6TB Plus ( 3.5\", 256MB, 5400","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Твърд диск, Western Digital Red 6TB Plus ( 3.5\", 256MB, 5400","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SQLite Web: db.sqlite","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"SQLite Web: db.sqlite","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Screenpipe Dashboard","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Screenpipe Dashboard","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Welcome to Steam","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Welcome to Steam","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"YouTube","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"YouTube","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bitwarden","depth":6,"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,"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,"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,"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,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New chat","depth":12,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Gemini","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Temporary chat","depth":12,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"PLUS","depth":11,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"PLUS","depth":13,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Hi Lukáš","depth":23,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Where should we start?","depth":22,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Where should we start?","depth":24,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"🖼️ Create image, button, tap to use tool","depth":22,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"🖼️ Create image","depth":24,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"🎸 Create music, button, tap to use tool","depth":22,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"🎸 Create music","depth":24,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Boost my day, button, tap to use tool","depth":22,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Boost my day","depth":24,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Help me learn, button, tap to use tool","depth":22,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-8324480675119053289
|
-3716590350432924989
|
app_switch
|
accessibility
|
NULL
|
DXP4800PLUS-B5F8
Steam Account Verification - [EMA DXP4800PLUS-B5F8
Steam Account Verification - [EMAIL] - Gmail
Western Digital Red Plus 3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX) от 238,97 € (467,38 лв.) Вътрешен хард диск Western Digital - Pazaruvaj.com
Western Digital Red Plus 3.5 6TB 5400rpm 256MB SATA3 (WD60EFPX) от 238,97 € (467,38 лв.) Вътрешен хард диск Western Digital - Pazaruvaj.com
| Senetic
| Senetic
Твърд диск, Western Digital Red 6TB Plus ( 3.5", 256MB, 5400
Твърд диск, Western Digital Red 6TB Plus ( 3.5", 256MB, 5400
SQLite Web: db.sqlite
SQLite Web: db.sqlite
Close tab
Screenpipe Dashboard
Screenpipe Dashboard
Welcome to Steam
Welcome to Steam
YouTube
YouTube
New Tab
New Tab
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
Temporary chat
PLUS
PLUS
Conversation with Gemini
Conversation with Gemini
Hi Lukáš
Where should we start?
Where should we start?
🖼️ Create image, button, tap to use tool
🖼️ Create image
🎸 Create music, button, tap to use tool
🎸 Create music
Boost my day, button, tap to use tool
Boost my day
Help me learn, button, tap to use tool...
|
NULL
|
|
43176
|
920
|
4
|
2026-04-17T07:57:08.696571+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776412628696_m1.jpg...
|
Firefox
|
Meet - Backend Chapter — Work
|
1
|
meet.google.com/gjc-ikxu-wxu?authuser=lukas.kovali meet.google.com/gjc-ikxu-wxu?authuser=lukas.kovalik%40jiminny.com...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Close tab
[SRD-6793] Les Mills activity types not pulling in - Jira
Close tab
Problem loading page
Close tab
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Close tab
CloudWatch | us-east-2
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Console Home | Console Home | eu-west-1
Close tab
New Tab
Close tab
Meet - Backend Chapter
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
Nikolay Nikolov (Presenting, annotating)
Nikolay Nikolov (Presenting, annotating)
People
3
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"bounds":{"left":0.0,"top":0.072222225,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.13222222,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.13222222,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":4,"bounds":{"left":0.0,"top":0.17777778,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.17777778,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Problem loading page","depth":4,"bounds":{"left":0.0,"top":0.22333333,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.22333333,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Symfony\\Component\\Debug\\Exception\\FatalThrowableError: 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","depth":4,"bounds":{"left":0.0,"top":0.2688889,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.2688889,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0,"top":0.31444445,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.31444445,"width":0.010416667,"height":0.016666668},"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.36,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.36,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Console Home | Console Home | eu-west-1","depth":4,"bounds":{"left":0.0,"top":0.40555555,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.40555555,"width":0.010416667,"height":0.016666668},"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.4511111,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.4511111,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Meet - Backend Chapter","depth":4,"bounds":{"left":0.0,"top":0.49666667,"width":0.033680554,"height":0.045555554},"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.49666667,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.005902778,"top":0.54444444,"width":0.022222223,"height":0.035555556},"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},"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},"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},"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},"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},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Nikolay Nikolov (Presenting, annotating)","depth":12,"bounds":{"left":0.07534722,"top":0.101111114,"width":0.17916666,"height":0.022222223},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Nikolov (Presenting, annotating)","depth":13,"bounds":{"left":0.07534722,"top":0.10222222,"width":0.17916666,"height":0.020555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"People","depth":15,"bounds":{"left":0.88680553,"top":0.08944444,"width":0.04097222,"height":0.04},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"3","depth":22,"bounds":{"left":0.9145833,"top":0.101111114,"width":0.0048611113,"height":0.017222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Take notes with Gemini","depth":14,"bounds":{"left":0.93333334,"top":0.08944444,"width":0.025,"height":0.04},"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":17,"bounds":{"left":0.9361111,"top":0.101111114,"width":0.06388891,"height":0.017222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini","depth":22,"bounds":{"left":0.96666664,"top":0.101111114,"width":0.028125,"height":0.017222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Gemini","depth":21,"bounds":{"left":0.96458334,"top":0.090555556,"width":0.023611112,"height":0.037777778},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-3862460716810854562
|
9092900982803084018
|
app_switch
|
hybrid
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Close tab
[SRD-6793] Les Mills activity types not pulling in - Jira
Close tab
Problem loading page
Close tab
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Close tab
CloudWatch | us-east-2
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Console Home | Console Home | eu-west-1
Close tab
New Tab
Close tab
Meet - Backend Chapter
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
Nikolay Nikolov (Presenting, annotating)
Nikolay Nikolov (Presenting, annotating)
People
3
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelp0abl→Cmeet.google.com/gjc-ikxu-wxu?authuser=lukas.kovalik%40jiminny.comNikolay Nikolov (Presenting, annotating)Backend Chapter • 33 m left100% <478 • Fri 17 Apr 10:57:083+PhoStorm -FleAT AccountTransformer.phpKeranei haraeio$ Debug-opportunityWiroowDebug opportunity stagesProject v€ Account.php 15.04.26, 1055, 9.14 kвActivity php© Address.php 23.03.26, 10.59, 25 kAiPrompt,php 23.03.26, 10:59, 2.54 kL©CrmEntityRepository.phpOpportunity.php© Lead phpclass Lead extends Model implementsprotected static function boot(): voidstatie::updated(static function (Lead Slead): void (COLEaU-2HaSCnangeo (SELT:EKEANUEA,KELEVANI_PIELUSY1event (new LeadUpdated(Steas)):© AutomatedReport.php 3.04.26, 139© AutomatedReportResult.php 23.03>):© Calendar php 23.03.26, 10.59, 5.17 k3© Callimport. php 23.03.26, 10.59,4.3 ka© CoachingF-eedback,php 23.03.20,1©CoschingFeedbackVisibllity.php 23protected function casts(): array© CoachingSection,php 23.03.26, 10.51© CoschingSectionCriterion.php 23.0© CoachingSectionOriterionF eedback©CoachingSectionF-eedback.php 23192© CommentAbstract.php 23.03.26,10return ['stage_id' = "integer",'converted_at' »>"datetine"'stage_updated_at' a> "datetine'.Accept Reject® Commentinterface,.php 23.03.26, 10© Contact.php 15.04.26, 10-55, 10.17 ka"renotely_created_at' a "datetine',© Device.php 23.03.26, 10.59, 3.57 k8© EmalMessage.php 23.03.26, 10:59, 9197© GenericAPrompt.php 23.03.26, 10.5© Group. php 23.03.26, 10:59, 14.29 kBpooeic rundcion dettesdsu ges cantrtoutelnbox.php 15.04.26, 1055, 5.86 кk© nboxÉmal.php 15.04.26, 10.55, 6.11 k©InboxEmalBatch.php 30.03.24, 10:30282© Invitation,php 23.03.26, 10.39, 6.88 kl© JobLog.php 23.03.26, 10-59, 2.15 kлJobTide.php 23.03.26, 10.59, 2.98 kBGet the uni to lead phote.© Language,.php 23.03.26, 10:59, 2,51 kJ© LanguageOlalect.php 23.03.26, 10.51207public function getPhotoUrlAttribute(): string© Lead.pho 13.042208©MobileSetting, php 23.03.26, 10.59, 1209© Model,php 23.03.26, 10.59, 1.88 k8218© Moment.php 23.03.26, 1059,2.78 k8213© Nudge.php 23.03.26, 10.59,7A1k8212SphotoPath = Sthis->photo_path;Il If tean photo_path is already a fully qualified URL1f (preg_natch(*/*https?:V\//S*, SphotoPath)) (SphotoUrl = SphotoPath;j else (© NudgeRun,php 23.03.26, 10.59, 3.46© Opportunity.php 17.04.28, 10.52, 16.0214© Participant.php 15.04.26, 10.55, 18.45I/ Prepend cdn base unl to photo uns1ed: Sphpto v Nocept Rla xul coobipernheBaca) : + 2 ot210es →© Partner.php 31.63.26, M.02, 5.55 kBDarmieeiAA AhA 19A1.M 1066 119LJminny(ModelsCastsDmerepooeoonl snaro you kereenOapp › app › Repositories › Crm › CrmÊntityRepository.php › © CrmEntityRepository › ® a impoUOpportunitySyncTest.testResolveAcoountidCascadeVbratsuroosRead Lead.php #L189-208The Lead model has the same issue. Let me fix it as well:0 Lead.phpFri 17 Apr 10:67+0 .mohondMNikolay NikoloyBut, what caused the bug recentClaude Opus 4.5192:1SymfonyUF UTF-8Lukas Kovalik10:57 AM | Backend Chapter...
|
NULL
|
|
43178
|
920
|
5
|
2026-04-17T07:57:10.838649+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776412630838_m1.jpg...
|
Firefox
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
FirefoxFileBookmarksEdit→ViewHistoryProfilesQ.Tool FirefoxFileBookmarksEdit→ViewHistoryProfilesQ.ToolsWindowHelp0meet.google.com/gjc-ikxu-wxu?authuser=lukas.kovalik%40jiminny.comNikolay Nikolov (Presenting, annotating)• Backend Chapter • 33 m left100% <478 • Fri 17 Apr 10:57:103+PhpStorm -FleAT AccountTransformer.phpCeraneie hariei$ Debug-opportunityWiroowDebug opportunity stagesUOpportunitySyncTest.testResolveAcoountidProject v© Account.php 15.04.26, 10-55, 2.14 k0©Activity.php© Address.php 23.03.26, 10.59, 2.5 k8© APrompt.php 23.03.26, 10-39, 2.36 kl© AutomatedReport.php 3.04.26, 139© AutomatedReportResult.php 23.03.© Calendar php 23.03.26, 10.59, 5.17 k8© Callimport. php 23.03.26, 10.59,4.3 ka© CoachingF-eedback,php 23.03.20, 1C©CoschingFeedbackVisibllity.php 23189 €*protected function casts(): array© CoachingSection,php 23.03.26, 10.51© CoschingSectionCriterion.php 23.0© CoachingSectionOriterionF eedback192©CoachingSectionF-eedback.php 23© CommentAbstract.php 23.03.26,10return ["stage_id' "integer','converted at' a>"datetine""stago_updated_at' a "datetine',Accept Reject® Commentinterface,.php 23.03.26, 10© Contact.php 15.04.26, 10-55, 10.17 ka'renotely_created_at* = "datetine',© Device.php 23.03.26, 10.59, 3.57 k8© EmalMessage.php 23.03.26, 10:59, 9197© GenericAPrompt.php 23.03.26, 10.5© Group. php 23.03.26, 10:59, 14.29 kBpooeic tunccion gettesosu gestand doutelnbox.php 15.04.26, 1055, 5.86 кk© nboxÉmal.php 15.04.26, 10.55, 6.11 k291©InboxEmalBatch.php 30.03.24, 10:30282© Invitation,php 23.03.26, 10.59, 6.88 Ml© JobLog.php 23.03.26, 10-59, 2.15 kлJobTide.php 23.03.26, 10.59, 2.98 kB© Language.php 23.03.26, 10:59, 2.51 k© LanguageOlalect.php 23.03.26, 10.51207public function getPhotoUrlAttribute(): string© Lead.pho 13.042203©MobileSetting, php 23.03.26, 10.59, 1209© Model,php 23.03.26, 10.59, 1.88 k8218© Moment.php 23.03.26, 1059,2.78 k8213© Nudge.php 23.03.26, 10.59,7A1k8SphotoPath = Sthis->photo_path;Il If tean photo_path is already a fully qualified URL1f Cpreg,natch( pattern*/nttps?:V\//S", SphotoPath)) (SphotoUrl = SphotoPath;j else (© NudgeRun,php 23.03.26, 10.59, 3.46© Opportunity.php 17.04.28, 10.52, 16.0214© Participant.php 15.04.26, 10.55, 18.451 ede:Sphpto o Accept fla xus io de Gipiernte Bael) : +- 2 el 2 t0es →© Partner.php 31.63.26, M.02, 5.55 kBDarmieeina nhлcastsDmerepooeoondl snore youkereonOapp › app › Repositories › Crm › © CrmÊntityRepository.php › © OrmEntityRepository › ® e impo©CrmEntityRepository.phpOpportunity.php© Lead phpclass Lead extends Model implementsLnetsunDeosprotected static function boot(): voidstatio::updated(static function (Lead Stead): void {SELT::KEINUEA_KELEVANIFIELUSevent (new LeadUpdated(Steas)):Read Lead.php #L189-208The Lead model has the same issue. Let me fix it as well:0 Lead.phpBut, what caused the bug recently -lPHP. 8.3 W Wndsurf Teams192:1UF UTF-8Fri 17 Apr 10:67+0 .mMNikolay NikolovLukas Kovalik10:57 AM Backend Chapter...
|
NULL
|
-4899465654918793457
|
NULL
|
app_switch
|
ocr
|
NULL
|
FirefoxFileBookmarksEdit→ViewHistoryProfilesQ.Tool FirefoxFileBookmarksEdit→ViewHistoryProfilesQ.ToolsWindowHelp0meet.google.com/gjc-ikxu-wxu?authuser=lukas.kovalik%40jiminny.comNikolay Nikolov (Presenting, annotating)• Backend Chapter • 33 m left100% <478 • Fri 17 Apr 10:57:103+PhpStorm -FleAT AccountTransformer.phpCeraneie hariei$ Debug-opportunityWiroowDebug opportunity stagesUOpportunitySyncTest.testResolveAcoountidProject v© Account.php 15.04.26, 10-55, 2.14 k0©Activity.php© Address.php 23.03.26, 10.59, 2.5 k8© APrompt.php 23.03.26, 10-39, 2.36 kl© AutomatedReport.php 3.04.26, 139© AutomatedReportResult.php 23.03.© Calendar php 23.03.26, 10.59, 5.17 k8© Callimport. php 23.03.26, 10.59,4.3 ka© CoachingF-eedback,php 23.03.20, 1C©CoschingFeedbackVisibllity.php 23189 €*protected function casts(): array© CoachingSection,php 23.03.26, 10.51© CoschingSectionCriterion.php 23.0© CoachingSectionOriterionF eedback192©CoachingSectionF-eedback.php 23© CommentAbstract.php 23.03.26,10return ["stage_id' "integer','converted at' a>"datetine""stago_updated_at' a "datetine',Accept Reject® Commentinterface,.php 23.03.26, 10© Contact.php 15.04.26, 10-55, 10.17 ka'renotely_created_at* = "datetine',© Device.php 23.03.26, 10.59, 3.57 k8© EmalMessage.php 23.03.26, 10:59, 9197© GenericAPrompt.php 23.03.26, 10.5© Group. php 23.03.26, 10:59, 14.29 kBpooeic tunccion gettesosu gestand doutelnbox.php 15.04.26, 1055, 5.86 кk© nboxÉmal.php 15.04.26, 10.55, 6.11 k291©InboxEmalBatch.php 30.03.24, 10:30282© Invitation,php 23.03.26, 10.59, 6.88 Ml© JobLog.php 23.03.26, 10-59, 2.15 kлJobTide.php 23.03.26, 10.59, 2.98 kB© Language.php 23.03.26, 10:59, 2.51 k© LanguageOlalect.php 23.03.26, 10.51207public function getPhotoUrlAttribute(): string© Lead.pho 13.042203©MobileSetting, php 23.03.26, 10.59, 1209© Model,php 23.03.26, 10.59, 1.88 k8218© Moment.php 23.03.26, 1059,2.78 k8213© Nudge.php 23.03.26, 10.59,7A1k8SphotoPath = Sthis->photo_path;Il If tean photo_path is already a fully qualified URL1f Cpreg,natch( pattern*/nttps?:V\//S", SphotoPath)) (SphotoUrl = SphotoPath;j else (© NudgeRun,php 23.03.26, 10.59, 3.46© Opportunity.php 17.04.28, 10.52, 16.0214© Participant.php 15.04.26, 10.55, 18.451 ede:Sphpto o Accept fla xus io de Gipiernte Bael) : +- 2 el 2 t0es →© Partner.php 31.63.26, M.02, 5.55 kBDarmieeina nhлcastsDmerepooeoondl snore youkereonOapp › app › Repositories › Crm › © CrmÊntityRepository.php › © OrmEntityRepository › ® e impo©CrmEntityRepository.phpOpportunity.php© Lead phpclass Lead extends Model implementsLnetsunDeosprotected static function boot(): voidstatio::updated(static function (Lead Stead): void {SELT::KEINUEA_KELEVANIFIELUSevent (new LeadUpdated(Steas)):Read Lead.php #L189-208The Lead model has the same issue. Let me fix it as well:0 Lead.phpBut, what caused the bug recently -lPHP. 8.3 W Wndsurf Teams192:1UF UTF-8Fri 17 Apr 10:67+0 .mMNikolay NikolovLukas Kovalik10:57 AM Backend Chapter...
|
NULL
|
|
43179
|
921
|
2
|
2026-04-17T07:57:12.800351+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776412632800_m2.jpg...
|
Firefox
|
NULL
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileFV faVsco.jsProject vEditViewNavigateC PhpStormFileFV faVsco.jsProject vEditViewNavigateCodeLaravelRefactor#11894 on JY-18909-automated-reports-ask-jiminny k ~ToolsWindowHelp> D Redisv D ServiceTraits€ OpportunitySy& SyncCrmEntiti© SyncFieldsTra© WriteCrm Trait.→IUTIS•Weohook© BatchSyncCollect© BatchSyncRedisS© Client.php© ClosedDealStage6 DealFieldsService© DecorateActivity.© FieldDefinitions.p© FieldTypeConveri© HubspotClientInte© HubspotTokenMa© PayloadBuilder.pt© RemoteCrmObjec(C) ResponseNormali© Service.php© SyncFieldAction.f© SyncRelatedActiv© WebhookSyncBa1v D IntegrationApp> D Accessors~ D Api© ActionUrl.php• EnumUrllnterfa© FlowUrl.php© PageResult.ph© ProxyUrl.phpc) recuestsu lceLa kequestcxecu• RequestExecuSystemEvents© SystemUrl.phpC TokenBuilder.f• TokenBuilderlr© UrlBuilder.php> D Config> DDTO> MFilters>MJobs› _ ProspectSearchS> D ServiceTraits© DataClient.php© DecorateActivity.© LocalSearch.php© LocalSearchlntert© RemoteSearch.pr© Service.php_ ListenersMeradara[ MigrationPipedriveD SalesforceD Traits© AutomatedReportsService.php© TeamSetupController.phppnp apl.onp© AutomatedReportsCommandTest.php© TrackProviderInstalledEvent.phpC AutomatedReportsCallbackService.phpOpportunitySyncTrait.phph© SendReportJob.php© SendReportMailJob.php© ReportController.php© TokenBuilder.php• Filesystem.phpAutomatedReportsCommand.phpAskJiminnykeporscontroller.ono© AutomatedReportsSendCommand.php© Team.php© CreateActivityLoggedEvent.php© UserPilotActivityListener.phpC RequestGenerateAskJiminnyReportJob.phpC RequestGenerateReportJob.phpsyncopoortunity.onoService.php© AutomatedReportResult.phpCc w .*O results© AutomatedReport.php1IYACreateHeldActivityEvent.phpAEVZVSA8388928758Y4875OY0070899900901902903904905906936937941942745944745949950951952953954955956957958959960961962963964Y0/trait OpportunitySyncTraitprivate function buildOpportunityData(if ($stage) {$data['stage_id'] = $stage->id;if ($businessProcess) {$recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);if ($recordType) {$data['record_type_id'] = $recordType->id;rerurn boatarLusaeesprivate function resolveBusinessProcess(?string $pipelineld): ?BusinessProcessf...}Lusagesprivate function getBusinessProcess(string $pipelineId): ?BusinessProcess{...}2 usagesprivate function resolveStage(BusinessProcess $businessProcess, Pstring $stageId): ?Stageif (empty($stageid)) {return null$cacheKey = $businessProcess->getId().if (isset($this->cachedStages[$cacheKey])) {return $this->cachedStages[$cacheKey];$stageId;sstage = schis->crmentltykepos1tory->getP1pel1nescagebyconaltlonsфbusinessprocess,cenorovoer o => nstaoelor"cype → stace..lyrc urrurtoNtiyif ($stage === null) {$this->importStages(null, $stageId);if ($stage === null) {$this->logger→>info(' [HubSpot] Stage does not exist => ' . $stageId);Helper code will nelp IDt lo understand vour Laravel apo code. Generale Don't snow Anymore loday 8.09= custom.log(° scratch_1.json= laravel.logV connect.vueA HS_local jiminny@localhost]iib crm_configurations EU]A SF [jiminny@localhost]V Onboard.vueA console [EU] x|Al console [PROD]& console SlAGING15421543-1544154515461548154915511552=155315541555155615571558E1559156015611562—1563=15641565156615671568156915701571157215731574157515761577157815791580-15861081590Ix. Aulo vfajiminny~oElee* rrom cemдUA22зSELECT * FROM crm_layout_entities WHERE crm_layoutSELECT * FROM teams WHERE id = 575;select * from opportunities where team_id = 575;=SELECT * FROM activities WHERE uvid_to_bin('96b126select * from contacts cwhere c.crm_configuration_id = 370 order by c.updaSELECT * FROM participants where activity_id = 39gSELECT * FROM participants where activity_id = 39SELECT * FROM activity_summary_logs where activitySELECT * FROM activities WHERE uvid_to_bin('c7d99FSELECT * FROM activities WHERE uuid_to_bin('2e6ff4select * from crm_profiles where crm_configurationselect * from opportunities where crm_configuratioselect * from accounts where crm_configuration_idselect * from contacts where crm_configuration_id# owner 13236 525785080# contact 116779180 665587441856 - activity - AL# contact 214247[PHONE]7 - asnosuoporuro# company 4176133 47150650569# deal 7100953 410150124747SELECTCONCAT(u.id, CASE WHEN U.id = t.owner_id THENu.email,sa.*,t.owner_id FROM social_accounts saJOIN users u on u.id = sa.sociable_idJOIN teams t 1.n<->1: on t.id = u.team_idWHERE u.team_id = 400 and sa.provider = 'hubspot:select * from features;select * from team_features where feature_id = 40,select * from teams where id = 556; # owner: 18101select * from crm_configurations where id = 477;SELECT * FROM users WHERE id = 18101;SELECTCONCAT(u.id, LASE WHEN u.id = t.owner_id THENu.email,sa.*,t.owner_id FROM social_accounts saJuIn users u on u.1d = sa.soclable_1dJuir realls t us?lon t.id = u.team_idWHERE u.team_id = 556 and sa.provider = 'integrselect * from opportunities where id = 7594349;Backend Chapter - 33 m left100% C8 • Fri 17 Apr 10:57:12AutomatedReportsCommandTestCascadeAutomated Report Retrsearched impor stages in apo (43Investigating HubSpotNow let me look at the reso lveßusinessProcess path more caretully - specifically what triggers repeatedamportolages calls witnout new webnlookswreao Oooonunivsync raltonoNow can see tne oue cleary Look ar reso vestacel aulines yo-yoePoad OnnortunitvSvnc rait ohn * 1.501Found it. There are two bugs in resolvestage():Root CauseBug 1: importStages result isignored — OpportunitySyncTrait. php: 961-9690 phpif ($stage == null) ‹$this->importStages(null, $stageld); // return value discarded!if ($stage === null) { // always true - $stage was never reassigned$this->logger→>info([HubSpot) Stage does not exist = • $stageld):$this->cachedStages [$cacheKey] = $stage; // caches nullThe return value of importStages() is thrown away, $stage is never reassigned, so:1. importstages is called every time (no stage miss cached properly)null is cached - but Bug 2 means the cache never helps anywayBug 2: nult is cached - line 969 caches null for missing stages. So the next deal with the same stagewill hit the cache and get null, skip the import, but the first deal in each batch still triggers the HubSpotAPI CalIActually re-reading - the cache does prevent repeated calls within a single sync run for the same stage.But every new job/sync run resets ($cachedStages = [], so for scheduled sync jobs (e.g. SyncTeamMetadata), each run callis importStages for every deal with an unknown stageThe Real ScenarioSince vou're seeina frequent importStages calls without new webhooks, the likely triagers are-1. SyncleamMetadata Job — runs on a schedule and calls importstages( directly (syncleammetadata.php:100)2. Periodic opportunity sync - syncOpprcunicles orocesses many ceals, eaen calline resolvestaae() → tor any deal whose stage isn't in DB, importStages tires3. The fix bug: after importStages runs, $stage is still null - it never re-queries the DB - so thesace•esskodeo opoonunilv mav de skilodeo or saveo Winout stage 10 ano the next Sunc runhits the same stage again, creating an infinite loop of unnecessary importsThe Fix0 phpif ($stage === null) {sstage = schis→>importstages null, sstageld:Assien tne return value so. a te stace is usea i roune, o lleels propelly cacneo, c the oeporunityIsn't skiooed unnecessarilv.Would you like me to apply this fix?ta all**Ask anything (24L)+ <>Code Claude Sonnet 4.6Winasun lea969:20 12 charsuir-o( 4 spaces...
|
NULL
|
-3207173655443941465
|
NULL
|
app_switch
|
ocr
|
NULL
|
PhpStormFileFV faVsco.jsProject vEditViewNavigateC PhpStormFileFV faVsco.jsProject vEditViewNavigateCodeLaravelRefactor#11894 on JY-18909-automated-reports-ask-jiminny k ~ToolsWindowHelp> D Redisv D ServiceTraits€ OpportunitySy& SyncCrmEntiti© SyncFieldsTra© WriteCrm Trait.→IUTIS•Weohook© BatchSyncCollect© BatchSyncRedisS© Client.php© ClosedDealStage6 DealFieldsService© DecorateActivity.© FieldDefinitions.p© FieldTypeConveri© HubspotClientInte© HubspotTokenMa© PayloadBuilder.pt© RemoteCrmObjec(C) ResponseNormali© Service.php© SyncFieldAction.f© SyncRelatedActiv© WebhookSyncBa1v D IntegrationApp> D Accessors~ D Api© ActionUrl.php• EnumUrllnterfa© FlowUrl.php© PageResult.ph© ProxyUrl.phpc) recuestsu lceLa kequestcxecu• RequestExecuSystemEvents© SystemUrl.phpC TokenBuilder.f• TokenBuilderlr© UrlBuilder.php> D Config> DDTO> MFilters>MJobs› _ ProspectSearchS> D ServiceTraits© DataClient.php© DecorateActivity.© LocalSearch.php© LocalSearchlntert© RemoteSearch.pr© Service.php_ ListenersMeradara[ MigrationPipedriveD SalesforceD Traits© AutomatedReportsService.php© TeamSetupController.phppnp apl.onp© AutomatedReportsCommandTest.php© TrackProviderInstalledEvent.phpC AutomatedReportsCallbackService.phpOpportunitySyncTrait.phph© SendReportJob.php© SendReportMailJob.php© ReportController.php© TokenBuilder.php• Filesystem.phpAutomatedReportsCommand.phpAskJiminnykeporscontroller.ono© AutomatedReportsSendCommand.php© Team.php© CreateActivityLoggedEvent.php© UserPilotActivityListener.phpC RequestGenerateAskJiminnyReportJob.phpC RequestGenerateReportJob.phpsyncopoortunity.onoService.php© AutomatedReportResult.phpCc w .*O results© AutomatedReport.php1IYACreateHeldActivityEvent.phpAEVZVSA8388928758Y4875OY0070899900901902903904905906936937941942745944745949950951952953954955956957958959960961962963964Y0/trait OpportunitySyncTraitprivate function buildOpportunityData(if ($stage) {$data['stage_id'] = $stage->id;if ($businessProcess) {$recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);if ($recordType) {$data['record_type_id'] = $recordType->id;rerurn boatarLusaeesprivate function resolveBusinessProcess(?string $pipelineld): ?BusinessProcessf...}Lusagesprivate function getBusinessProcess(string $pipelineId): ?BusinessProcess{...}2 usagesprivate function resolveStage(BusinessProcess $businessProcess, Pstring $stageId): ?Stageif (empty($stageid)) {return null$cacheKey = $businessProcess->getId().if (isset($this->cachedStages[$cacheKey])) {return $this->cachedStages[$cacheKey];$stageId;sstage = schis->crmentltykepos1tory->getP1pel1nescagebyconaltlonsфbusinessprocess,cenorovoer o => nstaoelor"cype → stace..lyrc urrurtoNtiyif ($stage === null) {$this->importStages(null, $stageId);if ($stage === null) {$this->logger→>info(' [HubSpot] Stage does not exist => ' . $stageId);Helper code will nelp IDt lo understand vour Laravel apo code. Generale Don't snow Anymore loday 8.09= custom.log(° scratch_1.json= laravel.logV connect.vueA HS_local jiminny@localhost]iib crm_configurations EU]A SF [jiminny@localhost]V Onboard.vueA console [EU] x|Al console [PROD]& console SlAGING15421543-1544154515461548154915511552=155315541555155615571558E1559156015611562—1563=15641565156615671568156915701571157215731574157515761577157815791580-15861081590Ix. Aulo vfajiminny~oElee* rrom cemдUA22зSELECT * FROM crm_layout_entities WHERE crm_layoutSELECT * FROM teams WHERE id = 575;select * from opportunities where team_id = 575;=SELECT * FROM activities WHERE uvid_to_bin('96b126select * from contacts cwhere c.crm_configuration_id = 370 order by c.updaSELECT * FROM participants where activity_id = 39gSELECT * FROM participants where activity_id = 39SELECT * FROM activity_summary_logs where activitySELECT * FROM activities WHERE uvid_to_bin('c7d99FSELECT * FROM activities WHERE uuid_to_bin('2e6ff4select * from crm_profiles where crm_configurationselect * from opportunities where crm_configuratioselect * from accounts where crm_configuration_idselect * from contacts where crm_configuration_id# owner 13236 525785080# contact 116779180 665587441856 - activity - AL# contact 214247[PHONE]7 - asnosuoporuro# company 4176133 47150650569# deal 7100953 410150124747SELECTCONCAT(u.id, CASE WHEN U.id = t.owner_id THENu.email,sa.*,t.owner_id FROM social_accounts saJOIN users u on u.id = sa.sociable_idJOIN teams t 1.n<->1: on t.id = u.team_idWHERE u.team_id = 400 and sa.provider = 'hubspot:select * from features;select * from team_features where feature_id = 40,select * from teams where id = 556; # owner: 18101select * from crm_configurations where id = 477;SELECT * FROM users WHERE id = 18101;SELECTCONCAT(u.id, LASE WHEN u.id = t.owner_id THENu.email,sa.*,t.owner_id FROM social_accounts saJuIn users u on u.1d = sa.soclable_1dJuir realls t us?lon t.id = u.team_idWHERE u.team_id = 556 and sa.provider = 'integrselect * from opportunities where id = 7594349;Backend Chapter - 33 m left100% C8 • Fri 17 Apr 10:57:12AutomatedReportsCommandTestCascadeAutomated Report Retrsearched impor stages in apo (43Investigating HubSpotNow let me look at the reso lveßusinessProcess path more caretully - specifically what triggers repeatedamportolages calls witnout new webnlookswreao Oooonunivsync raltonoNow can see tne oue cleary Look ar reso vestacel aulines yo-yoePoad OnnortunitvSvnc rait ohn * 1.501Found it. There are two bugs in resolvestage():Root CauseBug 1: importStages result isignored — OpportunitySyncTrait. php: 961-9690 phpif ($stage == null) ‹$this->importStages(null, $stageld); // return value discarded!if ($stage === null) { // always true - $stage was never reassigned$this->logger→>info([HubSpot) Stage does not exist = • $stageld):$this->cachedStages [$cacheKey] = $stage; // caches nullThe return value of importStages() is thrown away, $stage is never reassigned, so:1. importstages is called every time (no stage miss cached properly)null is cached - but Bug 2 means the cache never helps anywayBug 2: nult is cached - line 969 caches null for missing stages. So the next deal with the same stagewill hit the cache and get null, skip the import, but the first deal in each batch still triggers the HubSpotAPI CalIActually re-reading - the cache does prevent repeated calls within a single sync run for the same stage.But every new job/sync run resets ($cachedStages = [], so for scheduled sync jobs (e.g. SyncTeamMetadata), each run callis importStages for every deal with an unknown stageThe Real ScenarioSince vou're seeina frequent importStages calls without new webhooks, the likely triagers are-1. SyncleamMetadata Job — runs on a schedule and calls importstages( directly (syncleammetadata.php:100)2. Periodic opportunity sync - syncOpprcunicles orocesses many ceals, eaen calline resolvestaae() → tor any deal whose stage isn't in DB, importStages tires3. The fix bug: after importStages runs, $stage is still null - it never re-queries the DB - so thesace•esskodeo opoonunilv mav de skilodeo or saveo Winout stage 10 ano the next Sunc runhits the same stage again, creating an infinite loop of unnecessary importsThe Fix0 phpif ($stage === null) {sstage = schis→>importstages null, sstageld:Assien tne return value so. a te stace is usea i roune, o lleels propelly cacneo, c the oeporunityIsn't skiooed unnecessarilv.Would you like me to apply this fix?ta all**Ask anything (24L)+ <>Code Claude Sonnet 4.6Winasun lea969:20 12 charsuir-o( 4 spaces...
|
NULL
|
|
43223
|
920
|
33
|
2026-04-17T07:58:48.010120+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776412728010_m1.jpg...
|
iTerm2
|
ec2-user@ip-10-20-6-111:~
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
UW PICO 5.09 UW PICO 5.09 New Buffer
[ Read 137 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
Last login: Fri Apr 17 10:32:22 on ttys013
/Users/lukas/.zprofile:138: unmatched "
/Users/lukas/.zprofile:138: unmatched "
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ % veu
zsh: command not found: veu
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ % zp
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ % ssh jiminny-eu-ecs1
Enter MFA code for arn:aws:iam::438740370364:mfa/[EMAIL]:
Warning: Permanently added 'jiminny-eu-ecs1' (ED25519) to the list of known hosts.
A newer release of "Amazon Linux" is available.
Version 2023.10.20260105:
Version 2023.10.20260120:
Version 2023.10.20260202:
Version 2023.10.20260216:
Version 2023.10.20260302:
Version 2023.10.20260325:
Version 2023.10.20260330:
Version 2023.11.20260406:
Version 2023.11.20260413:
Version 2023.8.20250707:
Version 2023.8.20250715:
Version 2023.8.20250721:
Version 2023.8.20250808:
Version 2023.8.20250818:
Version 2023.8.20250908:
Version 2023.8.20250915:
Version 2023.9.20250929:
Version 2023.9.20251014:
Version 2023.9.20251020:
Version 2023.9.20251027:
Version 2023.9.20251105:
Version 2023.9.20251110:
Version 2023.9.20251117:
Version 2023.9.20251208:
Run "/usr/bin/dnf check-release-update" for full release and version update info
, #_
~\_ ####_
~~ \_#####\
~~ \###|
~~ \#/ ___ Amazon Linux 2023 (ECS Optimized)
~~ V~' '->
~~~ /
~~._. _/
_/ _/
_/m/'
For documentation, visit [URL_WITH_CREDENTIALS] ~]$ docker exec -it $(docker ps --format "{{.ID}}" --filter "name=ecs-worker" | head -1) /bin/bash -c "cd /home/jiminny && bash"
root@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook redis --key-type ids --detailed
INFO Scanning Redis keys (type: ids).
Total keys found [PASSWORD_DOTS] 87
Total IDs across all keys [PASSWORD_DOTS] 18,991
📊 Memory Overview
Total Memory [PASSWORD_DOTS] 1.17 MB
ids keys [PASSWORD_DOTS] 1.17 MB
batch_sync_deal:ids:338:all (Formalize - 432)
Config ID: 338
Type: set
Memory: 582.22 KB
TTL: 23h 26m 22s
Count: 9314
Sample: 36225705250, 29787925122, 329967686878
batch_sync_contact:ids:439:all (Log My Care - 522)
Config ID: 439
Type: set
Memory: 203.66 KB
TTL: 15h 27m 1s
Count: 3257
Sample: 4723151, 569384381669, 751397913846
batch_sync_contact:ids:370:all (Buynomics - 462)
Config ID: 370
Type: set
Memory: 159.54 KB
TTL: 23h 58m 24s
Count: 2551
Sample: 7855597, 214629714698, 1081551
batch_sync_company:ids:439:all (Log My Care - 522)
Config ID: 439
Type: set
Memory: 143.97 KB
TTL: 15h 26m 38s
Count: 2302
Sample: 97509907664, 287839612106, 13347420118
batch_sync_company:ids:370:all (Buynomics - 462)
Config ID: 370
Type: set
Memory: 28.54 KB
TTL: 23h 57m 58s
Count: 455
Sample: 53416555927, 5299782086, 48272143029
batch_sync_company:ids:346:all (Global Expansion - 444)
Config ID: 346
Type: set
Memory: 17.66 KB
TTL: 23h 59m 57s
Count: 281
Sample: 54084980525, 31037091957, 16036051109
batch_sync_contact:ids:346:all (Global Expansion - 444)
Config ID: 346
Type: set
Memory: 12.6 KB
TTL: 23h 59m 57s
Count: 200
Sample: 216133633092, 216059621965, 216123582828
batch_sync_contact:ids:373:all (KPSBremen.de - 465)
Config ID: 373
Type: set
Memory: 4.35 KB
TTL: 23h 42m 32s
Count: 68
Sample: 751746229489, 662801482974, 751204849893
batch_sync_company:ids:449:all (SiSU Health UK - 531)
Config ID: 449
Type: set
Memory: 3.6 KB
TTL: 8h 7m 26s
Count: 56
Sample: 293769448657, 7905289206, 6994524653
batch_sync_contact:ids:449:all (SiSU Health UK - 531)
Config ID: 449
Type: set
Memory: 3.47 KB
TTL: 22h 45m 52s
Count: 54
Sample: 730208, 752394791155, 759217738995
batch_sync_deal:ids:439:all (Log My Care - 522)
Config ID: 439
Type: set
Memory: 3.47 KB
TTL: 9h 4m 51s
Count: 54
Sample: 364507398345, 494814103757, 494016526555
batch_sync_company:ids:373:all (KPSBremen.de - 465)
Config ID: 373
Type: set
Memory: 3.41 KB
TTL: 23h 55m 1s
Count: 53
Sample: 399250577648, 426553672928, 379424069826
batch_sync_deal:ids:449:all (SiSU Health UK - 531)
Config ID: 449
Type: set
Memory: 3.04 KB
TTL: 8h 7m 33s
Count: 47
Sample: 498503490753, 498500636859, 498501354684
batch_sync_contact:ids:331:all (The National College - 416)
Config ID: 331
Type: set
Memory: 1.6 KB
TTL: 23h 59m 35s
Count: 24
Sample: 753335460029, 759353955559, 600494578893
batch_sync_company:ids:331:all (The National College - 416)
Config ID: 331
Type: set
Memory: 1.47 KB
TTL: 23h 59m 22s
Count: 22
Sample: 23755815000, 5684894521, 3972787939
batch_sync_deal:ids:331:all (The National College - 416)
Config ID: 331
Type: set
Memory: 1.47 KB
TTL: 23h 55m 17s
Count: 22
Sample: 499035527382, 494192151741, 499111919842
batch_sync_contact:ids:488:all (MakeMyHouseGreen - 567)
Config ID: 488
Type: set
Memory: 1.41 KB
TTL: 23h 57m 17s
Count: 21
Sample: 935601, 4269001, 2258
batch_sync_contact:ids:170:all (LutherOne - 199)
Config ID: 170
Type: set
Memory: 996 B
TTL: 23h 59m 49s
Count: 14
Sample: 6438163, 18894909, 131340729177
batch_sync_contact:ids:364:all (Lead Forensics - 190)
Config ID: 364
Type: set
Memory: 932 B
TTL: 23h 56m 58s
Count: 13
Sample: 103762162328, 103730206004, 103746035125
batch_sync_contact:ids:124:all (Intruder - 149)
Config ID: 124
Type: set
Memory: 868 B
TTL: 23h 53m 21s
Count: 12
Sample: 209000212364, 72073201, 32825651463
batch_sync_contact:ids:70:all (Scoro - 93)
Config ID: 70
Type: set
Memory: 804 B
TTL: 23h 49m 7s
Count: 11
Sample: 548911252691, 549509632216, 549499217106
batch_sync_contact:ids:483:all (Veremark - 561)
Config ID: 483
Type: set
Memory: 804 B
TTL: 23h 55m 11s
Count: 11
Sample: 215753056547, 215474268483, 215540281168
batch_sync_contact:ids:363:all (Global Group - 456)
Config ID: 363
Type: set
Memory: 548 B
TTL: 23h 57m 2s
Count: 7
Sample: 156053546908, 14789232785, 1569201
batch_sync_deal:ids:455:all (Argos Security - 537)
Config ID: 455
Type: set
Memory: 548 B
TTL: 23h 54m 15s
Count: 7
Sample: 498986026223, 17924102876, 498928162009
batch_sync_deal:ids:461:all (Fieldly - 543)
Config ID: 461
Type: set
Memory: 548 B
TTL: 23h 59m 34s
Count: 7
Sample: 498984577267, 499024879840, 496749330660
batch_sync_company:ids:465:all (Spotler - 545)
Config ID: 465
Type: set
Memory: 484 B
TTL: 23h 53m 55s
Count: 6
Sample: 12886901694, 47800033493, 426487803113
batch_sync_company:ids:483:all (Veremark - 561)
Config ID: 483
Type: set
Memory: 484 B
TTL: 23h 53m 12s
Count: 6
Sample: 54056532238, 25291493956, 52568117585
batch_sync_contact:ids:465:all (Spotler - 545)
Config ID: 465
Type: set
Memory: 484 B
TTL: 23h 53m 55s
Count: 6
Sample: 25784346850, 758403684599, 758356838593
batch_sync_contact:ids:96:all (Nourish Care - 119)
Config ID: 96
Type: set
Memory: 420 B
TTL: 23h 51m 15s
Count: 5
Sample: 759339342027, 6476279789, 759338177732
batch_sync_deal:ids:241:all (PatentRenewal.com ApS - 306)
Config ID: 241
Type: set
Memory: 420 B
TTL: 23h 59m 34s
Count: 5
Sample: 57554678843, 59164863320, 40205117839
batch_sync_company:ids:175:all (Team iAM - 203)
Config ID: 175
Type: set
Memory: 356 B
TTL: 23h 52m 11s
Count: 4
Sample: 31289463313, 53432109235, 47337393078
batch_sync_contact:ids:197:all (Kindly - 264)
Config ID: 197
Type: set
Memory: 356 B
TTL: 23h 56m 33s
Count: 4
Sample: 759337959662, 759340603582, 759336916201
batch_sync_contact:ids:308:all (Foodles - 380)
Config ID: 308
Type: set
Memory: 356 B
TTL: 23h 47m 13s
Count: 4
Sample: 209605481386, 216074957452, 216074957451
batch_sync_contact:ids:461:all (Fieldly - 543)
Config ID: 461
Type: set
Memory: 356 B
TTL: 23h 59m 38s
Count: 4
Sample: 758403694783, 6338351, 755171302642
batch_sync_contact:ids:485:all (LATUS Group - 563)
Config ID: 485
Type: set
Memory: 356 B
TTL: 23h 59m 51s
Count: 4
Sample: 216014615839, 216124673101, 215534275979
batch_sync_company:ids:170:all (LutherOne - 199)
Config ID: 170
Type: set
Memory: 292 B
TTL: 23h 55m 25s
Count: 3
Sample: 5061344699, 8979817578, 53544705830
batch_sync_company:ids:197:all (Kindly - 264)
Config ID: 197
Type: set
Memory: 292 B
TTL: 23h 56m 26s
Count: 3
Sample: 426652716232, 426652949738, 426671486159
batch_sync_company:ids:436:all (Moxso - 519)
Config ID: 436
Type: set
Memory: 292 B
TTL: 23h 54m 5s
Count: 3
Sample: 5637526753, 9027001061, 426485659872
batch_sync_company:ids:455:all (Argos Security - 537)
Config ID: 455
Type: set
Memory: 292 B
TTL: 23h 49m 25s
Count: 3
Sample: 12009203674, 12897632476, 331995461840
batch_sync_contact:ids:130:all (Latana Brand Tracking - 155)
Config ID: 130
Type: set
Memory: 292 B
TTL: 23h 44m 14s
Count: 3
Sample: 216074963815, 216074963816, 216074963817
batch_sync_contact:ids:175:all (Team iAM - 203)
Config ID: 175
Type: set
Memory: 292 B
TTL: 23h 52m 37s
Count: 3
Sample: 212581164888, 216121454962, 216121861967
batch_sync_contact:ids:455:all (Argos Security - 537)
Config ID: 455
Type: set
Memory: 292 B
TTL: 23h 49m 25s
Count: 3
Sample: 15081981630, 28607253707, 589985456367
batch_sync_deal:ids:465:all (Spotler - 545)
Config ID: 465
Type: set
Memory: 292 B
TTL: 23h 54m 43s
Count: 3
Sample: 498984572105, 498986026222, 498987447516
batch_sync_company:ids:70:all (Scoro - 93)
Config ID: 70
Type: set
Memory: 228 B
TTL: 23h 48m 59s
Count: 2
Sample: 308374847674, 426652674241
batch_sync_company:ids:461:all (Fieldly - 543)
Config ID: 461
Type: set
Memory: 228 B
TTL: 23h 57m 6s
Count: 2
Sample: 612188920, 1045770343
batch_sync_contact:ids:112:all (Switchee - 137)
Config ID: 112
Type: set
Memory: 228 B
TTL: 23h 50m 12s
Count: 2
Sample: 216074959999, 216074960000
batch_sync_contact:ids:201:all (THRIVE - 266)
Config ID: 201
Type: set
Memory: 228 B
TTL: 23h 59m 17s
Count: 2
Sample: 17229652, 23550651
batch_sync_contact:ids:253:all (Zymego - 322)
Config ID: 253
Type: set
Memory: 228 B
TTL: 23h 48m 32s
Count: 2
Sample: 9236122316, 108257234125
batch_sync_contact:ids:307:all (Story Terrace Inc - 379)
Config ID: 307
Type: set
Memory: 228 B
TTL: 23h 54m 36s
Count: 2
Sample: 216118037022, 216120038928
batch_sync_contact:ids:319:all (MySalesCoach - 400)
Config ID: 319
Type: set
Memory: 228 B
TTL: 23h 51m 16s
Count: 2
Sample: 191355036878, 759224982717
batch_sync_contact:ids:335:all (Eletive - 429)
Config ID: 335
Type: set
Memory: 228 B
TTL: 23h 58m 13s
Count: 2
Sample: 159795767866, 757397522679
batch_sync_contact:ids:339:all (inspera.no - 436)
Config ID: 339
Type: set
Memory: 228 B
TTL: 23h 55m 54s
Count: 2
Sample: 183336486505, 216074960287
batch_sync_contact:ids:412:all (Antavo - 500)
Config ID: 412
Type: set
Memory: 228 B
TTL: 23h 50m 30s
Count: 2
Sample: 644080299229, 705317319867
batch_sync_deal:ids:96:all (Nourish Care - 119)
Config ID: 96
Type: set
Memory: 228 B
TTL: 23h 51m 21s
Count: 2
Sample: 13955519955, 499101468869
batch_sync_deal:ids:253:all (Zymego - 322)
Config ID: 253
Type: set
Memory: 228 B
TTL: 23h 49m 0s
Count: 2
Sample: 499101779135, 499117828335
batch_sync_company:ids:95:all (Cronofy - 118)
Config ID: 95
Type: set
Memory: 164 B
TTL: 23h 44m 9s
Count: 1
Sample: 54084980481
batch_sync_company:ids:96:all (Nourish Care - 119)
Config ID: 96
Type: set
Memory: 164 B
TTL: 23h 51m 18s
Count: 1
Sample: 231946184895
batch_sync_company:ids:112:all (Switchee - 137)
Config ID: 112
Type: set
Memory: 164 B
TTL: 23h 45m 50s
Count: 1
Sample: 30902131597
batch_sync_company:ids:124:all (Intruder - 149)
Config ID: 124
Type: set
Memory: 164 B
TTL: 23h 49m 59s
Count: 1
Sample: 19200117829
batch_sync_company:ids:130:all (Latana Brand Tracking - 155)
Config ID: 130
Type: set
Memory: 164 B
TTL: 23h 43m 46s
Count: 1
Sample: 3412118684
batch_sync_company:ids:191:all (Orlo - 253)
Config ID: 191
Type: set
Memory: 164 B
TTL: 23h 53m 53s
Count: 1
Sample: 2492067355
batch_sync_company:ids:200:all (Jobadder - 265)
Config ID: 200
Type: set
Memory: 164 B
TTL: 23h 41m 16s
Count: 1
Sample: 54051603071
batch_sync_company:ids:253:all (Zymego - 322)
Config ID: 253
Type: set
Memory: 164 B
TTL: 23h 48m 34s
Count: 1
Sample: 45363246315
batch_sync_company:ids:295:all (APLYiD - 367)
Config ID: 295
Type: set
Memory: 164 B
TTL: 23h 43m 38s
Count: 1
Sample: 426645956821
batch_sync_company:ids:308:all (Foodles - 380)
Config ID: 308
Type: set
Memory: 164 B
TTL: 23h 43m 36s
Count: 1
Sample: 4623764126
batch_sync_company:ids:485:all (LATUS Group - 563)
Config ID: 485
Type: set
Memory: 164 B
TTL: 23h 59m 51s
Count: 1
Sample: 16393088676
batch_sync_contact:ids:81:all (Hurree - 104)
Config ID: 81
Type: set
Memory: 164 B
TTL: 23h 52m 48s
Count: 1
Sample: 216123172492
batch_sync_contact:ids:86:all (Teamtailor - 109)
Config ID: 86
Type: set
Memory: 164 B
TTL: 5h 35m 55s
Count: 1
Sample: 128480851
batch_sync_contact:ids:95:all (Cronofy - 118)
Config ID: 95
Type: set
Memory: 164 B
TTL: 23h 44m 9s
Count: 1
Sample: 216119828808
batch_sync_contact:ids:191:all (Orlo - 253)
Config ID: 191
Type: set
Memory: 164 B
TTL: 23h 43m 42s
Count: 1
Sample: 185805924409
batch_sync_contact:ids:200:all (Jobadder - 265)
Config ID: 200
Type: set
Memory: 164 B
TTL: 23h 41m 16s
Count: 1
Sample: 216122837601
batch_sync_contact:ids:255:all (Screendragon - 324)
Config ID: 255
Type: set
Memory: 164 B
TTL: 23h 44m 22s
Count: 1
Sample: 215982276420
batch_sync_contact:ids:278:all (Akixi - 348)
Config ID: 278
Type: set
Memory: 164 B
TTL: 23h 46m 52s
Count: 1
Sample: 758404365499
batch_sync_contact:ids:284:all (Brickflow (Property Funding Hub Ltd) - 354)
Config ID: 284
Type: set
Memory: 164 B
TTL: 23h 44m 47s
Count: 1
Sample: 195262486661
batch_sync_contact:ids:295:all (APLYiD - 367)
Config ID: 295
Type: set
Memory: 164 B
TTL: 23h 44m 11s
Count: 1
Sample: 759223062753
batch_sync_contact:ids:322:all (Talkative - 403)
Config ID: 322
Type: set
Memory: 164 B
TTL: 23h 56m 55s
Count: 1
Sample: 751262016755
batch_sync_contact:ids:367:all (Sensat - 459)
Config ID: 367
Type: set
Memory: 164 B
TTL: 23h 51m 33s
Count: 1
Sample: 215694537008
batch_sync_contact:ids:403:all (Fundrella - 491)
Config ID: 403
Type: set
Memory: 164 B
TTL: 23h 43m 28s
Count: 1
Sample: 147101
batch_sync_contact:ids:436:all (Moxso - 519)
Config ID: 436
Type: set
Memory: 164 B
TTL: 23h 54m 15s
Count: 1
Sample: 294701
batch_sync_contact:ids:491:all (CreateFuture - 570)
Config ID: 491
Type: set
Memory: 164 B
TTL: 23h 51m 48s
Count: 1
Sample: 214629839743
batch_sync_deal:ids:191:all (Orlo - 253)
Config ID: 191
Type: set
Memory: 164 B
TTL: 23h 54m 11s
Count: 1
Sample: 59230347609
batch_sync_deal:ids:200:all (Jobadder - 265)
Config ID: 200
Type: set
Memory: 164 B
TTL: 23h 50m 5s
Count: 1
Sample: 58878310503
batch_sync_deal:ids:255:all (Screendragon - 324)
Config ID: 255
Type: set
Memory: 164 B
TTL: 23h 44m 20s
Count: 1
Sample: 59243074931
batch_sync_deal:ids:335:all (Eletive - 429)
Config ID: 335
Type: set
Memory: 164 B
TTL: 23h 39m 13s
Count: 1
Sample: 498499653826
batch_sync_deal:ids:370:all (Buynomics - 462)
Config ID: 370
Type: set
Memory: 164 B
TTL: 5h 19m 0s
Count: 1
Sample: 10644871427
batch_sync_deal:ids:373:all (KPSBremen.de - 465)
Config ID: 373
Type: set
Memory: 164 B
TTL: 2h 41m 0s
Count: 1
Sample: 493652735187
batch_sync_deal:ids:483:all (Veremark - 561)
Config ID: 483
Type: set
Memory: 164 B
TTL: 23h 47m 59s
Count: 1
Sample: 59186288665
root@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook redis --key-type ids --detailed -T 459
INFO Scanning Redis keys (type: ids).
Total keys found [PASSWORD_DOTS] 1
Total IDs across all keys [PASSWORD_DOTS] 1
📊 Memory Overview
Total Memory [PASSWORD_DOTS] 164 B
ids keys [PASSWORD_DOTS] 164 B
batch_sync_contact:ids:367:all (Sensat - 459)
Config ID: 367
Type: set
Memory: 164 B
TTL: 23h 51m 6s
Count: 1
Sample: 215694537008
root@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook metrics -T 459
INFO Webhook Metrics — 2026-04-17.
📊 Webhook Metrics Summary
==========================================
Date: 2026-04-17
Filters: Config: 367
Total Teams: 1
Total Webhooks: 7
🏢 Config 367 (Sensat - 459) - 7 webhooks
📦 contact: 6 webhooks
🔔 property_change: 6 events, 5 properties
📦 deal: 1 webhooks
🔔 property_change: 1 events, 1 properties
root@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook metrics -T 459 --from 2026-04-15
INFO Managing webhook metrics for date range.
Date Range [PASSWORD_DOTS] 2026-04-15 to 2026-04-17
Config ID [PASSWORD_DOTS] 367
📊 Range Summary
Date Range [PASSWORD_DOTS] 2026-04-15 to 2026-04-17
Total Days [PASSWORD_DOTS] 3
Oldest Data Age [PASSWORD_DOTS] 2.0 days ago
Total Webhooks [PASSWORD_DOTS] 1,065,651
Daily Average [PASSWORD_DOTS] 355,217.00
Active Companies [PASSWORD_DOTS] 89
🏢 Company Details
Company 367 (Sensat - 459)
Total Webhooks: 796
Days Active: 3/3
Daily Average: 265.33
root@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook metrics -T 459 --from 2026-04-15 -D
INFO Managing webhook metrics for date range.
Date Range [PASSWORD_DOTS] 2026-04-15 to 2026-04-17
Config ID [PASSWORD_DOTS] 367
📊 Range Summary
Date Range [PASSWORD_DOTS] 2026-04-15 to 2026-04-17
Total Days [PASSWORD_DOTS] 3
Oldest Data Age [PASSWORD_DOTS] 2.0 days ago
Total Webhooks [PASSWORD_DOTS] 1,065,677
Daily Average [PASSWORD_DOTS] 355,225.67
Active Companies [PASSWORD_DOTS] 89
📅 Daily Breakdown
2026-04-15: 335,647 webhooks, 88 companies active
2026-04-16: 671,679 webhooks, 88 companies active
2026-04-17: 58,351 webhooks, 68 companies active
🏢 Company Details
Company 367 (Sensat - 459)
Total Webhooks: 796
Days Active: 3/3
Daily Average: 265.33
company (114 total, avg: 38)
association_change: 92 total, avg: 46, active: 2 days
creation: 3 total, avg: 1.5, active: 2 days
property_change: 19 total, avg: 9.5, active: 2 days
Unique properties: 4
Top properties: hubspot_owner_id(12), domain(3), name(3), phone(1)
deal (164 total, avg: 54.67)
property_change: 164 total, avg: 54.67, active: 3 days
Unique properties: 8
Top properties: notes_last_updated(134), closedate(7), dealstage(5), hs_deal_stage_probability(5), hs_manual_forecast_category(5)
contact (518 total, avg: 172.67)
property_change: 390 total, avg: 130, active: 3 days
Unique properties: 9
Top properties: hubspot_owner_id(186), firstname(35), email(35), associatedcompanyid(33), country(33)
creation: 36 total, avg: 18, active: 2 days
association_change: 92 total, avg: 46, active: 2 days
root@67e84f80b9d1:/home/jiminny#
DOCKER
Close Tab
DEV (-zsh)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
-zsh
Close Tab
✳ Review screenpipe usage and Boosteroid integration (claude)
Close Tab
ec2-user@ip-10-30-159-186:~ (nc)
Close Tab
ec2-user@ip-10-20-6-111:~ (nc)
Close Tab
⌥⌘1
ec2-user@ip-10-20-6-111:~...
|
[{"role":"AXTextArea","text [{"role":"AXTextArea","text":"UW PICO 5.09 New Buffer \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n [ Read 137 lines ] \n^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos \n^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell \nLast login: Fri Apr 17 10:32:22 on ttys013\n/Users/lukas/.zprofile:138: unmatched \"\n/Users/lukas/.zprofile:138: unmatched \"\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ % veu\nzsh: command not found: veu\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ % zp\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ % ssh jiminny-eu-ecs1\nEnter MFA code for arn:aws:iam::438740370364:mfa/lukas.kovalik@jiminny.com: \nWarning: Permanently added 'jiminny-eu-ecs1' (ED25519) to the list of known hosts.\n\nA newer release of \"Amazon Linux\" is available.\n Version 2023.10.20260105:\n Version 2023.10.20260120:\n Version 2023.10.20260202:\n Version 2023.10.20260216:\n Version 2023.10.20260302:\n Version 2023.10.20260325:\n Version 2023.10.20260330:\n Version 2023.11.20260406:\n Version 2023.11.20260413:\n Version 2023.8.20250707:\n Version 2023.8.20250715:\n Version 2023.8.20250721:\n Version 2023.8.20250808:\n Version 2023.8.20250818:\n Version 2023.8.20250908:\n Version 2023.8.20250915:\n Version 2023.9.20250929:\n Version 2023.9.20251014:\n Version 2023.9.20251020:\n Version 2023.9.20251027:\n Version 2023.9.20251105:\n Version 2023.9.20251110:\n Version 2023.9.20251117:\n Version 2023.9.20251208:\nRun \"/usr/bin/dnf check-release-update\" for full release and version update info\n , #_\n ~\\_ ####_\n ~~ \\_#####\\\n ~~ \\###|\n ~~ \\#/ ___ Amazon Linux 2023 (ECS Optimized)\n ~~ V~' '->\n ~~~ /\n ~~._. _/\n _/ _/\n _/m/'\n\nFor documentation, visit http://aws.amazon.com/documentation/ecs\nLast login: Thu Apr 16 11:09:44 2026 from 10.20.163.228\n[ec2-user@ip-10-20-6-111 ~]$ docker exec -it $(docker ps --format \"{{.ID}}\" --filter \"name=ecs-worker\" | head -1) /bin/bash -c \"cd /home/jiminny && bash\"\nroot@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook redis --key-type ids --detailed \n\n INFO Scanning Redis keys (type: ids). \n\n\n Total keys found .............................................................................................................................. 87 \n Total IDs across all keys ................................................................................................................. 18,991 \n\n📊\u0000 Memory Overview\n Total Memory ............................................................................................................................. 1.17 MB \n ids keys ................................................................................................................................. 1.17 MB \n\nbatch_sync_deal:ids:338:all (Formalize - 432)\n Config ID: 338\n Type: set\n Memory: 582.22 KB\n TTL: 23h 26m 22s\n Count: 9314\n Sample: 36225705250, 29787925122, 329967686878\n\nbatch_sync_contact:ids:439:all (Log My Care - 522)\n Config ID: 439\n Type: set\n Memory: 203.66 KB\n TTL: 15h 27m 1s\n Count: 3257\n Sample: 4723151, 569384381669, 751397913846\n\nbatch_sync_contact:ids:370:all (Buynomics - 462)\n Config ID: 370\n Type: set\n Memory: 159.54 KB\n TTL: 23h 58m 24s\n Count: 2551\n Sample: 7855597, 214629714698, 1081551\n\nbatch_sync_company:ids:439:all (Log My Care - 522)\n Config ID: 439\n Type: set\n Memory: 143.97 KB\n TTL: 15h 26m 38s\n Count: 2302\n Sample: 97509907664, 287839612106, 13347420118\n\nbatch_sync_company:ids:370:all (Buynomics - 462)\n Config ID: 370\n Type: set\n Memory: 28.54 KB\n TTL: 23h 57m 58s\n Count: 455\n Sample: 53416555927, 5299782086, 48272143029\n\nbatch_sync_company:ids:346:all (Global Expansion - 444)\n Config ID: 346\n Type: set\n Memory: 17.66 KB\n TTL: 23h 59m 57s\n Count: 281\n Sample: 54084980525, 31037091957, 16036051109\n\nbatch_sync_contact:ids:346:all (Global Expansion - 444)\n Config ID: 346\n Type: set\n Memory: 12.6 KB\n TTL: 23h 59m 57s\n Count: 200\n Sample: 216133633092, 216059621965, 216123582828\n\nbatch_sync_contact:ids:373:all (KPSBremen.de - 465)\n Config ID: 373\n Type: set\n Memory: 4.35 KB\n TTL: 23h 42m 32s\n Count: 68\n Sample: 751746229489, 662801482974, 751204849893\n\nbatch_sync_company:ids:449:all (SiSU Health UK - 531)\n Config ID: 449\n Type: set\n Memory: 3.6 KB\n TTL: 8h 7m 26s\n Count: 56\n Sample: 293769448657, 7905289206, 6994524653\n\nbatch_sync_contact:ids:449:all (SiSU Health UK - 531)\n Config ID: 449\n Type: set\n Memory: 3.47 KB\n TTL: 22h 45m 52s\n Count: 54\n Sample: 730208, 752394791155, 759217738995\n\nbatch_sync_deal:ids:439:all (Log My Care - 522)\n Config ID: 439\n Type: set\n Memory: 3.47 KB\n TTL: 9h 4m 51s\n Count: 54\n Sample: 364507398345, 494814103757, 494016526555\n\nbatch_sync_company:ids:373:all (KPSBremen.de - 465)\n Config ID: 373\n Type: set\n Memory: 3.41 KB\n TTL: 23h 55m 1s\n Count: 53\n Sample: 399250577648, 426553672928, 379424069826\n\nbatch_sync_deal:ids:449:all (SiSU Health UK - 531)\n Config ID: 449\n Type: set\n Memory: 3.04 KB\n TTL: 8h 7m 33s\n Count: 47\n Sample: 498503490753, 498500636859, 498501354684\n\nbatch_sync_contact:ids:331:all (The National College - 416)\n Config ID: 331\n Type: set\n Memory: 1.6 KB\n TTL: 23h 59m 35s\n Count: 24\n Sample: 753335460029, 759353955559, 600494578893\n\nbatch_sync_company:ids:331:all (The National College - 416)\n Config ID: 331\n Type: set\n Memory: 1.47 KB\n TTL: 23h 59m 22s\n Count: 22\n Sample: 23755815000, 5684894521, 3972787939\n\nbatch_sync_deal:ids:331:all (The National College - 416)\n Config ID: 331\n Type: set\n Memory: 1.47 KB\n TTL: 23h 55m 17s\n Count: 22\n Sample: 499035527382, 494192151741, 499111919842\n\nbatch_sync_contact:ids:488:all (MakeMyHouseGreen - 567)\n Config ID: 488\n Type: set\n Memory: 1.41 KB\n TTL: 23h 57m 17s\n Count: 21\n Sample: 935601, 4269001, 2258\n\nbatch_sync_contact:ids:170:all (LutherOne - 199)\n Config ID: 170\n Type: set\n Memory: 996 B\n TTL: 23h 59m 49s\n Count: 14\n Sample: 6438163, 18894909, 131340729177\n\nbatch_sync_contact:ids:364:all (Lead Forensics - 190)\n Config ID: 364\n Type: set\n Memory: 932 B\n TTL: 23h 56m 58s\n Count: 13\n Sample: 103762162328, 103730206004, 103746035125\n\nbatch_sync_contact:ids:124:all (Intruder - 149)\n Config ID: 124\n Type: set\n Memory: 868 B\n TTL: 23h 53m 21s\n Count: 12\n Sample: 209000212364, 72073201, 32825651463\n\nbatch_sync_contact:ids:70:all (Scoro - 93)\n Config ID: 70\n Type: set\n Memory: 804 B\n TTL: 23h 49m 7s\n Count: 11\n Sample: 548911252691, 549509632216, 549499217106\n\nbatch_sync_contact:ids:483:all (Veremark - 561)\n Config ID: 483\n Type: set\n Memory: 804 B\n TTL: 23h 55m 11s\n Count: 11\n Sample: 215753056547, 215474268483, 215540281168\n\nbatch_sync_contact:ids:363:all (Global Group - 456)\n Config ID: 363\n Type: set\n Memory: 548 B\n TTL: 23h 57m 2s\n Count: 7\n Sample: 156053546908, 14789232785, 1569201\n\nbatch_sync_deal:ids:455:all (Argos Security - 537)\n Config ID: 455\n Type: set\n Memory: 548 B\n TTL: 23h 54m 15s\n Count: 7\n Sample: 498986026223, 17924102876, 498928162009\n\nbatch_sync_deal:ids:461:all (Fieldly - 543)\n Config ID: 461\n Type: set\n Memory: 548 B\n TTL: 23h 59m 34s\n Count: 7\n Sample: 498984577267, 499024879840, 496749330660\n\nbatch_sync_company:ids:465:all (Spotler - 545)\n Config ID: 465\n Type: set\n Memory: 484 B\n TTL: 23h 53m 55s\n Count: 6\n Sample: 12886901694, 47800033493, 426487803113\n\nbatch_sync_company:ids:483:all (Veremark - 561)\n Config ID: 483\n Type: set\n Memory: 484 B\n TTL: 23h 53m 12s\n Count: 6\n Sample: 54056532238, 25291493956, 52568117585\n\nbatch_sync_contact:ids:465:all (Spotler - 545)\n Config ID: 465\n Type: set\n Memory: 484 B\n TTL: 23h 53m 55s\n Count: 6\n Sample: 25784346850, 758403684599, 758356838593\n\nbatch_sync_contact:ids:96:all (Nourish Care - 119)\n Config ID: 96\n Type: set\n Memory: 420 B\n TTL: 23h 51m 15s\n Count: 5\n Sample: 759339342027, 6476279789, 759338177732\n\nbatch_sync_deal:ids:241:all (PatentRenewal.com ApS - 306)\n Config ID: 241\n Type: set\n Memory: 420 B\n TTL: 23h 59m 34s\n Count: 5\n Sample: 57554678843, 59164863320, 40205117839\n\nbatch_sync_company:ids:175:all (Team iAM - 203)\n Config ID: 175\n Type: set\n Memory: 356 B\n TTL: 23h 52m 11s\n Count: 4\n Sample: 31289463313, 53432109235, 47337393078\n\nbatch_sync_contact:ids:197:all (Kindly - 264)\n Config ID: 197\n Type: set\n Memory: 356 B\n TTL: 23h 56m 33s\n Count: 4\n Sample: 759337959662, 759340603582, 759336916201\n\nbatch_sync_contact:ids:308:all (Foodles - 380)\n Config ID: 308\n Type: set\n Memory: 356 B\n TTL: 23h 47m 13s\n Count: 4\n Sample: 209605481386, 216074957452, 216074957451\n\nbatch_sync_contact:ids:461:all (Fieldly - 543)\n Config ID: 461\n Type: set\n Memory: 356 B\n TTL: 23h 59m 38s\n Count: 4\n Sample: 758403694783, 6338351, 755171302642\n\nbatch_sync_contact:ids:485:all (LATUS Group - 563)\n Config ID: 485\n Type: set\n Memory: 356 B\n TTL: 23h 59m 51s\n Count: 4\n Sample: 216014615839, 216124673101, 215534275979\n\nbatch_sync_company:ids:170:all (LutherOne - 199)\n Config ID: 170\n Type: set\n Memory: 292 B\n TTL: 23h 55m 25s\n Count: 3\n Sample: 5061344699, 8979817578, 53544705830\n\nbatch_sync_company:ids:197:all (Kindly - 264)\n Config ID: 197\n Type: set\n Memory: 292 B\n TTL: 23h 56m 26s\n Count: 3\n Sample: 426652716232, 426652949738, 426671486159\n\nbatch_sync_company:ids:436:all (Moxso - 519)\n Config ID: 436\n Type: set\n Memory: 292 B\n TTL: 23h 54m 5s\n Count: 3\n Sample: 5637526753, 9027001061, 426485659872\n\nbatch_sync_company:ids:455:all (Argos Security - 537)\n Config ID: 455\n Type: set\n Memory: 292 B\n TTL: 23h 49m 25s\n Count: 3\n Sample: 12009203674, 12897632476, 331995461840\n\nbatch_sync_contact:ids:130:all (Latana Brand Tracking - 155)\n Config ID: 130\n Type: set\n Memory: 292 B\n TTL: 23h 44m 14s\n Count: 3\n Sample: 216074963815, 216074963816, 216074963817\n\nbatch_sync_contact:ids:175:all (Team iAM - 203)\n Config ID: 175\n Type: set\n Memory: 292 B\n TTL: 23h 52m 37s\n Count: 3\n Sample: 212581164888, 216121454962, 216121861967\n\nbatch_sync_contact:ids:455:all (Argos Security - 537)\n Config ID: 455\n Type: set\n Memory: 292 B\n TTL: 23h 49m 25s\n Count: 3\n Sample: 15081981630, 28607253707, 589985456367\n\nbatch_sync_deal:ids:465:all (Spotler - 545)\n Config ID: 465\n Type: set\n Memory: 292 B\n TTL: 23h 54m 43s\n Count: 3\n Sample: 498984572105, 498986026222, 498987447516\n\nbatch_sync_company:ids:70:all (Scoro - 93)\n Config ID: 70\n Type: set\n Memory: 228 B\n TTL: 23h 48m 59s\n Count: 2\n Sample: 308374847674, 426652674241\n\nbatch_sync_company:ids:461:all (Fieldly - 543)\n Config ID: 461\n Type: set\n Memory: 228 B\n TTL: 23h 57m 6s\n Count: 2\n Sample: 612188920, 1045770343\n\nbatch_sync_contact:ids:112:all (Switchee - 137)\n Config ID: 112\n Type: set\n Memory: 228 B\n TTL: 23h 50m 12s\n Count: 2\n Sample: 216074959999, 216074960000\n\nbatch_sync_contact:ids:201:all (THRIVE - 266)\n Config ID: 201\n Type: set\n Memory: 228 B\n TTL: 23h 59m 17s\n Count: 2\n Sample: 17229652, 23550651\n\nbatch_sync_contact:ids:253:all (Zymego - 322)\n Config ID: 253\n Type: set\n Memory: 228 B\n TTL: 23h 48m 32s\n Count: 2\n Sample: 9236122316, 108257234125\n\nbatch_sync_contact:ids:307:all (Story Terrace Inc - 379)\n Config ID: 307\n Type: set\n Memory: 228 B\n TTL: 23h 54m 36s\n Count: 2\n Sample: 216118037022, 216120038928\n\nbatch_sync_contact:ids:319:all (MySalesCoach - 400)\n Config ID: 319\n Type: set\n Memory: 228 B\n TTL: 23h 51m 16s\n Count: 2\n Sample: 191355036878, 759224982717\n\nbatch_sync_contact:ids:335:all (Eletive - 429)\n Config ID: 335\n Type: set\n Memory: 228 B\n TTL: 23h 58m 13s\n Count: 2\n Sample: 159795767866, 757397522679\n\nbatch_sync_contact:ids:339:all (inspera.no - 436)\n Config ID: 339\n Type: set\n Memory: 228 B\n TTL: 23h 55m 54s\n Count: 2\n Sample: 183336486505, 216074960287\n\nbatch_sync_contact:ids:412:all (Antavo - 500)\n Config ID: 412\n Type: set\n Memory: 228 B\n TTL: 23h 50m 30s\n Count: 2\n Sample: 644080299229, 705317319867\n\nbatch_sync_deal:ids:96:all (Nourish Care - 119)\n Config ID: 96\n Type: set\n Memory: 228 B\n TTL: 23h 51m 21s\n Count: 2\n Sample: 13955519955, 499101468869\n\nbatch_sync_deal:ids:253:all (Zymego - 322)\n Config ID: 253\n Type: set\n Memory: 228 B\n TTL: 23h 49m 0s\n Count: 2\n Sample: 499101779135, 499117828335\n\nbatch_sync_company:ids:95:all (Cronofy - 118)\n Config ID: 95\n Type: set\n Memory: 164 B\n TTL: 23h 44m 9s\n Count: 1\n Sample: 54084980481\n\nbatch_sync_company:ids:96:all (Nourish Care - 119)\n Config ID: 96\n Type: set\n Memory: 164 B\n TTL: 23h 51m 18s\n Count: 1\n Sample: 231946184895\n\nbatch_sync_company:ids:112:all (Switchee - 137)\n Config ID: 112\n Type: set\n Memory: 164 B\n TTL: 23h 45m 50s\n Count: 1\n Sample: 30902131597\n\nbatch_sync_company:ids:124:all (Intruder - 149)\n Config ID: 124\n Type: set\n Memory: 164 B\n TTL: 23h 49m 59s\n Count: 1\n Sample: 19200117829\n\nbatch_sync_company:ids:130:all (Latana Brand Tracking - 155)\n Config ID: 130\n Type: set\n Memory: 164 B\n TTL: 23h 43m 46s\n Count: 1\n Sample: 3412118684\n\nbatch_sync_company:ids:191:all (Orlo - 253)\n Config ID: 191\n Type: set\n Memory: 164 B\n TTL: 23h 53m 53s\n Count: 1\n Sample: 2492067355\n\nbatch_sync_company:ids:200:all (Jobadder - 265)\n Config ID: 200\n Type: set\n Memory: 164 B\n TTL: 23h 41m 16s\n Count: 1\n Sample: 54051603071\n\nbatch_sync_company:ids:253:all (Zymego - 322)\n Config ID: 253\n Type: set\n Memory: 164 B\n TTL: 23h 48m 34s\n Count: 1\n Sample: 45363246315\n\nbatch_sync_company:ids:295:all (APLYiD - 367)\n Config ID: 295\n Type: set\n Memory: 164 B\n TTL: 23h 43m 38s\n Count: 1\n Sample: 426645956821\n\nbatch_sync_company:ids:308:all (Foodles - 380)\n Config ID: 308\n Type: set\n Memory: 164 B\n TTL: 23h 43m 36s\n Count: 1\n Sample: 4623764126\n\nbatch_sync_company:ids:485:all (LATUS Group - 563)\n Config ID: 485\n Type: set\n Memory: 164 B\n TTL: 23h 59m 51s\n Count: 1\n Sample: 16393088676\n\nbatch_sync_contact:ids:81:all (Hurree - 104)\n Config ID: 81\n Type: set\n Memory: 164 B\n TTL: 23h 52m 48s\n Count: 1\n Sample: 216123172492\n\nbatch_sync_contact:ids:86:all (Teamtailor - 109)\n Config ID: 86\n Type: set\n Memory: 164 B\n TTL: 5h 35m 55s\n Count: 1\n Sample: 128480851\n\nbatch_sync_contact:ids:95:all (Cronofy - 118)\n Config ID: 95\n Type: set\n Memory: 164 B\n TTL: 23h 44m 9s\n Count: 1\n Sample: 216119828808\n\nbatch_sync_contact:ids:191:all (Orlo - 253)\n Config ID: 191\n Type: set\n Memory: 164 B\n TTL: 23h 43m 42s\n Count: 1\n Sample: 185805924409\n\nbatch_sync_contact:ids:200:all (Jobadder - 265)\n Config ID: 200\n Type: set\n Memory: 164 B\n TTL: 23h 41m 16s\n Count: 1\n Sample: 216122837601\n\nbatch_sync_contact:ids:255:all (Screendragon - 324)\n Config ID: 255\n Type: set\n Memory: 164 B\n TTL: 23h 44m 22s\n Count: 1\n Sample: 215982276420\n\nbatch_sync_contact:ids:278:all (Akixi - 348)\n Config ID: 278\n Type: set\n Memory: 164 B\n TTL: 23h 46m 52s\n Count: 1\n Sample: 758404365499\n\nbatch_sync_contact:ids:284:all (Brickflow (Property Funding Hub Ltd) - 354)\n Config ID: 284\n Type: set\n Memory: 164 B\n TTL: 23h 44m 47s\n Count: 1\n Sample: 195262486661\n\nbatch_sync_contact:ids:295:all (APLYiD - 367)\n Config ID: 295\n Type: set\n Memory: 164 B\n TTL: 23h 44m 11s\n Count: 1\n Sample: 759223062753\n\nbatch_sync_contact:ids:322:all (Talkative - 403)\n Config ID: 322\n Type: set\n Memory: 164 B\n TTL: 23h 56m 55s\n Count: 1\n Sample: 751262016755\n\nbatch_sync_contact:ids:367:all (Sensat - 459)\n Config ID: 367\n Type: set\n Memory: 164 B\n TTL: 23h 51m 33s\n Count: 1\n Sample: 215694537008\n\nbatch_sync_contact:ids:403:all (Fundrella - 491)\n Config ID: 403\n Type: set\n Memory: 164 B\n TTL: 23h 43m 28s\n Count: 1\n Sample: 147101\n\nbatch_sync_contact:ids:436:all (Moxso - 519)\n Config ID: 436\n Type: set\n Memory: 164 B\n TTL: 23h 54m 15s\n Count: 1\n Sample: 294701\n\nbatch_sync_contact:ids:491:all (CreateFuture - 570)\n Config ID: 491\n Type: set\n Memory: 164 B\n TTL: 23h 51m 48s\n Count: 1\n Sample: 214629839743\n\nbatch_sync_deal:ids:191:all (Orlo - 253)\n Config ID: 191\n Type: set\n Memory: 164 B\n TTL: 23h 54m 11s\n Count: 1\n Sample: 59230347609\n\nbatch_sync_deal:ids:200:all (Jobadder - 265)\n Config ID: 200\n Type: set\n Memory: 164 B\n TTL: 23h 50m 5s\n Count: 1\n Sample: 58878310503\n\nbatch_sync_deal:ids:255:all (Screendragon - 324)\n Config ID: 255\n Type: set\n Memory: 164 B\n TTL: 23h 44m 20s\n Count: 1\n Sample: 59243074931\n\nbatch_sync_deal:ids:335:all (Eletive - 429)\n Config ID: 335\n Type: set\n Memory: 164 B\n TTL: 23h 39m 13s\n Count: 1\n Sample: 498499653826\n\nbatch_sync_deal:ids:370:all (Buynomics - 462)\n Config ID: 370\n Type: set\n Memory: 164 B\n TTL: 5h 19m 0s\n Count: 1\n Sample: 10644871427\n\nbatch_sync_deal:ids:373:all (KPSBremen.de - 465)\n Config ID: 373\n Type: set\n Memory: 164 B\n TTL: 2h 41m 0s\n Count: 1\n Sample: 493652735187\n\nbatch_sync_deal:ids:483:all (Veremark - 561)\n Config ID: 483\n Type: set\n Memory: 164 B\n TTL: 23h 47m 59s\n Count: 1\n Sample: 59186288665\n\nroot@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook redis --key-type ids --detailed -T 459\n\n INFO Scanning Redis keys (type: ids). \n\n\n Total keys found ............................................................................................................................... 1 \n Total IDs across all keys ...................................................................................................................... 1 \n\n📊\u0000 Memory Overview\n Total Memory ............................................................................................................................... 164 B \n ids keys ................................................................................................................................... 164 B \n\nbatch_sync_contact:ids:367:all (Sensat - 459)\n Config ID: 367\n Type: set\n Memory: 164 B\n TTL: 23h 51m 6s\n Count: 1\n Sample: 215694537008\n\nroot@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook metrics -T 459\n\n INFO Webhook Metrics — 2026-04-17. \n\n📊\u0000 Webhook Metrics Summary\n==========================================\nDate: 2026-04-17\nFilters: Config: 367\nTotal Teams: 1\nTotal Webhooks: 7\n\n🏢\u0000 Config 367 (Sensat - 459) - 7 webhooks\n 📦\u0000 contact: 6 webhooks\n 🔔\u0000 property_change: 6 events, 5 properties\n 📦\u0000 deal: 1 webhooks\n 🔔\u0000 property_change: 1 events, 1 properties\n\nroot@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook metrics -T 459 --from 2026-04-15\n\n INFO Managing webhook metrics for date range. \n\n Date Range .............................................................................................................. 2026-04-15 to 2026-04-17 \n Config ID .................................................................................................................................... 367 \n\n📊\u0000 Range Summary\n Date Range .............................................................................................................. 2026-04-15 to 2026-04-17 \n Total Days ..................................................................................................................................... 3 \n Oldest Data Age ..................................................................................................................... 2.0 days ago \n Total Webhooks ......................................................................................................................... 1,065,651 \n Daily Average ......................................................................................................................... 355,217.00 \n Active Companies .............................................................................................................................. 89 \n\n🏢\u0000 Company Details\n\n Company 367 (Sensat - 459)\n Total Webhooks: 796\n Days Active: 3/3\n Daily Average: 265.33\nroot@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook metrics -T 459 --from 2026-04-15 -D\n\n INFO Managing webhook metrics for date range. \n\n Date Range .............................................................................................................. 2026-04-15 to 2026-04-17 \n Config ID .................................................................................................................................... 367 \n\n📊\u0000 Range Summary\n Date Range .............................................................................................................. 2026-04-15 to 2026-04-17 \n Total Days ..................................................................................................................................... 3 \n Oldest Data Age ..................................................................................................................... 2.0 days ago \n Total Webhooks ......................................................................................................................... 1,065,677 \n Daily Average ......................................................................................................................... 355,225.67 \n Active Companies .............................................................................................................................. 89 \n\n📅\u0000 Daily Breakdown\n 2026-04-15: 335,647 webhooks, 88 companies active\n 2026-04-16: 671,679 webhooks, 88 companies active\n 2026-04-17: 58,351 webhooks, 68 companies active\n\n🏢\u0000 Company Details\n\n Company 367 (Sensat - 459)\n Total Webhooks: 796\n Days Active: 3/3\n Daily Average: 265.33\n company (114 total, avg: 38)\n association_change: 92 total, avg: 46, active: 2 days\n creation: 3 total, avg: 1.5, active: 2 days\n property_change: 19 total, avg: 9.5, active: 2 days\n Unique properties: 4\n Top properties: hubspot_owner_id(12), domain(3), name(3), phone(1)\n deal (164 total, avg: 54.67)\n property_change: 164 total, avg: 54.67, active: 3 days\n Unique properties: 8\n Top properties: notes_last_updated(134), closedate(7), dealstage(5), hs_deal_stage_probability(5), hs_manual_forecast_category(5)\n contact (518 total, avg: 172.67)\n property_change: 390 total, avg: 130, active: 3 days\n Unique properties: 9\n Top properties: hubspot_owner_id(186), firstname(35), email(35), associatedcompanyid(33), country(33)\n creation: 36 total, avg: 18, active: 2 days\n association_change: 92 total, avg: 46, active: 2 days\nroot@67e84f80b9d1:/home/jiminny#","depth":4,"value":"UW PICO 5.09 New Buffer \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n [ Read 137 lines ] \n^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos \n^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell \nLast login: Fri Apr 17 10:32:22 on ttys013\n/Users/lukas/.zprofile:138: unmatched \"\n/Users/lukas/.zprofile:138: unmatched \"\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ % veu\nzsh: command not found: veu\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ % zp\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ % ssh jiminny-eu-ecs1\nEnter MFA code for arn:aws:iam::438740370364:mfa/lukas.kovalik@jiminny.com: \nWarning: Permanently added 'jiminny-eu-ecs1' (ED25519) to the list of known hosts.\n\nA newer release of \"Amazon Linux\" is available.\n Version 2023.10.20260105:\n Version 2023.10.20260120:\n Version 2023.10.20260202:\n Version 2023.10.20260216:\n Version 2023.10.20260302:\n Version 2023.10.20260325:\n Version 2023.10.20260330:\n Version 2023.11.20260406:\n Version 2023.11.20260413:\n Version 2023.8.20250707:\n Version 2023.8.20250715:\n Version 2023.8.20250721:\n Version 2023.8.20250808:\n Version 2023.8.20250818:\n Version 2023.8.20250908:\n Version 2023.8.20250915:\n Version 2023.9.20250929:\n Version 2023.9.20251014:\n Version 2023.9.20251020:\n Version 2023.9.20251027:\n Version 2023.9.20251105:\n Version 2023.9.20251110:\n Version 2023.9.20251117:\n Version 2023.9.20251208:\nRun \"/usr/bin/dnf check-release-update\" for full release and version update info\n , #_\n ~\\_ ####_\n ~~ \\_#####\\\n ~~ \\###|\n ~~ \\#/ ___ Amazon Linux 2023 (ECS Optimized)\n ~~ V~' '->\n ~~~ /\n ~~._. _/\n _/ _/\n _/m/'\n\nFor documentation, visit http://aws.amazon.com/documentation/ecs\nLast login: Thu Apr 16 11:09:44 2026 from 10.20.163.228\n[ec2-user@ip-10-20-6-111 ~]$ docker exec -it $(docker ps --format \"{{.ID}}\" --filter \"name=ecs-worker\" | head -1) /bin/bash -c \"cd /home/jiminny && bash\"\nroot@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook redis --key-type ids --detailed \n\n INFO Scanning Redis keys (type: ids). \n\n\n Total keys found .............................................................................................................................. 87 \n Total IDs across all keys ................................................................................................................. 18,991 \n\n📊\u0000 Memory Overview\n Total Memory ............................................................................................................................. 1.17 MB \n ids keys ................................................................................................................................. 1.17 MB \n\nbatch_sync_deal:ids:338:all (Formalize - 432)\n Config ID: 338\n Type: set\n Memory: 582.22 KB\n TTL: 23h 26m 22s\n Count: 9314\n Sample: 36225705250, 29787925122, 329967686878\n\nbatch_sync_contact:ids:439:all (Log My Care - 522)\n Config ID: 439\n Type: set\n Memory: 203.66 KB\n TTL: 15h 27m 1s\n Count: 3257\n Sample: 4723151, 569384381669, 751397913846\n\nbatch_sync_contact:ids:370:all (Buynomics - 462)\n Config ID: 370\n Type: set\n Memory: 159.54 KB\n TTL: 23h 58m 24s\n Count: 2551\n Sample: 7855597, 214629714698, 1081551\n\nbatch_sync_company:ids:439:all (Log My Care - 522)\n Config ID: 439\n Type: set\n Memory: 143.97 KB\n TTL: 15h 26m 38s\n Count: 2302\n Sample: 97509907664, 287839612106, 13347420118\n\nbatch_sync_company:ids:370:all (Buynomics - 462)\n Config ID: 370\n Type: set\n Memory: 28.54 KB\n TTL: 23h 57m 58s\n Count: 455\n Sample: 53416555927, 5299782086, 48272143029\n\nbatch_sync_company:ids:346:all (Global Expansion - 444)\n Config ID: 346\n Type: set\n Memory: 17.66 KB\n TTL: 23h 59m 57s\n Count: 281\n Sample: 54084980525, 31037091957, 16036051109\n\nbatch_sync_contact:ids:346:all (Global Expansion - 444)\n Config ID: 346\n Type: set\n Memory: 12.6 KB\n TTL: 23h 59m 57s\n Count: 200\n Sample: 216133633092, 216059621965, 216123582828\n\nbatch_sync_contact:ids:373:all (KPSBremen.de - 465)\n Config ID: 373\n Type: set\n Memory: 4.35 KB\n TTL: 23h 42m 32s\n Count: 68\n Sample: 751746229489, 662801482974, 751204849893\n\nbatch_sync_company:ids:449:all (SiSU Health UK - 531)\n Config ID: 449\n Type: set\n Memory: 3.6 KB\n TTL: 8h 7m 26s\n Count: 56\n Sample: 293769448657, 7905289206, 6994524653\n\nbatch_sync_contact:ids:449:all (SiSU Health UK - 531)\n Config ID: 449\n Type: set\n Memory: 3.47 KB\n TTL: 22h 45m 52s\n Count: 54\n Sample: 730208, 752394791155, 759217738995\n\nbatch_sync_deal:ids:439:all (Log My Care - 522)\n Config ID: 439\n Type: set\n Memory: 3.47 KB\n TTL: 9h 4m 51s\n Count: 54\n Sample: 364507398345, 494814103757, 494016526555\n\nbatch_sync_company:ids:373:all (KPSBremen.de - 465)\n Config ID: 373\n Type: set\n Memory: 3.41 KB\n TTL: 23h 55m 1s\n Count: 53\n Sample: 399250577648, 426553672928, 379424069826\n\nbatch_sync_deal:ids:449:all (SiSU Health UK - 531)\n Config ID: 449\n Type: set\n Memory: 3.04 KB\n TTL: 8h 7m 33s\n Count: 47\n Sample: 498503490753, 498500636859, 498501354684\n\nbatch_sync_contact:ids:331:all (The National College - 416)\n Config ID: 331\n Type: set\n Memory: 1.6 KB\n TTL: 23h 59m 35s\n Count: 24\n Sample: 753335460029, 759353955559, 600494578893\n\nbatch_sync_company:ids:331:all (The National College - 416)\n Config ID: 331\n Type: set\n Memory: 1.47 KB\n TTL: 23h 59m 22s\n Count: 22\n Sample: 23755815000, 5684894521, 3972787939\n\nbatch_sync_deal:ids:331:all (The National College - 416)\n Config ID: 331\n Type: set\n Memory: 1.47 KB\n TTL: 23h 55m 17s\n Count: 22\n Sample: 499035527382, 494192151741, 499111919842\n\nbatch_sync_contact:ids:488:all (MakeMyHouseGreen - 567)\n Config ID: 488\n Type: set\n Memory: 1.41 KB\n TTL: 23h 57m 17s\n Count: 21\n Sample: 935601, 4269001, 2258\n\nbatch_sync_contact:ids:170:all (LutherOne - 199)\n Config ID: 170\n Type: set\n Memory: 996 B\n TTL: 23h 59m 49s\n Count: 14\n Sample: 6438163, 18894909, 131340729177\n\nbatch_sync_contact:ids:364:all (Lead Forensics - 190)\n Config ID: 364\n Type: set\n Memory: 932 B\n TTL: 23h 56m 58s\n Count: 13\n Sample: 103762162328, 103730206004, 103746035125\n\nbatch_sync_contact:ids:124:all (Intruder - 149)\n Config ID: 124\n Type: set\n Memory: 868 B\n TTL: 23h 53m 21s\n Count: 12\n Sample: 209000212364, 72073201, 32825651463\n\nbatch_sync_contact:ids:70:all (Scoro - 93)\n Config ID: 70\n Type: set\n Memory: 804 B\n TTL: 23h 49m 7s\n Count: 11\n Sample: 548911252691, 549509632216, 549499217106\n\nbatch_sync_contact:ids:483:all (Veremark - 561)\n Config ID: 483\n Type: set\n Memory: 804 B\n TTL: 23h 55m 11s\n Count: 11\n Sample: 215753056547, 215474268483, 215540281168\n\nbatch_sync_contact:ids:363:all (Global Group - 456)\n Config ID: 363\n Type: set\n Memory: 548 B\n TTL: 23h 57m 2s\n Count: 7\n Sample: 156053546908, 14789232785, 1569201\n\nbatch_sync_deal:ids:455:all (Argos Security - 537)\n Config ID: 455\n Type: set\n Memory: 548 B\n TTL: 23h 54m 15s\n Count: 7\n Sample: 498986026223, 17924102876, 498928162009\n\nbatch_sync_deal:ids:461:all (Fieldly - 543)\n Config ID: 461\n Type: set\n Memory: 548 B\n TTL: 23h 59m 34s\n Count: 7\n Sample: 498984577267, 499024879840, 496749330660\n\nbatch_sync_company:ids:465:all (Spotler - 545)\n Config ID: 465\n Type: set\n Memory: 484 B\n TTL: 23h 53m 55s\n Count: 6\n Sample: 12886901694, 47800033493, 426487803113\n\nbatch_sync_company:ids:483:all (Veremark - 561)\n Config ID: 483\n Type: set\n Memory: 484 B\n TTL: 23h 53m 12s\n Count: 6\n Sample: 54056532238, 25291493956, 52568117585\n\nbatch_sync_contact:ids:465:all (Spotler - 545)\n Config ID: 465\n Type: set\n Memory: 484 B\n TTL: 23h 53m 55s\n Count: 6\n Sample: 25784346850, 758403684599, 758356838593\n\nbatch_sync_contact:ids:96:all (Nourish Care - 119)\n Config ID: 96\n Type: set\n Memory: 420 B\n TTL: 23h 51m 15s\n Count: 5\n Sample: 759339342027, 6476279789, 759338177732\n\nbatch_sync_deal:ids:241:all (PatentRenewal.com ApS - 306)\n Config ID: 241\n Type: set\n Memory: 420 B\n TTL: 23h 59m 34s\n Count: 5\n Sample: 57554678843, 59164863320, 40205117839\n\nbatch_sync_company:ids:175:all (Team iAM - 203)\n Config ID: 175\n Type: set\n Memory: 356 B\n TTL: 23h 52m 11s\n Count: 4\n Sample: 31289463313, 53432109235, 47337393078\n\nbatch_sync_contact:ids:197:all (Kindly - 264)\n Config ID: 197\n Type: set\n Memory: 356 B\n TTL: 23h 56m 33s\n Count: 4\n Sample: 759337959662, 759340603582, 759336916201\n\nbatch_sync_contact:ids:308:all (Foodles - 380)\n Config ID: 308\n Type: set\n Memory: 356 B\n TTL: 23h 47m 13s\n Count: 4\n Sample: 209605481386, 216074957452, 216074957451\n\nbatch_sync_contact:ids:461:all (Fieldly - 543)\n Config ID: 461\n Type: set\n Memory: 356 B\n TTL: 23h 59m 38s\n Count: 4\n Sample: 758403694783, 6338351, 755171302642\n\nbatch_sync_contact:ids:485:all (LATUS Group - 563)\n Config ID: 485\n Type: set\n Memory: 356 B\n TTL: 23h 59m 51s\n Count: 4\n Sample: 216014615839, 216124673101, 215534275979\n\nbatch_sync_company:ids:170:all (LutherOne - 199)\n Config ID: 170\n Type: set\n Memory: 292 B\n TTL: 23h 55m 25s\n Count: 3\n Sample: 5061344699, 8979817578, 53544705830\n\nbatch_sync_company:ids:197:all (Kindly - 264)\n Config ID: 197\n Type: set\n Memory: 292 B\n TTL: 23h 56m 26s\n Count: 3\n Sample: 426652716232, 426652949738, 426671486159\n\nbatch_sync_company:ids:436:all (Moxso - 519)\n Config ID: 436\n Type: set\n Memory: 292 B\n TTL: 23h 54m 5s\n Count: 3\n Sample: 5637526753, 9027001061, 426485659872\n\nbatch_sync_company:ids:455:all (Argos Security - 537)\n Config ID: 455\n Type: set\n Memory: 292 B\n TTL: 23h 49m 25s\n Count: 3\n Sample: 12009203674, 12897632476, 331995461840\n\nbatch_sync_contact:ids:130:all (Latana Brand Tracking - 155)\n Config ID: 130\n Type: set\n Memory: 292 B\n TTL: 23h 44m 14s\n Count: 3\n Sample: 216074963815, 216074963816, 216074963817\n\nbatch_sync_contact:ids:175:all (Team iAM - 203)\n Config ID: 175\n Type: set\n Memory: 292 B\n TTL: 23h 52m 37s\n Count: 3\n Sample: 212581164888, 216121454962, 216121861967\n\nbatch_sync_contact:ids:455:all (Argos Security - 537)\n Config ID: 455\n Type: set\n Memory: 292 B\n TTL: 23h 49m 25s\n Count: 3\n Sample: 15081981630, 28607253707, 589985456367\n\nbatch_sync_deal:ids:465:all (Spotler - 545)\n Config ID: 465\n Type: set\n Memory: 292 B\n TTL: 23h 54m 43s\n Count: 3\n Sample: 498984572105, 498986026222, 498987447516\n\nbatch_sync_company:ids:70:all (Scoro - 93)\n Config ID: 70\n Type: set\n Memory: 228 B\n TTL: 23h 48m 59s\n Count: 2\n Sample: 308374847674, 426652674241\n\nbatch_sync_company:ids:461:all (Fieldly - 543)\n Config ID: 461\n Type: set\n Memory: 228 B\n TTL: 23h 57m 6s\n Count: 2\n Sample: 612188920, 1045770343\n\nbatch_sync_contact:ids:112:all (Switchee - 137)\n Config ID: 112\n Type: set\n Memory: 228 B\n TTL: 23h 50m 12s\n Count: 2\n Sample: 216074959999, 216074960000\n\nbatch_sync_contact:ids:201:all (THRIVE - 266)\n Config ID: 201\n Type: set\n Memory: 228 B\n TTL: 23h 59m 17s\n Count: 2\n Sample: 17229652, 23550651\n\nbatch_sync_contact:ids:253:all (Zymego - 322)\n Config ID: 253\n Type: set\n Memory: 228 B\n TTL: 23h 48m 32s\n Count: 2\n Sample: 9236122316, 108257234125\n\nbatch_sync_contact:ids:307:all (Story Terrace Inc - 379)\n Config ID: 307\n Type: set\n Memory: 228 B\n TTL: 23h 54m 36s\n Count: 2\n Sample: 216118037022, 216120038928\n\nbatch_sync_contact:ids:319:all (MySalesCoach - 400)\n Config ID: 319\n Type: set\n Memory: 228 B\n TTL: 23h 51m 16s\n Count: 2\n Sample: 191355036878, 759224982717\n\nbatch_sync_contact:ids:335:all (Eletive - 429)\n Config ID: 335\n Type: set\n Memory: 228 B\n TTL: 23h 58m 13s\n Count: 2\n Sample: 159795767866, 757397522679\n\nbatch_sync_contact:ids:339:all (inspera.no - 436)\n Config ID: 339\n Type: set\n Memory: 228 B\n TTL: 23h 55m 54s\n Count: 2\n Sample: 183336486505, 216074960287\n\nbatch_sync_contact:ids:412:all (Antavo - 500)\n Config ID: 412\n Type: set\n Memory: 228 B\n TTL: 23h 50m 30s\n Count: 2\n Sample: 644080299229, 705317319867\n\nbatch_sync_deal:ids:96:all (Nourish Care - 119)\n Config ID: 96\n Type: set\n Memory: 228 B\n TTL: 23h 51m 21s\n Count: 2\n Sample: 13955519955, 499101468869\n\nbatch_sync_deal:ids:253:all (Zymego - 322)\n Config ID: 253\n Type: set\n Memory: 228 B\n TTL: 23h 49m 0s\n Count: 2\n Sample: 499101779135, 499117828335\n\nbatch_sync_company:ids:95:all (Cronofy - 118)\n Config ID: 95\n Type: set\n Memory: 164 B\n TTL: 23h 44m 9s\n Count: 1\n Sample: 54084980481\n\nbatch_sync_company:ids:96:all (Nourish Care - 119)\n Config ID: 96\n Type: set\n Memory: 164 B\n TTL: 23h 51m 18s\n Count: 1\n Sample: 231946184895\n\nbatch_sync_company:ids:112:all (Switchee - 137)\n Config ID: 112\n Type: set\n Memory: 164 B\n TTL: 23h 45m 50s\n Count: 1\n Sample: 30902131597\n\nbatch_sync_company:ids:124:all (Intruder - 149)\n Config ID: 124\n Type: set\n Memory: 164 B\n TTL: 23h 49m 59s\n Count: 1\n Sample: 19200117829\n\nbatch_sync_company:ids:130:all (Latana Brand Tracking - 155)\n Config ID: 130\n Type: set\n Memory: 164 B\n TTL: 23h 43m 46s\n Count: 1\n Sample: 3412118684\n\nbatch_sync_company:ids:191:all (Orlo - 253)\n Config ID: 191\n Type: set\n Memory: 164 B\n TTL: 23h 53m 53s\n Count: 1\n Sample: 2492067355\n\nbatch_sync_company:ids:200:all (Jobadder - 265)\n Config ID: 200\n Type: set\n Memory: 164 B\n TTL: 23h 41m 16s\n Count: 1\n Sample: 54051603071\n\nbatch_sync_company:ids:253:all (Zymego - 322)\n Config ID: 253\n Type: set\n Memory: 164 B\n TTL: 23h 48m 34s\n Count: 1\n Sample: 45363246315\n\nbatch_sync_company:ids:295:all (APLYiD - 367)\n Config ID: 295\n Type: set\n Memory: 164 B\n TTL: 23h 43m 38s\n Count: 1\n Sample: 426645956821\n\nbatch_sync_company:ids:308:all (Foodles - 380)\n Config ID: 308\n Type: set\n Memory: 164 B\n TTL: 23h 43m 36s\n Count: 1\n Sample: 4623764126\n\nbatch_sync_company:ids:485:all (LATUS Group - 563)\n Config ID: 485\n Type: set\n Memory: 164 B\n TTL: 23h 59m 51s\n Count: 1\n Sample: 16393088676\n\nbatch_sync_contact:ids:81:all (Hurree - 104)\n Config ID: 81\n Type: set\n Memory: 164 B\n TTL: 23h 52m 48s\n Count: 1\n Sample: 216123172492\n\nbatch_sync_contact:ids:86:all (Teamtailor - 109)\n Config ID: 86\n Type: set\n Memory: 164 B\n TTL: 5h 35m 55s\n Count: 1\n Sample: 128480851\n\nbatch_sync_contact:ids:95:all (Cronofy - 118)\n Config ID: 95\n Type: set\n Memory: 164 B\n TTL: 23h 44m 9s\n Count: 1\n Sample: 216119828808\n\nbatch_sync_contact:ids:191:all (Orlo - 253)\n Config ID: 191\n Type: set\n Memory: 164 B\n TTL: 23h 43m 42s\n Count: 1\n Sample: 185805924409\n\nbatch_sync_contact:ids:200:all (Jobadder - 265)\n Config ID: 200\n Type: set\n Memory: 164 B\n TTL: 23h 41m 16s\n Count: 1\n Sample: 216122837601\n\nbatch_sync_contact:ids:255:all (Screendragon - 324)\n Config ID: 255\n Type: set\n Memory: 164 B\n TTL: 23h 44m 22s\n Count: 1\n Sample: 215982276420\n\nbatch_sync_contact:ids:278:all (Akixi - 348)\n Config ID: 278\n Type: set\n Memory: 164 B\n TTL: 23h 46m 52s\n Count: 1\n Sample: 758404365499\n\nbatch_sync_contact:ids:284:all (Brickflow (Property Funding Hub Ltd) - 354)\n Config ID: 284\n Type: set\n Memory: 164 B\n TTL: 23h 44m 47s\n Count: 1\n Sample: 195262486661\n\nbatch_sync_contact:ids:295:all (APLYiD - 367)\n Config ID: 295\n Type: set\n Memory: 164 B\n TTL: 23h 44m 11s\n Count: 1\n Sample: 759223062753\n\nbatch_sync_contact:ids:322:all (Talkative - 403)\n Config ID: 322\n Type: set\n Memory: 164 B\n TTL: 23h 56m 55s\n Count: 1\n Sample: 751262016755\n\nbatch_sync_contact:ids:367:all (Sensat - 459)\n Config ID: 367\n Type: set\n Memory: 164 B\n TTL: 23h 51m 33s\n Count: 1\n Sample: 215694537008\n\nbatch_sync_contact:ids:403:all (Fundrella - 491)\n Config ID: 403\n Type: set\n Memory: 164 B\n TTL: 23h 43m 28s\n Count: 1\n Sample: 147101\n\nbatch_sync_contact:ids:436:all (Moxso - 519)\n Config ID: 436\n Type: set\n Memory: 164 B\n TTL: 23h 54m 15s\n Count: 1\n Sample: 294701\n\nbatch_sync_contact:ids:491:all (CreateFuture - 570)\n Config ID: 491\n Type: set\n Memory: 164 B\n TTL: 23h 51m 48s\n Count: 1\n Sample: 214629839743\n\nbatch_sync_deal:ids:191:all (Orlo - 253)\n Config ID: 191\n Type: set\n Memory: 164 B\n TTL: 23h 54m 11s\n Count: 1\n Sample: 59230347609\n\nbatch_sync_deal:ids:200:all (Jobadder - 265)\n Config ID: 200\n Type: set\n Memory: 164 B\n TTL: 23h 50m 5s\n Count: 1\n Sample: 58878310503\n\nbatch_sync_deal:ids:255:all (Screendragon - 324)\n Config ID: 255\n Type: set\n Memory: 164 B\n TTL: 23h 44m 20s\n Count: 1\n Sample: 59243074931\n\nbatch_sync_deal:ids:335:all (Eletive - 429)\n Config ID: 335\n Type: set\n Memory: 164 B\n TTL: 23h 39m 13s\n Count: 1\n Sample: 498499653826\n\nbatch_sync_deal:ids:370:all (Buynomics - 462)\n Config ID: 370\n Type: set\n Memory: 164 B\n TTL: 5h 19m 0s\n Count: 1\n Sample: 10644871427\n\nbatch_sync_deal:ids:373:all (KPSBremen.de - 465)\n Config ID: 373\n Type: set\n Memory: 164 B\n TTL: 2h 41m 0s\n Count: 1\n Sample: 493652735187\n\nbatch_sync_deal:ids:483:all (Veremark - 561)\n Config ID: 483\n Type: set\n Memory: 164 B\n TTL: 23h 47m 59s\n Count: 1\n Sample: 59186288665\n\nroot@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook redis --key-type ids --detailed -T 459\n\n INFO Scanning Redis keys (type: ids). \n\n\n Total keys found ............................................................................................................................... 1 \n Total IDs across all keys ...................................................................................................................... 1 \n\n📊\u0000 Memory Overview\n Total Memory ............................................................................................................................... 164 B \n ids keys ................................................................................................................................... 164 B \n\nbatch_sync_contact:ids:367:all (Sensat - 459)\n Config ID: 367\n Type: set\n Memory: 164 B\n TTL: 23h 51m 6s\n Count: 1\n Sample: 215694537008\n\nroot@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook metrics -T 459\n\n INFO Webhook Metrics — 2026-04-17. \n\n📊\u0000 Webhook Metrics Summary\n==========================================\nDate: 2026-04-17\nFilters: Config: 367\nTotal Teams: 1\nTotal Webhooks: 7\n\n🏢\u0000 Config 367 (Sensat - 459) - 7 webhooks\n 📦\u0000 contact: 6 webhooks\n 🔔\u0000 property_change: 6 events, 5 properties\n 📦\u0000 deal: 1 webhooks\n 🔔\u0000 property_change: 1 events, 1 properties\n\nroot@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook metrics -T 459 --from 2026-04-15\n\n INFO Managing webhook metrics for date range. \n\n Date Range .............................................................................................................. 2026-04-15 to 2026-04-17 \n Config ID .................................................................................................................................... 367 \n\n📊\u0000 Range Summary\n Date Range .............................................................................................................. 2026-04-15 to 2026-04-17 \n Total Days ..................................................................................................................................... 3 \n Oldest Data Age ..................................................................................................................... 2.0 days ago \n Total Webhooks ......................................................................................................................... 1,065,651 \n Daily Average ......................................................................................................................... 355,217.00 \n Active Companies .............................................................................................................................. 89 \n\n🏢\u0000 Company Details\n\n Company 367 (Sensat - 459)\n Total Webhooks: 796\n Days Active: 3/3\n Daily Average: 265.33\nroot@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook metrics -T 459 --from 2026-04-15 -D\n\n INFO Managing webhook metrics for date range. \n\n Date Range .............................................................................................................. 2026-04-15 to 2026-04-17 \n Config ID .................................................................................................................................... 367 \n\n📊\u0000 Range Summary\n Date Range .............................................................................................................. 2026-04-15 to 2026-04-17 \n Total Days ..................................................................................................................................... 3 \n Oldest Data Age ..................................................................................................................... 2.0 days ago \n Total Webhooks ......................................................................................................................... 1,065,677 \n Daily Average ......................................................................................................................... 355,225.67 \n Active Companies .............................................................................................................................. 89 \n\n📅\u0000 Daily Breakdown\n 2026-04-15: 335,647 webhooks, 88 companies active\n 2026-04-16: 671,679 webhooks, 88 companies active\n 2026-04-17: 58,351 webhooks, 68 companies active\n\n🏢\u0000 Company Details\n\n Company 367 (Sensat - 459)\n Total Webhooks: 796\n Days Active: 3/3\n Daily Average: 265.33\n company (114 total, avg: 38)\n association_change: 92 total, avg: 46, active: 2 days\n creation: 3 total, avg: 1.5, active: 2 days\n property_change: 19 total, avg: 9.5, active: 2 days\n Unique properties: 4\n Top properties: hubspot_owner_id(12), domain(3), name(3), phone(1)\n deal (164 total, avg: 54.67)\n property_change: 164 total, avg: 54.67, active: 3 days\n Unique properties: 8\n Top properties: notes_last_updated(134), closedate(7), dealstage(5), hs_deal_stage_probability(5), hs_manual_forecast_category(5)\n contact (518 total, avg: 172.67)\n property_change: 390 total, avg: 130, active: 3 days\n Unique properties: 9\n Top properties: hubspot_owner_id(186), firstname(35), email(35), associatedcompanyid(33), country(33)\n creation: 36 total, avg: 18, active: 2 days\n association_change: 92 total, avg: 46, active: 2 days\nroot@67e84f80b9d1:/home/jiminny#","is_focused":true},{"role":"AXRadioButton","text":"DOCKER","depth":2,"bounds":{"left":0.00069444446,"top":0.06,"width":0.12291667,"height":0.026666667},"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.0048611113,"top":0.064444445,"width":0.011111111,"height":0.017777778},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"DEV (-zsh)","depth":2,"bounds":{"left":0.12361111,"top":0.06,"width":0.12291667,"height":0.026666667},"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.12777779,"top":0.064444445,"width":0.011111111,"height":0.017777778},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"APP (-zsh)","depth":2,"bounds":{"left":0.24652778,"top":0.06,"width":0.12291667,"height":0.026666667},"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.25069445,"top":0.064444445,"width":0.011111111,"height":0.017777778},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.36944443,"top":0.06,"width":0.12291667,"height":0.026666667},"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.37361112,"top":0.064444445,"width":0.011111111,"height":0.017777778},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.4923611,"top":0.06,"width":0.12291667,"height":0.026666667},"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.4965278,"top":0.064444445,"width":0.011111111,"height":0.017777778},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"✳ Review screenpipe usage and Boosteroid integration (claude)","depth":2,"bounds":{"left":0.61527777,"top":0.06,"width":0.12291667,"height":0.026666667},"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.61944443,"top":0.064444445,"width":0.011111111,"height":0.017777778},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"ec2-user@ip-10-30-159-186:~ (nc)","depth":2,"bounds":{"left":0.73819447,"top":0.06,"width":0.12291667,"height":0.026666667},"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.7423611,"top":0.064444445,"width":0.011111111,"height":0.017777778},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"ec2-user@ip-10-20-6-111:~ (nc)","depth":2,"bounds":{"left":0.8611111,"top":0.06,"width":0.12291667,"height":0.026666667},"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.86527777,"top":0.064444445,"width":0.011111111,"height":0.017777778},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"⌥⌘1","depth":1,"bounds":{"left":0.95555556,"top":0.033333335,"width":0.03888889,"height":0.018888889},"automation_id":"_NS:8","role_description":"text"},{"role":"AXStaticText","text":"ec2-user@ip-10-20-6-111:~","depth":1,"bounds":{"left":0.43611112,"top":0.034444444,"width":0.12916666,"height":0.017777778},"role_description":"text"}]...
|
-2337461687480936
|
-4666508300195287295
|
app_switch
|
accessibility
|
NULL
|
UW PICO 5.09 UW PICO 5.09 New Buffer
[ Read 137 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
Last login: Fri Apr 17 10:32:22 on ttys013
/Users/lukas/.zprofile:138: unmatched "
/Users/lukas/.zprofile:138: unmatched "
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ % veu
zsh: command not found: veu
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ % zp
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ % ssh jiminny-eu-ecs1
Enter MFA code for arn:aws:iam::438740370364:mfa/[EMAIL]:
Warning: Permanently added 'jiminny-eu-ecs1' (ED25519) to the list of known hosts.
A newer release of "Amazon Linux" is available.
Version 2023.10.20260105:
Version 2023.10.20260120:
Version 2023.10.20260202:
Version 2023.10.20260216:
Version 2023.10.20260302:
Version 2023.10.20260325:
Version 2023.10.20260330:
Version 2023.11.20260406:
Version 2023.11.20260413:
Version 2023.8.20250707:
Version 2023.8.20250715:
Version 2023.8.20250721:
Version 2023.8.20250808:
Version 2023.8.20250818:
Version 2023.8.20250908:
Version 2023.8.20250915:
Version 2023.9.20250929:
Version 2023.9.20251014:
Version 2023.9.20251020:
Version 2023.9.20251027:
Version 2023.9.20251105:
Version 2023.9.20251110:
Version 2023.9.20251117:
Version 2023.9.20251208:
Run "/usr/bin/dnf check-release-update" for full release and version update info
, #_
~\_ ####_
~~ \_#####\
~~ \###|
~~ \#/ ___ Amazon Linux 2023 (ECS Optimized)
~~ V~' '->
~~~ /
~~._. _/
_/ _/
_/m/'
For documentation, visit [URL_WITH_CREDENTIALS] ~]$ docker exec -it $(docker ps --format "{{.ID}}" --filter "name=ecs-worker" | head -1) /bin/bash -c "cd /home/jiminny && bash"
root@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook redis --key-type ids --detailed
INFO Scanning Redis keys (type: ids).
Total keys found [PASSWORD_DOTS] 87
Total IDs across all keys [PASSWORD_DOTS] 18,991
📊 Memory Overview
Total Memory [PASSWORD_DOTS] 1.17 MB
ids keys [PASSWORD_DOTS] 1.17 MB
batch_sync_deal:ids:338:all (Formalize - 432)
Config ID: 338
Type: set
Memory: 582.22 KB
TTL: 23h 26m 22s
Count: 9314
Sample: 36225705250, 29787925122, 329967686878
batch_sync_contact:ids:439:all (Log My Care - 522)
Config ID: 439
Type: set
Memory: 203.66 KB
TTL: 15h 27m 1s
Count: 3257
Sample: 4723151, 569384381669, 751397913846
batch_sync_contact:ids:370:all (Buynomics - 462)
Config ID: 370
Type: set
Memory: 159.54 KB
TTL: 23h 58m 24s
Count: 2551
Sample: 7855597, 214629714698, 1081551
batch_sync_company:ids:439:all (Log My Care - 522)
Config ID: 439
Type: set
Memory: 143.97 KB
TTL: 15h 26m 38s
Count: 2302
Sample: 97509907664, 287839612106, 13347420118
batch_sync_company:ids:370:all (Buynomics - 462)
Config ID: 370
Type: set
Memory: 28.54 KB
TTL: 23h 57m 58s
Count: 455
Sample: 53416555927, 5299782086, 48272143029
batch_sync_company:ids:346:all (Global Expansion - 444)
Config ID: 346
Type: set
Memory: 17.66 KB
TTL: 23h 59m 57s
Count: 281
Sample: 54084980525, 31037091957, 16036051109
batch_sync_contact:ids:346:all (Global Expansion - 444)
Config ID: 346
Type: set
Memory: 12.6 KB
TTL: 23h 59m 57s
Count: 200
Sample: 216133633092, 216059621965, 216123582828
batch_sync_contact:ids:373:all (KPSBremen.de - 465)
Config ID: 373
Type: set
Memory: 4.35 KB
TTL: 23h 42m 32s
Count: 68
Sample: 751746229489, 662801482974, 751204849893
batch_sync_company:ids:449:all (SiSU Health UK - 531)
Config ID: 449
Type: set
Memory: 3.6 KB
TTL: 8h 7m 26s
Count: 56
Sample: 293769448657, 7905289206, 6994524653
batch_sync_contact:ids:449:all (SiSU Health UK - 531)
Config ID: 449
Type: set
Memory: 3.47 KB
TTL: 22h 45m 52s
Count: 54
Sample: 730208, 752394791155, 759217738995
batch_sync_deal:ids:439:all (Log My Care - 522)
Config ID: 439
Type: set
Memory: 3.47 KB
TTL: 9h 4m 51s
Count: 54
Sample: 364507398345, 494814103757, 494016526555
batch_sync_company:ids:373:all (KPSBremen.de - 465)
Config ID: 373
Type: set
Memory: 3.41 KB
TTL: 23h 55m 1s
Count: 53
Sample: 399250577648, 426553672928, 379424069826
batch_sync_deal:ids:449:all (SiSU Health UK - 531)
Config ID: 449
Type: set
Memory: 3.04 KB
TTL: 8h 7m 33s
Count: 47
Sample: 498503490753, 498500636859, 498501354684
batch_sync_contact:ids:331:all (The National College - 416)
Config ID: 331
Type: set
Memory: 1.6 KB
TTL: 23h 59m 35s
Count: 24
Sample: 753335460029, 759353955559, 600494578893
batch_sync_company:ids:331:all (The National College - 416)
Config ID: 331
Type: set
Memory: 1.47 KB
TTL: 23h 59m 22s
Count: 22
Sample: 23755815000, 5684894521, 3972787939
batch_sync_deal:ids:331:all (The National College - 416)
Config ID: 331
Type: set
Memory: 1.47 KB
TTL: 23h 55m 17s
Count: 22
Sample: 499035527382, 494192151741, 499111919842
batch_sync_contact:ids:488:all (MakeMyHouseGreen - 567)
Config ID: 488
Type: set
Memory: 1.41 KB
TTL: 23h 57m 17s
Count: 21
Sample: 935601, 4269001, 2258
batch_sync_contact:ids:170:all (LutherOne - 199)
Config ID: 170
Type: set
Memory: 996 B
TTL: 23h 59m 49s
Count: 14
Sample: 6438163, 18894909, 131340729177
batch_sync_contact:ids:364:all (Lead Forensics - 190)
Config ID: 364
Type: set
Memory: 932 B
TTL: 23h 56m 58s
Count: 13
Sample: 103762162328, 103730206004, 103746035125
batch_sync_contact:ids:124:all (Intruder - 149)
Config ID: 124
Type: set
Memory: 868 B
TTL: 23h 53m 21s
Count: 12
Sample: 209000212364, 72073201, 32825651463
batch_sync_contact:ids:70:all (Scoro - 93)
Config ID: 70
Type: set
Memory: 804 B
TTL: 23h 49m 7s
Count: 11
Sample: 548911252691, 549509632216, 549499217106
batch_sync_contact:ids:483:all (Veremark - 561)
Config ID: 483
Type: set
Memory: 804 B
TTL: 23h 55m 11s
Count: 11
Sample: 215753056547, 215474268483, 215540281168
batch_sync_contact:ids:363:all (Global Group - 456)
Config ID: 363
Type: set
Memory: 548 B
TTL: 23h 57m 2s
Count: 7
Sample: 156053546908, 14789232785, 1569201
batch_sync_deal:ids:455:all (Argos Security - 537)
Config ID: 455
Type: set
Memory: 548 B
TTL: 23h 54m 15s
Count: 7
Sample: 498986026223, 17924102876, 498928162009
batch_sync_deal:ids:461:all (Fieldly - 543)
Config ID: 461
Type: set
Memory: 548 B
TTL: 23h 59m 34s
Count: 7
Sample: 498984577267, 499024879840, 496749330660
batch_sync_company:ids:465:all (Spotler - 545)
Config ID: 465
Type: set
Memory: 484 B
TTL: 23h 53m 55s
Count: 6
Sample: 12886901694, 47800033493, 426487803113
batch_sync_company:ids:483:all (Veremark - 561)
Config ID: 483
Type: set
Memory: 484 B
TTL: 23h 53m 12s
Count: 6
Sample: 54056532238, 25291493956, 52568117585
batch_sync_contact:ids:465:all (Spotler - 545)
Config ID: 465
Type: set
Memory: 484 B
TTL: 23h 53m 55s
Count: 6
Sample: 25784346850, 758403684599, 758356838593
batch_sync_contact:ids:96:all (Nourish Care - 119)
Config ID: 96
Type: set
Memory: 420 B
TTL: 23h 51m 15s
Count: 5
Sample: 759339342027, 6476279789, 759338177732
batch_sync_deal:ids:241:all (PatentRenewal.com ApS - 306)
Config ID: 241
Type: set
Memory: 420 B
TTL: 23h 59m 34s
Count: 5
Sample: 57554678843, 59164863320, 40205117839
batch_sync_company:ids:175:all (Team iAM - 203)
Config ID: 175
Type: set
Memory: 356 B
TTL: 23h 52m 11s
Count: 4
Sample: 31289463313, 53432109235, 47337393078
batch_sync_contact:ids:197:all (Kindly - 264)
Config ID: 197
Type: set
Memory: 356 B
TTL: 23h 56m 33s
Count: 4
Sample: 759337959662, 759340603582, 759336916201
batch_sync_contact:ids:308:all (Foodles - 380)
Config ID: 308
Type: set
Memory: 356 B
TTL: 23h 47m 13s
Count: 4
Sample: 209605481386, 216074957452, 216074957451
batch_sync_contact:ids:461:all (Fieldly - 543)
Config ID: 461
Type: set
Memory: 356 B
TTL: 23h 59m 38s
Count: 4
Sample: 758403694783, 6338351, 755171302642
batch_sync_contact:ids:485:all (LATUS Group - 563)
Config ID: 485
Type: set
Memory: 356 B
TTL: 23h 59m 51s
Count: 4
Sample: 216014615839, 216124673101, 215534275979
batch_sync_company:ids:170:all (LutherOne - 199)
Config ID: 170
Type: set
Memory: 292 B
TTL: 23h 55m 25s
Count: 3
Sample: 5061344699, 8979817578, 53544705830
batch_sync_company:ids:197:all (Kindly - 264)
Config ID: 197
Type: set
Memory: 292 B
TTL: 23h 56m 26s
Count: 3
Sample: 426652716232, 426652949738, 426671486159
batch_sync_company:ids:436:all (Moxso - 519)
Config ID: 436
Type: set
Memory: 292 B
TTL: 23h 54m 5s
Count: 3
Sample: 5637526753, 9027001061, 426485659872
batch_sync_company:ids:455:all (Argos Security - 537)
Config ID: 455
Type: set
Memory: 292 B
TTL: 23h 49m 25s
Count: 3
Sample: 12009203674, 12897632476, 331995461840
batch_sync_contact:ids:130:all (Latana Brand Tracking - 155)
Config ID: 130
Type: set
Memory: 292 B
TTL: 23h 44m 14s
Count: 3
Sample: 216074963815, 216074963816, 216074963817
batch_sync_contact:ids:175:all (Team iAM - 203)
Config ID: 175
Type: set
Memory: 292 B
TTL: 23h 52m 37s
Count: 3
Sample: 212581164888, 216121454962, 216121861967
batch_sync_contact:ids:455:all (Argos Security - 537)
Config ID: 455
Type: set
Memory: 292 B
TTL: 23h 49m 25s
Count: 3
Sample: 15081981630, 28607253707, 589985456367
batch_sync_deal:ids:465:all (Spotler - 545)
Config ID: 465
Type: set
Memory: 292 B
TTL: 23h 54m 43s
Count: 3
Sample: 498984572105, 498986026222, 498987447516
batch_sync_company:ids:70:all (Scoro - 93)
Config ID: 70
Type: set
Memory: 228 B
TTL: 23h 48m 59s
Count: 2
Sample: 308374847674, 426652674241
batch_sync_company:ids:461:all (Fieldly - 543)
Config ID: 461
Type: set
Memory: 228 B
TTL: 23h 57m 6s
Count: 2
Sample: 612188920, 1045770343
batch_sync_contact:ids:112:all (Switchee - 137)
Config ID: 112
Type: set
Memory: 228 B
TTL: 23h 50m 12s
Count: 2
Sample: 216074959999, 216074960000
batch_sync_contact:ids:201:all (THRIVE - 266)
Config ID: 201
Type: set
Memory: 228 B
TTL: 23h 59m 17s
Count: 2
Sample: 17229652, 23550651
batch_sync_contact:ids:253:all (Zymego - 322)
Config ID: 253
Type: set
Memory: 228 B
TTL: 23h 48m 32s
Count: 2
Sample: 9236122316, 108257234125
batch_sync_contact:ids:307:all (Story Terrace Inc - 379)
Config ID: 307
Type: set
Memory: 228 B
TTL: 23h 54m 36s
Count: 2
Sample: 216118037022, 216120038928
batch_sync_contact:ids:319:all (MySalesCoach - 400)
Config ID: 319
Type: set
Memory: 228 B
TTL: 23h 51m 16s
Count: 2
Sample: 191355036878, 759224982717
batch_sync_contact:ids:335:all (Eletive - 429)
Config ID: 335
Type: set
Memory: 228 B
TTL: 23h 58m 13s
Count: 2
Sample: 159795767866, 757397522679
batch_sync_contact:ids:339:all (inspera.no - 436)
Config ID: 339
Type: set
Memory: 228 B
TTL: 23h 55m 54s
Count: 2
Sample: 183336486505, 216074960287
batch_sync_contact:ids:412:all (Antavo - 500)
Config ID: 412
Type: set
Memory: 228 B
TTL: 23h 50m 30s
Count: 2
Sample: 644080299229, 705317319867
batch_sync_deal:ids:96:all (Nourish Care - 119)
Config ID: 96
Type: set
Memory: 228 B
TTL: 23h 51m 21s
Count: 2
Sample: 13955519955, 499101468869
batch_sync_deal:ids:253:all (Zymego - 322)
Config ID: 253
Type: set
Memory: 228 B
TTL: 23h 49m 0s
Count: 2
Sample: 499101779135, 499117828335
batch_sync_company:ids:95:all (Cronofy - 118)
Config ID: 95
Type: set
Memory: 164 B
TTL: 23h 44m 9s
Count: 1
Sample: 54084980481
batch_sync_company:ids:96:all (Nourish Care - 119)
Config ID: 96
Type: set
Memory: 164 B
TTL: 23h 51m 18s
Count: 1
Sample: 231946184895
batch_sync_company:ids:112:all (Switchee - 137)
Config ID: 112
Type: set
Memory: 164 B
TTL: 23h 45m 50s
Count: 1
Sample: 30902131597
batch_sync_company:ids:124:all (Intruder - 149)
Config ID: 124
Type: set
Memory: 164 B
TTL: 23h 49m 59s
Count: 1
Sample: 19200117829
batch_sync_company:ids:130:all (Latana Brand Tracking - 155)
Config ID: 130
Type: set
Memory: 164 B
TTL: 23h 43m 46s
Count: 1
Sample: 3412118684
batch_sync_company:ids:191:all (Orlo - 253)
Config ID: 191
Type: set
Memory: 164 B
TTL: 23h 53m 53s
Count: 1
Sample: 2492067355
batch_sync_company:ids:200:all (Jobadder - 265)
Config ID: 200
Type: set
Memory: 164 B
TTL: 23h 41m 16s
Count: 1
Sample: 54051603071
batch_sync_company:ids:253:all (Zymego - 322)
Config ID: 253
Type: set
Memory: 164 B
TTL: 23h 48m 34s
Count: 1
Sample: 45363246315
batch_sync_company:ids:295:all (APLYiD - 367)
Config ID: 295
Type: set
Memory: 164 B
TTL: 23h 43m 38s
Count: 1
Sample: 426645956821
batch_sync_company:ids:308:all (Foodles - 380)
Config ID: 308
Type: set
Memory: 164 B
TTL: 23h 43m 36s
Count: 1
Sample: 4623764126
batch_sync_company:ids:485:all (LATUS Group - 563)
Config ID: 485
Type: set
Memory: 164 B
TTL: 23h 59m 51s
Count: 1
Sample: 16393088676
batch_sync_contact:ids:81:all (Hurree - 104)
Config ID: 81
Type: set
Memory: 164 B
TTL: 23h 52m 48s
Count: 1
Sample: 216123172492
batch_sync_contact:ids:86:all (Teamtailor - 109)
Config ID: 86
Type: set
Memory: 164 B
TTL: 5h 35m 55s
Count: 1
Sample: 128480851
batch_sync_contact:ids:95:all (Cronofy - 118)
Config ID: 95
Type: set
Memory: 164 B
TTL: 23h 44m 9s
Count: 1
Sample: 216119828808
batch_sync_contact:ids:191:all (Orlo - 253)
Config ID: 191
Type: set
Memory: 164 B
TTL: 23h 43m 42s
Count: 1
Sample: 185805924409
batch_sync_contact:ids:200:all (Jobadder - 265)
Config ID: 200
Type: set
Memory: 164 B
TTL: 23h 41m 16s
Count: 1
Sample: 216122837601
batch_sync_contact:ids:255:all (Screendragon - 324)
Config ID: 255
Type: set
Memory: 164 B
TTL: 23h 44m 22s
Count: 1
Sample: 215982276420
batch_sync_contact:ids:278:all (Akixi - 348)
Config ID: 278
Type: set
Memory: 164 B
TTL: 23h 46m 52s
Count: 1
Sample: 758404365499
batch_sync_contact:ids:284:all (Brickflow (Property Funding Hub Ltd) - 354)
Config ID: 284
Type: set
Memory: 164 B
TTL: 23h 44m 47s
Count: 1
Sample: 195262486661
batch_sync_contact:ids:295:all (APLYiD - 367)
Config ID: 295
Type: set
Memory: 164 B
TTL: 23h 44m 11s
Count: 1
Sample: 759223062753
batch_sync_contact:ids:322:all (Talkative - 403)
Config ID: 322
Type: set
Memory: 164 B
TTL: 23h 56m 55s
Count: 1
Sample: 751262016755
batch_sync_contact:ids:367:all (Sensat - 459)
Config ID: 367
Type: set
Memory: 164 B
TTL: 23h 51m 33s
Count: 1
Sample: 215694537008
batch_sync_contact:ids:403:all (Fundrella - 491)
Config ID: 403
Type: set
Memory: 164 B
TTL: 23h 43m 28s
Count: 1
Sample: 147101
batch_sync_contact:ids:436:all (Moxso - 519)
Config ID: 436
Type: set
Memory: 164 B
TTL: 23h 54m 15s
Count: 1
Sample: 294701
batch_sync_contact:ids:491:all (CreateFuture - 570)
Config ID: 491
Type: set
Memory: 164 B
TTL: 23h 51m 48s
Count: 1
Sample: 214629839743
batch_sync_deal:ids:191:all (Orlo - 253)
Config ID: 191
Type: set
Memory: 164 B
TTL: 23h 54m 11s
Count: 1
Sample: 59230347609
batch_sync_deal:ids:200:all (Jobadder - 265)
Config ID: 200
Type: set
Memory: 164 B
TTL: 23h 50m 5s
Count: 1
Sample: 58878310503
batch_sync_deal:ids:255:all (Screendragon - 324)
Config ID: 255
Type: set
Memory: 164 B
TTL: 23h 44m 20s
Count: 1
Sample: 59243074931
batch_sync_deal:ids:335:all (Eletive - 429)
Config ID: 335
Type: set
Memory: 164 B
TTL: 23h 39m 13s
Count: 1
Sample: 498499653826
batch_sync_deal:ids:370:all (Buynomics - 462)
Config ID: 370
Type: set
Memory: 164 B
TTL: 5h 19m 0s
Count: 1
Sample: 10644871427
batch_sync_deal:ids:373:all (KPSBremen.de - 465)
Config ID: 373
Type: set
Memory: 164 B
TTL: 2h 41m 0s
Count: 1
Sample: 493652735187
batch_sync_deal:ids:483:all (Veremark - 561)
Config ID: 483
Type: set
Memory: 164 B
TTL: 23h 47m 59s
Count: 1
Sample: 59186288665
root@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook redis --key-type ids --detailed -T 459
INFO Scanning Redis keys (type: ids).
Total keys found [PASSWORD_DOTS] 1
Total IDs across all keys [PASSWORD_DOTS] 1
📊 Memory Overview
Total Memory [PASSWORD_DOTS] 164 B
ids keys [PASSWORD_DOTS] 164 B
batch_sync_contact:ids:367:all (Sensat - 459)
Config ID: 367
Type: set
Memory: 164 B
TTL: 23h 51m 6s
Count: 1
Sample: 215694537008
root@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook metrics -T 459
INFO Webhook Metrics — 2026-04-17.
📊 Webhook Metrics Summary
==========================================
Date: 2026-04-17
Filters: Config: 367
Total Teams: 1
Total Webhooks: 7
🏢 Config 367 (Sensat - 459) - 7 webhooks
📦 contact: 6 webhooks
🔔 property_change: 6 events, 5 properties
📦 deal: 1 webhooks
🔔 property_change: 1 events, 1 properties
root@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook metrics -T 459 --from 2026-04-15
INFO Managing webhook metrics for date range.
Date Range [PASSWORD_DOTS] 2026-04-15 to 2026-04-17
Config ID [PASSWORD_DOTS] 367
📊 Range Summary
Date Range [PASSWORD_DOTS] 2026-04-15 to 2026-04-17
Total Days [PASSWORD_DOTS] 3
Oldest Data Age [PASSWORD_DOTS] 2.0 days ago
Total Webhooks [PASSWORD_DOTS] 1,065,651
Daily Average [PASSWORD_DOTS] 355,217.00
Active Companies [PASSWORD_DOTS] 89
🏢 Company Details
Company 367 (Sensat - 459)
Total Webhooks: 796
Days Active: 3/3
Daily Average: 265.33
root@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook metrics -T 459 --from 2026-04-15 -D
INFO Managing webhook metrics for date range.
Date Range [PASSWORD_DOTS] 2026-04-15 to 2026-04-17
Config ID [PASSWORD_DOTS] 367
📊 Range Summary
Date Range [PASSWORD_DOTS] 2026-04-15 to 2026-04-17
Total Days [PASSWORD_DOTS] 3
Oldest Data Age [PASSWORD_DOTS] 2.0 days ago
Total Webhooks [PASSWORD_DOTS] 1,065,677
Daily Average [PASSWORD_DOTS] 355,225.67
Active Companies [PASSWORD_DOTS] 89
📅 Daily Breakdown
2026-04-15: 335,647 webhooks, 88 companies active
2026-04-16: 671,679 webhooks, 88 companies active
2026-04-17: 58,351 webhooks, 68 companies active
🏢 Company Details
Company 367 (Sensat - 459)
Total Webhooks: 796
Days Active: 3/3
Daily Average: 265.33
company (114 total, avg: 38)
association_change: 92 total, avg: 46, active: 2 days
creation: 3 total, avg: 1.5, active: 2 days
property_change: 19 total, avg: 9.5, active: 2 days
Unique properties: 4
Top properties: hubspot_owner_id(12), domain(3), name(3), phone(1)
deal (164 total, avg: 54.67)
property_change: 164 total, avg: 54.67, active: 3 days
Unique properties: 8
Top properties: notes_last_updated(134), closedate(7), dealstage(5), hs_deal_stage_probability(5), hs_manual_forecast_category(5)
contact (518 total, avg: 172.67)
property_change: 390 total, avg: 130, active: 3 days
Unique properties: 9
Top properties: hubspot_owner_id(186), firstname(35), email(35), associatedcompanyid(33), country(33)
creation: 36 total, avg: 18, active: 2 days
association_change: 92 total, avg: 46, active: 2 days
root@67e84f80b9d1:/home/jiminny#
DOCKER
Close Tab
DEV (-zsh)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
-zsh
Close Tab
✳ Review screenpipe usage and Boosteroid integration (claude)
Close Tab
ec2-user@ip-10-30-159-186:~ (nc)
Close Tab
ec2-user@ip-10-20-6-111:~ (nc)
Close Tab
⌥⌘1
ec2-user@ip-10-20-6-111:~...
|
NULL
|
|
43224
|
921
|
19
|
2026-04-17T07:58:46.757809+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776412726757_m2.jpg...
|
Firefox
|
Meet - Backend Chapter — Work
|
1
|
meet.google.com/gjc-ikxu-wxu?authuser=lukas.kovali meet.google.com/gjc-ikxu-wxu?authuser=lukas.kovalik%40jiminny.com...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Close tab
[SRD-6793] Les Mills activity types not pulling in - Jira
Close tab
Problem loading page
Close tab
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Close tab
CloudWatch | us-east-2
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Console Home | Console Home | eu-west-1
Close tab
New Tab
Close tab
Meet - Backend Chapter...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.018945312,"height":-0.045138836},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.018945312,"height":-0.08263886},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.23398438,"top":1.0,"width":0.005859375,"height":-0.08263886},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Problem loading page","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Symfony\\Component\\Debug\\Exception\\FatalThrowableError: 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","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Console Home | Console Home | eu-west-1","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"New Tab","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Meet - Backend Chapter","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true}]...
|
2452931407015322489
|
-706931660339072270
|
app_switch
|
hybrid
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Close tab
[SRD-6793] Les Mills activity types not pulling in - Jira
Close tab
Problem loading page
Close tab
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Close tab
CloudWatch | us-east-2
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Console Home | Console Home | eu-west-1
Close tab
New Tab
Close tab
Meet - Backend Chapter
PhpStormFileFV faVsco.js vProjectvM-INIERNAL WE.HOOK S-1UPEjiminny_storageM+ Icenses.moMIMakerlepackage-lock.ison= phostan.neon.dist= phpstan-baseline.neon<phpunit.xmlTe raw_sql_query.sqlM+ README.mdsonar-project.properties=test.py‹> Untitled Diagram.xmlus vetur.config.jsM+ WEbnOOK HILICKING_IMPLE› ih External Librariesv E° Scratches and Consolesv D Database ConsolesV AEUA console [EU]A DEAL RISKS [EU]A DI [EU]EditViewNavigateCodeLaravelRefactorToolsWindowHelp#11894 on JY-18909-automated-reports-ask-jiminny k ~© AutomatedReportsService.phpC TeamSetupController.phppnp apl.onp© AutomatedReportsCommandTest.php© CreateHeldActivityEvent.phpC ActivityLogged.phpAulomaleakeporscallbackservice.ono© SendReportJob.php© SendReportMailJob.php© ReportController.php© TokenBuilder.php• Filesystem.phpAutomatedReportsCommand.phpAskJiminnykeporscontroller.ono© AutomatedReportsSendCommand.php© Team.php© TrackProviderInstalledEvent.php© CreateActivityLoggedEvent.phpC RequestGenerateAskJiminnyReportJob.php© AutomatedReportsRepository.php© UserPilotActivityListener.phplibd# Backend Chapter • 32 m leftAAutomatedReportsCommandTest-100% CS•8•Fri 17 Apr 10:58:46E custom.log(iii crm configurations [EUl= laravel.logA SF [jiminny@localhost]C" scratch_1.jsonV connect.vueV Onboard.vueconsole PRODL console [STAGING]A HS_local [iminny@localhost]Al console [EU] X15831584© RequestGenerateReportJob.php© SyncOpportunity.phpOpportunitySyncTrait.php x© Service.php© AutomatedReportResult.php1585© AutomatedReport.php1586"podcast_audio_url"Cc Wu resuitsT15871588trait OpportunitySyncTrait1589A32 X2 X19 A V1590private function updateOpportunity(string Scrmid, array Sproperties, array Sassociations): Opportunitycril orowoer o = oorillorE1591=1592=15931594Ix. Aulo vCONCAT(u.id, CASE WHEN U.id = t.owner_id THEN ' (owner)' ELSE "' END) AS user_id,U.emall,sa.*.t.owner_id FROM social_accounts saJOIN users u on u.id = sa.sociable idJOIN teams t 1.n<->1: on t.id = u.team_idWHERE u.team_id = 556 and sa.provider = 'integration-app';Eajiminny v026 49 422 Х 3 X103 ^8071810811812813814815816$values = array_merge($attributes, $data);$opportunity = $this->crmEntityRepository->upsert0pportunity($attributes, $values);$this->importExternalFieldData($properties, $opportunity->getId());$this->update0pportunityAssociations($opportunity, $associations);15961598E1590select * from opportunities where id = 7594349;select * from teams where id = 459;SELECTCONCAT(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 saJOIN users u on u.id = sa.sociable_idJOIN teams tI.ns"l on c.10 = U.ceall 10WHERE U.team_id = 459 and sa.provider = 'hubspot';Services+,o, c|v D DatabaseV AEUc consoe s 400msfị crm_configurations 1 s 504 msv A jiminny@localhost4 SFA HS_localV A PROD4, console 1 s 806 msV A STAGINGA console,, DockerCSVPS3.Nprovider_user_idI provider_user_tokenprovider_refresh_token• expiresIn refresh token expiresproviderI stateI auth_scopeI retry_afterI created_atupdated_atprovider_user_token_encryptedprovider_refresh_token_encryptedI encryption_key (Hex)• sociable_typeowner_idFirefox4318355CLgaitTZMxIZQLNQML8kQEwrAgwACAKUAhI.na1-8238-b045-4568-867a-048eb2318684BAQEDBxirrMOBIJPJhwIo1KwCMhSZIBAwPHxCHFPaK4Jy9e8SsUIFuToyQLNQML8kQEwrA{UACBKGawEFThv1GK702QDDkBrfE8PBSGSgNUYTFSAFoAYABok8mHAnAAeAA1776413740<null>hubspotconnected<null>SIULL7079-04-0179992026-04-17 07:45:40eyJpdiI6IlhBeFNWUW5B0U1zRHFjaWJFMDYxUGc9PSIsInZhbHVlIj0iazB4U2dVUXp0Mkо5cTg50GhHR1E2WmNRUm92YjVPbzhHTk9KRkRucXJSd1pCcXgх0W1pb2ZpRXFWNXc0QWN2b0dtL3BBcjhkN21WK®ZHNDF4NmpsQW9nSHNZWkxU0DUyZmFyc2ZvVDVIM1JoaThGTDBNd®V6V20zd2...eYJpd¡I6IjYyVFM2Ny9xUGw4VWRjTnpGbLBKRkE9PSIsInZhbHVLIjoiT1RDVis1RGVTeWVMK0I0VWZjQSIsIm1hYy[6IjUyZTM5ZmM0YjU5MWQ2ZjQ5NmQ0MWM2M2Q10GRjZGFmZjA2ZjAyMmQzZTQ4MjhkNm.…OxoooSo0oo 5o/ o/or-ollYrolurYoraLLSFEWALEELPAGBAFSGUSZAABLUSAYAOUIВ040 0,А Ва Ат 40 а 0 0 48 0 750 94013user14839O, E,T row retrieved stardSUM: 0 6:1 W Windsurf Teams1596:12uir-o...
|
NULL
|
|
43247
|
920
|
45
|
2026-04-17T07:59:26.067690+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776412766067_m1.jpg...
|
Firefox
|
Meet - Backend Chapter — Work
|
1
|
meet.google.com/gjc-ikxu-wxu?authuser=lukas.kovali meet.google.com/gjc-ikxu-wxu?authuser=lukas.kovalik%40jiminny.com...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Close tab
[SRD-6793] Les Mills activity types not pulling in - Jira
Close tab
Problem loading page
Close tab
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Close tab
CloudWatch | us-east-2
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Console Home | Console Home | eu-west-1
Close tab
New Tab
Close tab
Meet - Backend Chapter
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
Nikolay Nikolov (Presenting, annotating)
Nikolay Nikolov (Presenting, annotating)
People
3
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Zoom in
Open in new window
Enter Full Screen
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Nikolay Nikolov
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Lukas Kovalik
Others might see more of your background. Click to view your full video.
10:59
AM
Backend Chapter
Backend Chapter
Audio settings
Turn off microphone
Video settings
Turn off camera...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"bounds":{"left":0.0,"top":0.072222225,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.13222222,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.13222222,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":4,"bounds":{"left":0.0,"top":0.17777778,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.17777778,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Problem loading page","depth":4,"bounds":{"left":0.0,"top":0.22333333,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.22333333,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Symfony\\Component\\Debug\\Exception\\FatalThrowableError: 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","depth":4,"bounds":{"left":0.0,"top":0.2688889,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.2688889,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0,"top":0.31444445,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.31444445,"width":0.010416667,"height":0.016666668},"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.36,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.36,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Console Home | Console Home | eu-west-1","depth":4,"bounds":{"left":0.0,"top":0.40555555,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.40555555,"width":0.010416667,"height":0.016666668},"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.4511111,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.4511111,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Meet - Backend Chapter","depth":4,"bounds":{"left":0.0,"top":0.49666667,"width":0.033680554,"height":0.045555554},"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.49666667,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.005902778,"top":0.54444444,"width":0.022222223,"height":0.035555556},"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},"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},"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},"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},"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},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Nikolay Nikolov (Presenting, annotating)","depth":12,"bounds":{"left":0.07534722,"top":0.101111114,"width":0.17916666,"height":0.022222223},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Nikolov (Presenting, annotating)","depth":13,"bounds":{"left":0.07534722,"top":0.10222222,"width":0.17916666,"height":0.020555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"People","depth":15,"bounds":{"left":0.88680553,"top":0.08944444,"width":0.04097222,"height":0.04},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"3","depth":22,"bounds":{"left":0.9145833,"top":0.101111114,"width":0.0048611113,"height":0.017222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Take notes with Gemini","depth":14,"bounds":{"left":0.93333334,"top":0.08944444,"width":0.025,"height":0.04},"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":17,"bounds":{"left":0.9361111,"top":0.101111114,"width":0.06388891,"height":0.017222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini","depth":22,"bounds":{"left":0.96666664,"top":0.101111114,"width":0.028125,"height":0.017222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Gemini","depth":21,"bounds":{"left":0.96458334,"top":0.090555556,"width":0.023611112,"height":0.037777778},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"bounds":{"left":0.5798611,"top":0.61,"width":0.14652778,"height":0.08888889},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"bounds":{"left":0.72430557,"top":0.625,"width":0.08090278,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"bounds":{"left":0.7017361,"top":0.6205556,"width":0.11076389,"height":0.05666667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Zoom in","depth":13,"bounds":{"left":0.63090277,"top":0.78333336,"width":0.027777778,"height":0.044444446},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open in new window","depth":13,"bounds":{"left":0.6642361,"top":0.78333336,"width":0.027777778,"height":0.044444446},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Enter Full Screen","depth":13,"bounds":{"left":0.69756943,"top":0.78333336,"width":0.027777778,"height":0.044444446},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"bounds":{"left":0.9201389,"top":0.36666667,"width":0.079861104,"height":0.07722222},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"bounds":{"left":1.0,"top":0.38166666,"width":-0.064236164,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"bounds":{"left":1.0,"top":0.3772222,"width":-0.042013884,"height":0.045},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Nikolov","depth":17,"bounds":{"left":0.75590277,"top":0.485,"width":0.07847222,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"bounds":{"left":1.0,"top":0.75333333,"width":-0.0006943941,"height":0.07722222},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"bounds":{"left":0.92743057,"top":0.7683333,"width":0.07256943,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"bounds":{"left":0.9079861,"top":0.7638889,"width":0.092013896,"height":0.045},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Lukas Kovalik","depth":17,"bounds":{"left":0.75590277,"top":0.87166667,"width":0.06875,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Others might see more of your background. Click to view your full video.","depth":14,"bounds":{"left":0.9614583,"top":0.86722225,"width":0.019444445,"height":0.031111112},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"10:59","depth":12,"bounds":{"left":0.050347224,"top":0.9444444,"width":0.027777778,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AM","depth":12,"bounds":{"left":0.081597224,"top":0.9444444,"width":0.017708333,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Backend Chapter","depth":12,"bounds":{"left":0.11666667,"top":0.9111111,"width":0.090277776,"height":0.08888888},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Backend Chapter","depth":15,"bounds":{"left":0.11666667,"top":0.9444444,"width":0.090277776,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Audio settings","depth":13,"bounds":{"left":0.32118055,"top":0.9288889,"width":0.06111111,"height":0.053333335},"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":13,"bounds":{"left":0.34895834,"top":0.9288889,"width":0.033333335,"height":0.053333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Video settings","depth":13,"bounds":{"left":0.38784721,"top":0.9288889,"width":0.06111111,"height":0.053333335},"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":13,"bounds":{"left":0.415625,"top":0.9288889,"width":0.033333335,"height":0.053333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-6131238486682112467
|
-6453404014619690186
|
app_switch
|
hybrid
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Close tab
[SRD-6793] Les Mills activity types not pulling in - Jira
Close tab
Problem loading page
Close tab
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Close tab
CloudWatch | us-east-2
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Console Home | Console Home | eu-west-1
Close tab
New Tab
Close tab
Meet - Backend Chapter
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
Nikolay Nikolov (Presenting, annotating)
Nikolay Nikolov (Presenting, annotating)
People
3
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Zoom in
Open in new window
Enter Full Screen
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Nikolay Nikolov
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Lukas Kovalik
Others might see more of your background. Click to view your full video.
10:59
AM
Backend Chapter
Backend Chapter
Audio settings
Turn off microphone
Video settings
Turn off camera
iTerm2ShellEditViewSessionScriptsProfilesWindowHelpBackend Chapter • 31 m left100% (47 8• Fri 17 Apr 10:59:25ec2-user@ip-10-20-6-111:~DOCKER• ₴1DEV (-zsh)882APP (-zsh)|X3-zshX4Days Active: 3/3rootesr.y Asobst: 205.3%minnyit phe artisan crm-hubspot-nebhook metrics -T A59 .-From 2026-04-15 - 0root@67e84f80b9d1:/home/jiminny# php artisan crm:hubspot-webhook metrics -T 459 --from 2026-04-15 -D•95* Review screenp...• X6ec2-user@ip-10-30-...$7INFOManaging webhook metrics for date range.Date RangeConfig IDIll Range SummaryDate RangeTotal DaysOldest Data AgeTotal WebhooksDaily AverageActive Companiesiz Daily Breakdown2026-04-15:335,647 webhooks, 88 companies active2026-04-16: 671,679 webhooks, 88 companies active2026-04-17: 58,351 webhooks, 68 companies activeCompany Details2026-04-15 to2026-04-173672026-04-15 to 2026-04-1732.0 days ago1,065,677355,225.6789Company 367 (Sensat - 459)Total Webhooks: 796Days Active: 3/3Daily Average: 265.33company (114 total, avg: 38)association_change: 92 total, avg: 46, active: 2 dayscreation: 3 total, avg: 1.5, active: 2 daysproperty_change: 19 total, avg: 9.5, active: 2 daysUnique properties: 4Top properties: hubspot_owner_id(12), domain(3), name(3), phone(1)deal (164 total, avg: 54.67)property_change: 164 total, avg: 54.67, active: 3 daysUnique properties: 8Top properties: notes_last_updated(134), closedate(7), dealstage(5), hs_deal_stage_probability(5), hs_manual_forecast_category(5)contact (518 total, avg: 172.67)property_change:390 total, avg: 130, active: 3 daysUnique properties: 9Top properties: hubspot_owner_id(186), firstname(35), email(35),associatedcompanyid(33), country(33)creation: 36 total, avg: 18, active: 2 daysassociation_change: 92 total, avg: 46, active: 2 daysroot@67e84f80b9d1:/home/jiminny#1881ec2-user@ip-10-20-...88...
|
NULL
|
|
43248
|
921
|
31
|
2026-04-17T07:59:26.112221+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776412766112_m2.jpg...
|
Firefox
|
Meet - Backend Chapter — Work
|
1
|
meet.google.com/gjc-ikxu-wxu?authuser=lukas.kovali meet.google.com/gjc-ikxu-wxu?authuser=lukas.kovalik%40jiminny.com...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Close tab
[SRD-6793] Les Mills activity types not pulling in - Jira
Close tab
Problem loading page
Close tab
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Close tab
CloudWatch | us-east-2
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Console Home | Console Home | eu-west-1
Close tab
New Tab
Close tab
Meet - Backend Chapter
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
Nikolay Nikolov (Presenting, annotating)
Nikolay Nikolov (Presenting, annotating)
People
3
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Zoom in
Open in new window
Enter Full Screen
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Nikolay Nikolov
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Lukas Kovalik
Others might see more of your background. Click to view your full video.
10:59
AM
Backend Chapter
Backend Chapter
Audio settings
Turn off microphone
Video settings
Turn off camera
Nikolay Nikolov is presenting
Send a reaction
Turn on captions
Raise hand (ctrl + ⌘ + h)
More options
Leave call
Meeting details
Chat with everyone
Meeting tools
The presentation by Nikolay Nikolov was added to the main screen. The presentation by Nikolay Nikolov is on the main screen....
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.018945312,"height":-0.045138836},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.018945312,"height":-0.08263886},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.23398438,"top":1.0,"width":0.005859375,"height":-0.08263886},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Problem loading page","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Symfony\\Component\\Debug\\Exception\\FatalThrowableError: 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","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Console Home | Console Home | eu-west-1","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"New Tab","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Meet - Backend Chapter","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Nikolay Nikolov (Presenting, annotating)","depth":12,"bounds":{"left":0.27558595,"top":1.0,"width":0.10078125,"height":-0.063194394},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Nikolov (Presenting, annotating)","depth":13,"bounds":{"left":0.27558595,"top":1.0,"width":0.10078125,"height":-0.06388891},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"People","depth":15,"bounds":{"left":0.7320312,"top":1.0,"width":0.023046875,"height":-0.05590272},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"3","depth":22,"bounds":{"left":0.7476562,"top":1.0,"width":0.002734375,"height":-0.063194394},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Take notes with Gemini","depth":14,"bounds":{"left":0.75820315,"top":1.0,"width":0.0140625,"height":-0.05590272},"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":17,"bounds":{"left":0.7597656,"top":1.0,"width":0.051171876,"height":-0.063194394},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini","depth":22,"bounds":{"left":0.7769531,"top":1.0,"width":0.015820313,"height":-0.063194394},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Gemini","depth":21,"bounds":{"left":0.7757813,"top":1.0,"width":0.01328125,"height":-0.056597233},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Zoom in","depth":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open in new window","depth":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Enter Full Screen","depth":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Nikolov","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Lukas Kovalik","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Others might see more of your background. Click to view your full video.","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"10:59","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AM","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Backend Chapter","depth":12,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Backend Chapter","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Audio settings","depth":13,"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":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Video settings","depth":13,"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":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Nikolay Nikolov is presenting","depth":12,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Send a reaction","depth":12,"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":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Raise hand (ctrl + ⌘ + h)","depth":12,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"More options","depth":12,"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":12,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Meeting details","depth":12,"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":12,"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":12,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"The presentation by Nikolay Nikolov was added to the main screen. The presentation by Nikolay Nikolov is on the main screen.","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
2453541045214975099
|
-6453394943648760972
|
app_switch
|
hybrid
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Close tab
[SRD-6793] Les Mills activity types not pulling in - Jira
Close tab
Problem loading page
Close tab
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Close tab
CloudWatch | us-east-2
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Console Home | Console Home | eu-west-1
Close tab
New Tab
Close tab
Meet - Backend Chapter
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
Nikolay Nikolov (Presenting, annotating)
Nikolay Nikolov (Presenting, annotating)
People
3
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Zoom in
Open in new window
Enter Full Screen
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Nikolay Nikolov
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Lukas Kovalik
Others might see more of your background. Click to view your full video.
10:59
AM
Backend Chapter
Backend Chapter
Audio settings
Turn off microphone
Video settings
Turn off camera
Nikolay Nikolov is presenting
Send a reaction
Turn on captions
Raise hand (ctrl + ⌘ + h)
More options
Leave call
Meeting details
Chat with everyone
Meeting tools
The presentation by Nikolay Nikolov was added to the main screen. The presentation by Nikolay Nikolov is on the main screen.
PostmanlViewWindowHelolog Hubspot vrour team is now on tne Free pran with traamin, rou retain earung raccess and ouner members are read-oniy. view team permissions to see wno can eait, or upgrade to restore collaporationotl next orselPos. search dealsPosl kead a batch or obePusl kead a batch or as!eGEl KeadEngagementsread call{{baseUrl}} /crm/v3/objects/deal/480171536586= DocsParamsAuthorization•Headers 8BodyScriptsSettingsAuth lyoeTokenbearer lokenThe authorization header will be automatically generated when you send the request.Learn more about Bearer Token authorization.Backend Chapter • 31 m left100% C28 • Fri 17 Apr 10:59:25Q Searchoel keacutl ceucnoacementveraven nuos./apl.nuoaol.cu(a) SaveNo environmentyShare18SendiCookies;AI› Associations› Associations V4CMs - URL Redirects AP Collection› Companies> COMPAREContacts• Cky Odlects› CRM Owners> CRM Pipelines› Dealsv EngagementsOLD ENCAGEMENTSdET list meetingsPosT search moditied comoaniesPOST search tasksGET read call> POST search callsder list callsPOSt meetings scheduledde cel meeunePost get link to task> POST Create Contact with Association› Hubspot> Journal & webhoooks v4OAULN› Properties> RESEARCH> SFARCH$I›lickelsv Useturositiler per comoany oniy ooen deal stacesGET engagements old associated by dealGET engagements old associated by company> Ger get history of property - deal stageGET get usersGer sr oaun› GET Meeting outcomes per meeting› GET Read all properties new> GET Read all properties oldde ole call alsposttionsGET list with associationsGET list engagements oldGET recent engagementsGET get dealser cer cnoacementwoPATCH https://api.hubapi.com/engagements/v1/engagements/249273...Parchmos.aoi.nuoaelcom/crm/vs/odlec.s/meetinos/2402/30973PATCH https://api.hubapi.com/engagements/v1/engagements/249273...ENVIRONMENIS>SPECSELOWSlConnect Git ? Console-lermina.TLOKeIVariables in requestG tokenbaseUrl› All variablesCLOaILI 2MXIZOINGMIOKOEWIAO...https://api.hubapi.comPSNFirefox© Send + Get a successful responseSend + Visualize response*& Send + Write testsVault Tools & 000...
|
NULL
|
|
43252
|
921
|
33
|
2026-04-17T07:59:30.403479+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776412770403_m2.jpg...
|
PhpStorm
|
faVsco.js – console [EU]
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
"podcast_audio_url"
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
0 results
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Code changed:
Hide
Sync Changes
Hide This Notification
32...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.03046875,"top":0.017361112,"width":0.0453125,"height":0.022222223},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"bounds":{"left":0.07578125,"top":0.017361112,"width":0.14960937,"height":0.022222223},"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.78515625,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsCommandTest","depth":6,"bounds":{"left":0.803125,"top":0.017361112,"width":0.09765625,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsCommandTest'","depth":6,"bounds":{"left":0.9007813,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsCommandTest'","depth":6,"bounds":{"left":0.9140625,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9273437,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96015626,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9734375,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9867188,"top":0.017361112,"width":0.013281226,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show Replace Field","depth":4,"bounds":{"left":0.12382813,"top":0.19930555,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Search History","depth":3,"bounds":{"left":0.13867188,"top":0.19861111,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"\"podcast_audio_url\"","depth":4,"bounds":{"left":0.1515625,"top":0.19861111,"width":0.06367187,"height":0.013888889},"value":"\"podcast_audio_url\"","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"bounds":{"left":0.22578125,"top":0.19861111,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Match Case","depth":3,"bounds":{"left":0.2375,"top":0.19861111,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Words","depth":3,"bounds":{"left":0.24765626,"top":0.19861111,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Regex","depth":3,"bounds":{"left":0.2578125,"top":0.19861111,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Replace History","depth":3,"bounds":{"left":0.23320313,"top":1.0,"width":0.00859375,"height":0.0},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Replace","depth":4,"role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"bounds":{"left":0.23320313,"top":1.0,"width":0.00859375,"height":0.0},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Preserve case","depth":3,"bounds":{"left":0.23320313,"top":1.0,"width":0.00859375,"height":0.0},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"0 results","depth":4,"bounds":{"left":0.27382812,"top":0.19791667,"width":0.030078124,"height":0.015277778},"role_description":"text"},{"role":"AXButton","text":"Previous Occurrence","depth":4,"bounds":{"left":0.30390626,"top":0.19722222,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Occurrence","depth":4,"bounds":{"left":0.3140625,"top":0.19722222,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Filter Search Results","depth":4,"bounds":{"left":0.32421875,"top":0.19722222,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open in Window, Multiple Cursors","depth":4,"bounds":{"left":0.334375,"top":0.19722222,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Click to highlight","depth":4,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":4,"bounds":{"left":0.46640626,"top":0.19722222,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.049609374,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"32","depth":4,"bounds":{"left":0.42539063,"top":0.22430556,"width":0.012109375,"height":0.013194445},"role_description":"text"}]...
|
-3103415742087239625
|
-8250237716689468602
|
app_switch
|
hybrid
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
"podcast_audio_url"
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
0 results
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Code changed:
Hide
Sync Changes
Hide This Notification
32
PostmanlViewWindowHeloog Hubspot vrour team is now on tne Free pran with traamin, rou retain earung raccess and ouner members are read-oniy. view team permissions to see wno can eait, or upgrade to restore collaporationV COLLECTIONS› Associations› Associations V4CMs - URL Redirects AP Collection› Companies> COMPAREContacts• Cky Odlects› CRM Owners> CRM Pipelines› Dealsv EngagementsOLD ENCAGEMENTSdET list meetingsPosT search moditied comoaniesPOSt search tasksGET read call> POST search callsder list callsPOSt meetings scheduledde eel meeunePost get link to task> POST Create Contact with Association› Hubspot> Journal & webhoooks v4OAULN› Properties> RESEARCH> SFARCH›lickelsv Useturusitiler per comoany only ooen deal stacesGET engagements old associated by dealGET engagements old associated by company> Ger get history of property - deal stageGET get usersGer sr oaun› GET Meeting outcomes per meeting› GET Read all properties new> GET Read all properties oldotl next orselPos. search dealsPosl kead a batch or obePusl kead a bateh or as!eEngagementsread call{{baseUrl}} /crm/v3/objects/deal/480171536586= DocsParamsAuthorization•Headers 8BodyScriptsSettingsAuth lyoeTokenbearer lokenThe authorization header will be automatically generated when you send the request.Learn more about Bearer Token authorization.$1de ole call alsposttionsGET list with associationsGET list engagements oldGET recent engagementsGET get dealser cer cnoadementwoPATCH https://api.hubapi.com/engagements/v1/engagements/249273...Parchmos.aoi.nuoaelcom/crm/vs/odlec.s/meetinos/2402/30973PATCH https://api.hubapi.com/engagements/v1/engagements/249273...ENVIRONMENIS>SPECSELOWSE Connect Git E Console-lermina.PSPhpStormQ SearchGEl Keadoel keacTLOKeIutl ceucnoacementveraven nuos./apl.nuoaol.cu(a) SaveBackend Chapter • 31 m left100% C28 • Fri 17 Apr 10:59:29No environmenty;AIShare18Variables in requestSendiG tokenCLOaILI 2MXIZOINGMIOKOEWIAO...G baseUrlhttps://api.hubapi.comCookies› All variablesN© Send + Get a successful responseSend + Visualize response*& Send + Write testsVault Tools 000...
|
NULL
|
|
43259
|
920
|
51
|
2026-04-17T07:59:40.353690+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776412780353_m1.jpg...
|
Firefox
|
Meet - Backend Chapter — Work
|
1
|
meet.google.com/gjc-ikxu-wxu?authuser=lukas.kovali meet.google.com/gjc-ikxu-wxu?authuser=lukas.kovalik%40jiminny.com...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Close tab
[SRD-6793] Les Mills activity types not pulling in - Jira
Close tab
Problem loading page
Close tab
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Close tab
CloudWatch | us-east-2
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Console Home | Console Home | eu-west-1
Close tab
New Tab
Close tab
Meet - Backend Chapter
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
Nikolay Nikolov (Presenting, annotating)
Nikolay Nikolov (Presenting, annotating)
People
3
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Unpin Nikolay Nikolov's presentation from your main screen
You can't unmute someone else's presentation
More options for Nikolay Nikolov
Zoom in
Open in new window
Enter Full Screen
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Pin Nikolay Nikolov to your main screen
Mute Nikolay Nikolov's microphone
More options for Nikolay Nikolov
Nikolay Nikolov
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
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:59
AM
Backend Chapter
Backend Chapter
Audio settings
Turn off microphone
Video settings
Turn off camera
Nikolay Nikolov is presenting...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"bounds":{"left":0.0,"top":0.072222225,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.13222222,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.13222222,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":4,"bounds":{"left":0.0,"top":0.17777778,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.17777778,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Problem loading page","depth":4,"bounds":{"left":0.0,"top":0.22333333,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.22333333,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Symfony\\Component\\Debug\\Exception\\FatalThrowableError: 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","depth":4,"bounds":{"left":0.0,"top":0.2688889,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.2688889,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0,"top":0.31444445,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.31444445,"width":0.010416667,"height":0.016666668},"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.36,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.36,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Console Home | Console Home | eu-west-1","depth":4,"bounds":{"left":0.0,"top":0.40555555,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.40555555,"width":0.010416667,"height":0.016666668},"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.4511111,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.4511111,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Meet - Backend Chapter","depth":4,"bounds":{"left":0.0,"top":0.49666667,"width":0.033680554,"height":0.045555554},"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.49666667,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.005902778,"top":0.54444444,"width":0.022222223,"height":0.035555556},"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},"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},"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},"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},"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},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Nikolay Nikolov (Presenting, annotating)","depth":12,"bounds":{"left":0.07534722,"top":0.101111114,"width":0.17916666,"height":0.022222223},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Nikolov (Presenting, annotating)","depth":13,"bounds":{"left":0.07534722,"top":0.10222222,"width":0.17916666,"height":0.020555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"People","depth":15,"bounds":{"left":0.88680553,"top":0.08944444,"width":0.04097222,"height":0.04},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"3","depth":22,"bounds":{"left":0.9145833,"top":0.101111114,"width":0.0048611113,"height":0.017222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Take notes with Gemini","depth":14,"bounds":{"left":0.93333334,"top":0.08944444,"width":0.025,"height":0.04},"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":17,"bounds":{"left":0.9361111,"top":0.101111114,"width":0.06388891,"height":0.017222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini","depth":22,"bounds":{"left":0.96666664,"top":0.101111114,"width":0.028125,"height":0.017222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Gemini","depth":21,"bounds":{"left":0.96458334,"top":0.090555556,"width":0.023611112,"height":0.037777778},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"bounds":{"left":0.5798611,"top":0.61,"width":0.14652778,"height":0.08888889},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"bounds":{"left":0.72430557,"top":0.625,"width":0.08090278,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"bounds":{"left":0.7017361,"top":0.6205556,"width":0.11076389,"height":0.05666667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Unpin Nikolay Nikolov's presentation from your main screen","depth":13,"bounds":{"left":0.34618056,"top":0.5088889,"width":0.027777778,"height":0.044444446},"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":13,"bounds":{"left":0.37395832,"top":0.50666666,"width":0.030555556,"height":0.04888889},"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":13,"bounds":{"left":0.4045139,"top":0.5088889,"width":0.027777778,"height":0.044444446},"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":13,"bounds":{"left":0.63090277,"top":0.78333336,"width":0.027777778,"height":0.044444446},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open in new window","depth":13,"bounds":{"left":0.6642361,"top":0.78333336,"width":0.027777778,"height":0.044444446},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Enter Full Screen","depth":13,"bounds":{"left":0.69756943,"top":0.78333336,"width":0.027777778,"height":0.044444446},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"bounds":{"left":0.9201389,"top":0.36666667,"width":0.079861104,"height":0.07722222},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"bounds":{"left":1.0,"top":0.38166666,"width":-0.064236164,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"bounds":{"left":1.0,"top":0.3772222,"width":-0.042013884,"height":0.045},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Pin Nikolay Nikolov to your main screen","depth":13,"bounds":{"left":0.82395834,"top":0.31555554,"width":0.027777778,"height":0.044444446},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Mute Nikolay Nikolov's microphone","depth":13,"bounds":{"left":0.8517361,"top":0.31333333,"width":0.030555556,"height":0.04888889},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"More options for Nikolay Nikolov","depth":13,"bounds":{"left":0.8822917,"top":0.31555554,"width":0.027777778,"height":0.044444446},"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":17,"bounds":{"left":0.75590277,"top":0.485,"width":0.07847222,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"bounds":{"left":1.0,"top":0.75333333,"width":-0.0006943941,"height":0.07722222},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"bounds":{"left":0.92743057,"top":0.7683333,"width":0.07256943,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"bounds":{"left":0.9079861,"top":0.7638889,"width":0.092013896,"height":0.045},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"You’re continuously framed","depth":13,"bounds":{"left":0.82256943,"top":0.7,"width":0.030555556,"height":0.04888889},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Backgrounds and effects","depth":13,"bounds":{"left":0.853125,"top":0.7,"width":0.030555556,"height":0.04888889},"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":13,"bounds":{"left":0.8836806,"top":0.7022222,"width":0.027777778,"height":0.044444446},"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":17,"bounds":{"left":0.75590277,"top":0.87166667,"width":0.06875,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Others might see more of your background. Click to view your full video.","depth":14,"bounds":{"left":0.9614583,"top":0.86722225,"width":0.019444445,"height":0.031111112},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"10:59","depth":12,"bounds":{"left":0.050347224,"top":0.9444444,"width":0.027777778,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AM","depth":12,"bounds":{"left":0.081597224,"top":0.9444444,"width":0.017708333,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Backend Chapter","depth":12,"bounds":{"left":0.11666667,"top":0.9111111,"width":0.090277776,"height":0.08888888},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Backend Chapter","depth":15,"bounds":{"left":0.11666667,"top":0.9444444,"width":0.090277776,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Audio settings","depth":13,"bounds":{"left":0.32118055,"top":0.9288889,"width":0.06111111,"height":0.053333335},"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":13,"bounds":{"left":0.34895834,"top":0.9288889,"width":0.033333335,"height":0.053333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Video settings","depth":13,"bounds":{"left":0.38784721,"top":0.9288889,"width":0.06111111,"height":0.053333335},"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":13,"bounds":{"left":0.415625,"top":0.9288889,"width":0.033333335,"height":0.053333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Nikolay Nikolov is presenting","depth":12,"bounds":{"left":0.45451388,"top":0.9288889,"width":0.03888889,"height":0.053333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-4360578129290982292
|
-6453401884315911376
|
app_switch
|
hybrid
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Close tab
[SRD-6793] Les Mills activity types not pulling in - Jira
Close tab
Problem loading page
Close tab
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Close tab
CloudWatch | us-east-2
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Console Home | Console Home | eu-west-1
Close tab
New Tab
Close tab
Meet - Backend Chapter
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
Nikolay Nikolov (Presenting, annotating)
Nikolay Nikolov (Presenting, annotating)
People
3
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Unpin Nikolay Nikolov's presentation from your main screen
You can't unmute someone else's presentation
More options for Nikolay Nikolov
Zoom in
Open in new window
Enter Full Screen
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Pin Nikolay Nikolov to your main screen
Mute Nikolay Nikolov's microphone
More options for Nikolay Nikolov
Nikolay Nikolov
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
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:59
AM
Backend Chapter
Backend Chapter
Audio settings
Turn off microphone
Video settings
Turn off camera
Nikolay Nikolov is presenting
FirefoxFileEditViewHistoryBookmarksProfiles→ToolsWindowHelp> 0.ladlBackend Chapter • 31m leftmeet.google.com/gjc-ikxu-wxu?authuser=lukas.kovalik%40jiminny.comNikolay Nikolov (Presenting, annotating)100% 1978 • Fri 17 Apr 10:59:39=3B+ьcalwowohttps://github.com/jiminny/app/commit/6b85272ad481136889d0701ba49138935f93520bTO0ODebug crm-sync...E Workers |Datadog C RefineSentryjiminny /app 8‹> Code17 Pull requests6. AgentsActions0 WikiSecurity and qualitye Insights@ SettingsCommit 6b85272LakyLak committed 3 weeks agoJY-19666 add casting on objectP naster (#11796)Q Filter files...• fй app/Services/Crm/Hubspot/...B OpportunitySyncTrait.phpFri 17 Apr 10:50L Al BookmarxsLa Learnl Al Chapter home@ Review( Rev Prophet|Q Type to search+ -1 file changed +1-1 lines changedv app/Services/Cra/Hubspot/ServiceTraits/OpportunitySyncTrait.php ÷00 -454,7 +454,7 00 private function batchSyncCrnobjects(string SobjectType, array Scraids): array4>41Sthis->client->getContactsByIds(Schunk, Sthis-456459>getContactFields()):foreach (sobjects as Sobjectid « SobjectData) ‹Sthis->inportCrn0bject(SobjectType, Sobjectid, SobjectData,Ssyncobjects):sthis->logger->info('[' • Sthis->getDisplayhane() • '] Batchsynced. SobjectType,lComments o455456457459•D Browse files1 parent daefb8ß connit 6685272Q Search within codestnss-scisent gercontactsoyzostschunk, Schas">getContactFields()):foreach (Sobjects as sobjectid « SobjectData) ‹|sthis-simportCrnobject(SobjectType, |(string) sobjectid,sobjectbata, Ssync0bjects);sthis->logper->info('l' • Sthis->getDisplayNane() • '] Batchsynced Sobjecttype,& Lock conversationNikolay Nikolovmeet.google.com is sharing your screen.Lukas Kovalik10:59 AM | Backend Chapter...
|
NULL
|
|
43269
|
921
|
41
|
2026-04-17T07:59:59.669413+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776412799669_m2.jpg...
|
PhpStorm
|
faVsco.js – console [EU]
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
"podcast_audio_url"
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
0 results
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Code changed:
Hide
Sync Changes
Hide This Notification
32
2
19
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use HubSpot\Client\Crm\Deals\Model\CollectionResponseAssociatedId;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Models\Account;
use Exception;
use Jiminny\Component\DealInsights\Forecast\Forecast;
use Jiminny\Jobs\Crm\MatchActivitiesToNewOpportunity;
use Jiminny\Models\Contact;
use Jiminny\Models\Crm\BusinessProcess;
use Jiminny\Exceptions\CrmException;
use Jiminny\Models\Opportunity;
use Illuminate\Support\Collection;
use Jiminny\Models\Stage;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Crm\Hubspot\DealFieldsService;
use Jiminny\Services\Crm\Hubspot\OpportunitySyncStrategy\HubspotSingleSyncStrategy;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Services\Crm\OpportunitySyncStrategyResolver;
use Jiminny\Utils\CurrencyFormatter;
/**
* Optimized sync methods for better performance
* These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains
*/
trait OpportunitySyncTrait
{
private const int BATCH_SIZE = 100;
private const int BATCH_PROCESS_SIZE = 800;
protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;
protected CrmEntityRepository $crmEntityRepository;
protected DealFieldsService $dealFieldsService;
private ?array $cachedClosedDealStages = null;
private array $cachedBusinessProcesses = [];
private array $cachedStages = [];
public function syncOpportunities(array $parameters, ?string $strategy = null): int
{
$strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);
$parameters['config'] = $this->config;
$syncCount = 0;
$reportedTotal = 0;
$lastSyncedId = [];
try {
foreach ($strategies as $strategyName => $syncStrategy) {
$this->logger->info(
'[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .
$strategyName
);
$total = 0;
$lastId = null;
$buffer = [];
// HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies
foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {
$buffer[] = $hsOpportunity;
// process every 800 rows (fits < 1 000 association limit)
if (\count($buffer) >= self::BATCH_PROCESS_SIZE) {
$syncCount += $this->processOpportunityBatch($buffer);
$buffer = [];
}
}
// leftovers
if ($buffer) {
$syncCount += $this->processOpportunityBatch($buffer);
}
$reportedTotal += $total;
$lastSyncedId = $lastId;
}
} catch (\HubSpot\Client\Crm\Deals\ApiException | CrmException $e) {
$this->handleSyncException($e, $parameters);
}
$this->logger->info(
'[HubSpot] Synced opportunities',
[
'team' => $this->team->getId(),
'sync_count' => $syncCount,
'total' => $reportedTotal,
'last_synced_id' => $lastSyncedId,
]
);
return $reportedTotal;
}
private function handleSyncException(\Throwable $e, array $parameters): void
{
if (($parameters['since'] ?? null) instanceof Carbon) {
$parameters['since'] = $parameters['since']->toDateTimeString();
}
$parameters['config'] = $this->config->getId();
$this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [
'teamId' => $this->team->getUuid(),
'parameters' => $parameters,
'reason' => $e->getMessage(),
]);
}
/**
* @inheritdoc
*/
public function syncOpportunity(string $crmId): ?Opportunity
{
$strategy = $this->opportunitySyncStrategyResolver->resolve(
$this->config,
OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,
);
$parameters = [
'config' => $this->config,
'crm_id' => $crmId,
];
try {
if (! $strategy instanceof HubspotSingleSyncStrategy) {
throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');
}
$hsOpportunity = $strategy->fetchOpportunity($parameters);
} catch (\HubSpot\Client\Crm\Deals\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
$hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);
return $this->importOrUpdateOpportunity($hsOpportunity);
}
/**
* Process webhook-collected opportunity batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportOpportunityBatch jobs for batch processing.
*
* @return int Number of opportunity IDs dispatched to jobs
*/
public function batchSyncOpportunities(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,
$configId
);
}
/**
* Import a batch of opportunities by their CRM IDs.
* Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().
*
* @param array<string> $crmIds HubSpot deal CRM IDs
*
* @return array{success: array, failed_ids: array, errors?: array<string, string>}
*/
public function importOpportunityBatchByIds(array $crmIds): array
{
$fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);
$allDeals = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
$deals = $this->client->getOpportunitiesByIds($chunk, $fields);
foreach ($deals as $deal) {
$allDeals[] = $deal;
}
}
// IDs not returned by HubSpot are likely deleted or inaccessible deals.
// These are not failures — retrying won't bring them back.
$fetchedIds = array_map('strval', array_column($allDeals, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allDeals),
]);
}
if (empty($allDeals)) {
return ['success' => [], 'failed_ids' => []];
}
return $this->importOpportunityBatch($allDeals);
}
private function getClosedDealStages(): array
{
if ($this->cachedClosedDealStages !== null) {
return $this->cachedClosedDealStages;
}
$stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);
$data = [
'lost' => [],
'won' => [],
];
foreach ($stages as $stage) {
if ($stage->probability == 0.00) {
$data['lost'][] = $stage->crm_provider_id;
}
if ($stage->probability == 100.00) {
$data['won'][] = $stage->crm_provider_id;
}
}
$this->cachedClosedDealStages = $data;
return $data;
}
/**
* Import deals into the database with pre-fetched associations.
*
* API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT
* caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()
* where Laravel retries the whole job with backoff. After all retries exhausted,
* failed() requeues all IDs to Redis.
*
* The per-deal loop catches exceptions individually. A deal can end up in three states:
* - success: imported/updated successfully
* - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)
* These are permanent issues — retrying won't fix them.
* - skipped (null): missing dependencies (no account, unknown pipeline/stage).
* This is acceptable — the deal cannot be imported until those exist.
*/
private function importOpportunityBatch(array $deals): array
{
$syncedOpportunities = [
'success' => [],
'failed_ids' => [],
];
$dealIds = array_column($deals, 'id');
// Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the
// queue job retries the whole batch and eventually requeues all deal IDs back to Redis.
try {
$companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');
$contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');
$associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);
$existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(
$this->config,
array_map('strval', $dealIds)
);
$existingCrmIdSet = array_flip($existingCrmIds);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [
'teamId' => $this->team->getId(),
'dealCount' => count($dealIds),
'error' => $e->getMessage(),
]);
throw $e;
}
foreach ($deals as $deal) {
try {
$deal['associations'] = $this->prepareAssociationsForOpportunity(
$deal['id'],
$companyAssociations,
$contactAssociations,
$associationsData
);
$syncedOpportunity = $this->importOrUpdateOpportunity(
$deal,
isset($existingCrmIdSet[(string) $deal['id']])
);
if ($syncedOpportunity) {
$syncedOpportunities['success'][] = $syncedOpportunity;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [
'teamId' => $this->team->getId(),
'crmId' => $deal['id'],
'error' => $e->getMessage(),
]);
$syncedOpportunities['failed_ids'][] = $deal['id'];
$syncedOpportunities['errors'][$deal['id']] = $e->getMessage();
}
}
return $syncedOpportunities;
}
/**
* Prepare associated entities for opportunities with optimized batch processing
* Returns structured data with CRM ID to DB ID mappings for each opportunity
*/
private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array
{
// Step 1: Collect all unique company and contact IDs from associations
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
// Step 2: Batch sync missing entities and get CRM ID to DB ID mappings
$companyIdMappings = [];
$contactIdMappings = [];
if (! empty($allCompanyIds)) {
$companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);
}
if (! empty($allContactIds)) {
$contactIdMappings = $this->prepareAssociatedContacts($allContactIds);
}
return [
'company_id_mappings' => $companyIdMappings,
'contact_id_mappings' => $contactIdMappings,
];
}
/**
* Flatten association data to get unique IDs
*/
private function flattenAssociationIds(array $associations): array
{
$ids = [];
foreach ($associations as $dealAssociations) {
if (is_array($dealAssociations)) {
foreach ($dealAssociations as $id) {
$ids[$id] = true;
}
}
}
return array_keys($ids);
}
/**
* Batch sync missing accounts
*/
private function prepareAssociatedAccounts(array $companyIds): array
{
// Find which accounts already exist
$existingAccounts = $this->crmEntityRepository
->findAccountsByExternalIds($this->config, $companyIds);
$existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();
$existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {
return [$account->getCrmProviderId() => $account->getId()];
})->toArray();
$missingCompanyIds = array_diff($companyIds, $existingCompanyIds);
if (empty($missingCompanyIds)) {
return $existingAccountsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [
'teamId' => $this->team->getUuid(),
'total_companies' => count($companyIds),
'existing_companies' => count($existingCompanyIds),
'missing_companies' => count($missingCompanyIds),
]);
// we already have limit on opportunity ids count
// Initialize variable before try block
$syncedAccountsData = [];
try {
$syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [
'size' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
$syncedAccountsData = [];
}
return $existingAccountsData + $syncedAccountsData;
}
/**
* Prepare associated contacts - find existing and sync missing ones
* Returns mapping of CRM ID to DB ID
*/
private function prepareAssociatedContacts(array $contactIds): array
{
// Find which contacts already exist
$existingContacts = $this->crmEntityRepository
->findContactsByExternalIds($this->config, $contactIds);
$existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();
// Create mapping for existing contacts
$existingContactsData = $existingContacts->mapWithKeys(function ($contact) {
return [$contact->getCrmProviderId() => $contact->getId()];
})->toArray();
$missingContactIds = array_diff($contactIds, $existingContactIds);
if (empty($missingContactIds)) {
return $existingContactsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [
'teamId' => $this->team->getUuid(),
'total_contacts' => count($contactIds),
'existing_contacts' => count($existingContactIds),
'missing_contacts' => count($missingContactIds),
]);
// Sync missing contacts using batch API
try {
$syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [
'size' => count($missingContactIds),
'error' => $e->getMessage(),
]);
$syncedContactsData = [];
}
return $existingContactsData + $syncedContactsData;
}
private function batchSyncCrmObjects(string $objectType, array $crmIds): array
{
$syncObjects = [];
$crmObjectIds = array_values($crmIds);
foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {
try {
$objects = $objectType === 'companies' ?
$this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :
$this->client->getContactsByIds($chunk, $this->getContactFields());
foreach ($objects as $objectId => $objectData) {
$this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [
'requested_count' => count($chunk),
'synced_count' => count($objects),
]);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [
'ids' => $chunk,
'error' => $e->getMessage(),
]);
}
}
return $syncObjects;
}
private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void
{
try {
$object = $objectType === 'companies' ?
$this->importAccount($objectData) :
$this->importContact($objectData);
if ($object) {
$syncObjects[$object->getCrmProviderId()] = $object->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [
'id' => $objectId,
'error' => $e->getMessage(),
]);
}
}
/**
* Prepare associations for a single opportunity
*
* The return value is an array with the following structure:
* [
* 'companies' => [
* $companyCrmId => $companyId,
* ...
* ],
* 'contacts' => [
* $contactCrmId => $contactId,
* ...
* ],
* 'account_id' => $accountId,
* ]
*/
private function prepareAssociationsForOpportunity(
string $oppCrmId,
array $companyAssociations,
array $contactAssociations,
array $associationsData
): array {
$associations = [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
$oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];
foreach ($oppCompanyIds as $companyCrmId) {
if (isset($associationsData['company_id_mappings'][$companyCrmId])) {
$associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];
// Set primary account (first company becomes primary account)
if ($associations['account_id'] === null) {
$associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];
}
}
}
$oppContactIds = $contactAssociations[$oppCrmId] ?? [];
foreach ($oppContactIds as $contactCrmId) {
if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {
$associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];
}
}
return $associations;
}
/**
* Update only associations for an opportunity
*/
private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void
{
// Update contact associations
$this->importOpportunityContacts($opportunity, $associations['contacts']);
// Update company (account) associations
$this->updateOpportunityAccount($opportunity, $associations['account_id']);
}
/**
* Remove all contact associations from an opportunity
*/
private function removeAllOpportunityContacts(Opportunity $opportunity): void
{
$currentCount = (int) $opportunity->contacts()->count();
if ($currentCount > 0) {
$opportunity->contacts()->detach();
$this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_count' => $currentCount,
]);
}
}
private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void
{
if ($accountId === null) {
// No account ID provided - keep current account
return;
}
$currentAccountId = $opportunity->getAccountId();
// Only update if account has changed
if ($currentAccountId !== $accountId) {
$opportunity->account_id = $accountId;
$opportunity->save();
$this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [
'opportunity_id' => $opportunity->getId(),
'old_account_id' => $currentAccountId,
'new_account_id' => $accountId,
]);
}
}
/**
* Find existing opportunities by external IDs (OPTIMIZED VERSION)
* Uses batch query for better performance
*/
private function findExistingOpportunities(array $crmIds): Collection
{
return $this->crmEntityRepository
->findOpportunitiesByExternalIds($this->config, $crmIds);
}
private function processOpportunityBatch(array $opportunities): int
{
$syncedOpportunities = $this->importOpportunityBatch($opportunities);
return count($syncedOpportunities['success'] ?? []);
}
/**
* Convert single deal associations from HubSpot format to internal format
* Handles both HubSpot SDK objects and array formats
*
* @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed
*
* @return array Processed associations with DB IDs
*/
private function convertDealAssociations(array $opportunityAssociations): array
{
$associations = $this->initializeAssociationsStructure();
if (empty($opportunityAssociations)) {
return $associations;
}
$associationIds = $this->extractAssociationIds($opportunityAssociations);
$this->processCompanyAssociations($associationIds, $associations);
$this->processContactAssociations($associationIds, $associations);
return $associations;
}
private function initializeAssociationsStructure(): array
{
return [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
}
private function extractAssociationIds(array $opportunityAssociations): array
{
$associationIds = [];
foreach ($opportunityAssociations as $type => $associationData) {
if (! empty($associationData)) {
$associationIds[$type] = $this->convertSingleDealAssociations($associationData);
}
}
return $associationIds;
}
private function processCompanyAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['companies'])) {
return;
}
$companyId = $associationIds['companies'][0];
$account = $this->findOrSyncAccount($companyId);
if ($account instanceof Account) {
$associations['companies'][$companyId] = $account->getId();
$associations['account_id'] = $account->getId();
}
}
private function processContactAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['contacts'])) {
return;
}
foreach ($associationIds['contacts'] as $contactId) {
$contact = $this->findOrSyncContact($contactId);
if ($contact instanceof Contact) {
$associations['contacts'][$contactId] = $contact->getId();
}
}
}
private function findOrSyncAccount(string $companyId): ?Account
{
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);
if (! $account instanceof Account) {
$account = $this->syncAccount($companyId);
}
return $account;
}
private function findOrSyncContact(string $contactId): ?Contact
{
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);
if (! $contact instanceof Contact) {
$contact = $this->syncContact($contactId);
}
return $contact;
}
private function convertSingleDealAssociations($opportunityAssociations = null): array
{
$associationData = [];
if ($opportunityAssociations === null) {
return $associationData;
}
// Handle array input (from extractAssociationIds)
if (is_array($opportunityAssociations)) {
return $opportunityAssociations;
}
// Handle CollectionResponseAssociatedId object
if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {
foreach ($opportunityAssociations->getResults() as $association) {
$associationData[] = $association->getId();
}
}
return $associationData;
}
private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity
{
if (empty($crmData['properties'])) {
return null;
}
$crmId = (string) $crmData['id'];
$properties = $crmData['properties'];
$associations = $crmData['associations'] ?? [];
$opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(
$this->config,
$crmId
);
if ($opportunityExists) {
return $this->updateOpportunity($crmId, $properties, $associations);
} else {
return $this->createOpportunity($crmId, $properties, $associations);
}
}
/**
* Create new opportunity
*/
private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity
{
$accountId = $this->resolveAccountId($associations);
if (! $accountId) {
return null;
}
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
if (! $businessProcess) {
return null;
}
$stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);
if (! $stage) {
return null;
}
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->importOpportunityContacts($opportunity, $associations['contacts']);
if ($opportunity->wasRecentlyCreated) {
MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());
}
return $opportunity;
}
/**
* Update existing opportunity
*/
private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity
{
$accountId = $this->resolveAccountId($associations);
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
$stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->updateOpportunityAssociations($opportunity, $associations);
return $opportunity;
}
private function resolveAccountId(array $associations): ?int
{
if (! empty($associations['accountId'])) {
return $associations['accountId'];
}
if (empty($associations)) {
return null;
}
// we can't resolve multiple account ids (currently SDK returns one company)
foreach ($associations['companies'] as $accountId) {
return $accountId;
}
return null;
}
private function buildOpportunityData(
array $properties,
?int $accountId,
?BusinessProcess $businessProcess,
?Stage $stage
): array {
$ownerId = null;
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$name = 'Unknown';
if (isset($properties['dealname'])) {
$name = mb_strimwidth($properties['dealname'], 0, 128);
}
$amount = $this->resolveAmount($properties);
$currency = $properties['deal_currency_code'] ?? null;
$closeDate = null;
if (! empty($properties['closedate'])) {
$closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');
}
$remotelyCreatedAt = null;
if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {
$date = $this->parseCleanDatetime($properties['createdate']);
$remotelyCreatedAt = $date?->format('Y-m-d H:i:s');
}
$closedStages = $this->getClosedDealStages();
$isWon = in_array($properties['dealstage'], $closedStages['won']);
$isLost = in_array($properties['dealstage'], $closedStages['lost']);
$data = [
'team_id' => $this->team->getId(),
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => $name,
'value' => ! empty($amount) ? $amount : null,
'currency_code' => CurrencyFormatter::formatCode($currency),
'close_date' => $closeDate,
'is_closed' => $isWon || $isLost,
'is_won' => $isWon,
'remotely_created_at' => $remotelyCreatedAt,
'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),
'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),
];
if ($accountId) {
$data['account_id'] = $accountId;
}
if ($stage) {
$data['stage_id'] = $stage->id;
}
if ($businessProcess) {
$recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);
if ($recordType) {
$data['record_type_id'] = $recordType->id;
}
}
return $data;
}
private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess
{
if ($pipelineId === null) {
return null;
}
if (isset($this->cachedBusinessProcesses[$pipelineId])) {
return $this->cachedBusinessProcesses[$pipelineId];
}
$businessProcess = $this->getBusinessProcess($pipelineId);
if (! $businessProcess instanceof BusinessProcess) {
$this->importStages();
$businessProcess = $this->getBusinessProcess($pipelineId);
}
if (! $businessProcess instanceof BusinessProcess) {
$this->logger->info(
'[HubSpot] Deal is not attached to a pipeline',
[
'pipeline' => $pipelineId]
);
}
$this->cachedBusinessProcesses[$pipelineId] = $businessProcess;
return $businessProcess;
}
private function getBusinessProcess(string $pipelineId): ?BusinessProcess
{
return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);
}
private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage
{
if (empty($stageId)) {
return null;
}
$cacheKey = $businessProcess->getId() . ':' . $stageId;
if (isset($this->cachedStages[$cacheKey])) {
return $this->cachedStages[$cacheKey];
}
$stage = $this->crmEntityRepository->getPipelineStageByConditions(
$businessProcess,
[
'crm_provider_id' => $stageId,
'type' => Stage::TYPE_OPPORTUNITY,
]
);
if ($stage === null) {
$this->importStages(null, $stageId);
}
if ($stage === null) {
$this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);
}
$this->cachedStages[$cacheKey] = $stage;
return $stage;
}
private function resolveAmount(array $properties): ?string
{
$amount = null;
if (! empty($properties['amount'])) {
$amount = str_replace(',', '', $properties['amount']);
}
if ($this->config->hasDefaultCurrencyFieldSet()) {
$valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();
$amount = $properties[$valueFieldName] ?? $amount;
}
return $amount;
}
private function parseCleanDatetime(string $datetime): ?Carbon
{
// Treat pre-1980 values as invalid
$minValidDate = Carbon::parse('1980-01-01 00:00:00');
try {
$date = Carbon::parse($datetime);
if ($minValidDate->gt($date)) {
return null;
}
return $date;
} catch (Exception) {
return null; // On parse error, treat as null
}
}
private function resolveDealProbability(?string $stageProbability): int
{
if ($stageProbability === null) {
return 0;
}
$probability = (float) $stageProbability;
return $probability > 1 ? 0 : (int) ($probability * 100);
}
private function resolveForecastCategory(?string $forecastCategory): string
{
if (! $forecastCategory) {
return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;
}
$forecastCategory = str_replace('_', ' ', $forecastCategory);
return ucwords(strtolower($forecastCategory));
}
private function importExternalFieldData(array $properties, int $opportunityId): void
{
$crmFields = $this->getOpportunitySyncableFields();
$this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);
}
private function importOpportunityContacts(Opportunity $opportunity, array $associations): void
{
// Handle empty or missing contact associations
if (empty($associations)) {
// Remove all existing contact associations if none provided
$this->removeAllOpportunityContacts($opportunity);
return;
}
// Use differential sync approach for better performance and accuracy
$this->syncOpportunityContactsDifferential($opportunity, $associations);
}
/**
* Sync opportunity contacts using differential approach
* This compares current vs new associations and only makes necessary changes
*/
private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void
{
$currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);
$contactAssociationIds = array_keys($contactAssociations);
$contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);
$contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);
if (empty($contactsToAdd) && empty($contactsToRemove)) {
return;
}
$this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);
$this->removeContactAssociations($opportunity, $contactsToRemove);
$this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);
}
private function getCurrentContactCrmIds(Opportunity $opportunity): array
{
return $opportunity->contacts()
->pluck('contacts.crm_provider_id')
->toArray();
}
private function logContactAssociationChanges(
Opportunity $opportunity,
array $currentContactCrmIds,
array $contactAssociations,
array $contactsToAdd,
array $contactsToRemove
): void {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [
'opportunity_id' => $opportunity->getId(),
'current_contacts' => $currentContactCrmIds,
'new_contacts' => $contactAssociations,
'contacts_to_add' => $contactsToAdd,
'contacts_to_remove' => $contactsToRemove,
]);
}
private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void
{
if (empty($contactsToRemove)) {
return;
}
$contactsToDetach = $opportunity->contacts()
->whereIn('contacts.crm_provider_id', $contactsToRemove)
->pluck('contacts.id')
->toArray();
if (! empty($contactsToDetach)) {
$opportunity->contacts()->detach($contactsToDetach);
$this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_contact_crm_ids' => $contactsToRemove,
'removed_contact_count' => count($contactsToDetach),
]);
}
}
private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void
{
if (empty($contactsToAdd)) {
return;
}
$contactsAdded = [];
foreach ($contactsToAdd as $crmId) {
$id = $contactAssociations[$crmId];
if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {
$contactsAdded[] = $crmId;
}
}
$this->logAddedContacts($opportunity, $contactsAdded);
}
private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool
{
try {
$contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);
if (! $contact) {
return false;
}
return $this->performContactAttachment($opportunity, $contact, $crmId);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [
'opportunity_id' => $opportunity->getId(),
'contact_crm_id' => $crmId,
'error' => $e->getMessage(),
]);
return false;
}
}
private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool
{
try {
$opportunity->contacts()->attach($contact->getId(), [
'crm_provider_id' => $crmId,
]);
return true;
} catch (\Illuminate\Database\QueryException $e) {
if (str_contains($e->getMessage(), 'Duplicate entry')) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [
'contact_id' => $contact->getId(),
'contact_crm_id' => $crmId,
'opportunity_id' => $opportunity->getId(),
]);
return false;
}
throw $e;
}
}
private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void
{
if (! empty($contactsAdded)) {
$this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [
'opportunity_id' => $opportunity->getId(),
'contacts_to_add_count' => count($contactsAdded),
'added_contact_crm_ids' => $contactsAdded,
'added_contacts_count' => count($contactsAdded),
]);
}
}
}
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
26
9
22
3
103
Previous Highlighted Error
Next Highlighted Error...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.03046875,"top":0.017361112,"width":0.0453125,"height":0.022222223},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"bounds":{"left":0.07578125,"top":0.017361112,"width":0.14960937,"height":0.022222223},"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.78515625,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsCommandTest","depth":6,"bounds":{"left":0.803125,"top":0.017361112,"width":0.09765625,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsCommandTest'","depth":6,"bounds":{"left":0.9007813,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsCommandTest'","depth":6,"bounds":{"left":0.9140625,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9273437,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96015626,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9734375,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9867188,"top":0.017361112,"width":0.013281226,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show Replace Field","depth":4,"bounds":{"left":0.12382813,"top":0.19930555,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Search History","depth":3,"bounds":{"left":0.13867188,"top":0.19861111,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"\"podcast_audio_url\"","depth":4,"bounds":{"left":0.1515625,"top":0.19861111,"width":0.06367187,"height":0.013888889},"value":"\"podcast_audio_url\"","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"bounds":{"left":0.22578125,"top":0.19861111,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Match Case","depth":3,"bounds":{"left":0.2375,"top":0.19861111,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Words","depth":3,"bounds":{"left":0.24765626,"top":0.19861111,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Regex","depth":3,"bounds":{"left":0.2578125,"top":0.19861111,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Replace History","depth":3,"bounds":{"left":0.23320313,"top":1.0,"width":0.00859375,"height":0.0},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Replace","depth":4,"role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"bounds":{"left":0.23320313,"top":1.0,"width":0.00859375,"height":0.0},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Preserve case","depth":3,"bounds":{"left":0.23320313,"top":1.0,"width":0.00859375,"height":0.0},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"0 results","depth":4,"bounds":{"left":0.27382812,"top":0.19791667,"width":0.030078124,"height":0.015277778},"role_description":"text"},{"role":"AXButton","text":"Previous Occurrence","depth":4,"bounds":{"left":0.30390626,"top":0.19722222,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Occurrence","depth":4,"bounds":{"left":0.3140625,"top":0.19722222,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Filter Search Results","depth":4,"bounds":{"left":0.32421875,"top":0.19722222,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open in Window, Multiple Cursors","depth":4,"bounds":{"left":0.334375,"top":0.19722222,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Click to highlight","depth":4,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":4,"bounds":{"left":0.46640626,"top":0.19722222,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.049609374,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"32","depth":4,"bounds":{"left":0.42539063,"top":0.22430556,"width":0.012109375,"height":0.013194445},"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.43984374,"top":0.22430556,"width":0.009375,"height":0.013194445},"role_description":"text"},{"role":"AXStaticText","text":"19","depth":4,"bounds":{"left":0.4515625,"top":0.22430556,"width":0.011328125,"height":0.013194445},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.46484375,"top":0.22291666,"width":0.00859375,"height":0.015972223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4734375,"top":0.22291666,"width":0.008203125,"height":0.015972223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\CollectionResponseAssociatedId;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Models\\Account;\nuse Exception;\nuse Jiminny\\Component\\DealInsights\\Forecast\\Forecast;\nuse Jiminny\\Jobs\\Crm\\MatchActivitiesToNewOpportunity;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Crm\\BusinessProcess;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Models\\Opportunity;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DealFieldsService;\nuse Jiminny\\Services\\Crm\\Hubspot\\OpportunitySyncStrategy\\HubspotSingleSyncStrategy;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Services\\Crm\\OpportunitySyncStrategyResolver;\nuse Jiminny\\Utils\\CurrencyFormatter;\n\n/**\n * Optimized sync methods for better performance\n * These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains\n */\ntrait OpportunitySyncTrait\n{\n private const int BATCH_SIZE = 100;\n private const int BATCH_PROCESS_SIZE = 800;\n\n protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;\n protected CrmEntityRepository $crmEntityRepository;\n protected DealFieldsService $dealFieldsService;\n\n private ?array $cachedClosedDealStages = null;\n private array $cachedBusinessProcesses = [];\n private array $cachedStages = [];\n\n public function syncOpportunities(array $parameters, ?string $strategy = null): int\n {\n $strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);\n $parameters['config'] = $this->config;\n $syncCount = 0;\n $reportedTotal = 0;\n $lastSyncedId = [];\n\n try {\n foreach ($strategies as $strategyName => $syncStrategy) {\n $this->logger->info(\n '[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .\n $strategyName\n );\n\n $total = 0;\n $lastId = null;\n $buffer = [];\n\n // HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies\n foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {\n $buffer[] = $hsOpportunity;\n\n // process every 800 rows (fits < 1 000 association limit)\n if (\\count($buffer) >= self::BATCH_PROCESS_SIZE) {\n $syncCount += $this->processOpportunityBatch($buffer);\n $buffer = [];\n }\n }\n\n // leftovers\n if ($buffer) {\n $syncCount += $this->processOpportunityBatch($buffer);\n }\n\n $reportedTotal += $total;\n $lastSyncedId = $lastId;\n }\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException | CrmException $e) {\n $this->handleSyncException($e, $parameters);\n }\n\n $this->logger->info(\n '[HubSpot] Synced opportunities',\n [\n 'team' => $this->team->getId(),\n 'sync_count' => $syncCount,\n 'total' => $reportedTotal,\n 'last_synced_id' => $lastSyncedId,\n ]\n );\n\n return $reportedTotal;\n }\n\n private function handleSyncException(\\Throwable $e, array $parameters): void\n {\n if (($parameters['since'] ?? null) instanceof Carbon) {\n $parameters['since'] = $parameters['since']->toDateTimeString();\n }\n $parameters['config'] = $this->config->getId();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [\n 'teamId' => $this->team->getUuid(),\n 'parameters' => $parameters,\n 'reason' => $e->getMessage(),\n ]);\n }\n\n /**\n * @inheritdoc\n */\n public function syncOpportunity(string $crmId): ?Opportunity\n {\n $strategy = $this->opportunitySyncStrategyResolver->resolve(\n $this->config,\n OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,\n );\n\n $parameters = [\n 'config' => $this->config,\n 'crm_id' => $crmId,\n ];\n\n try {\n if (! $strategy instanceof HubspotSingleSyncStrategy) {\n throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');\n }\n\n $hsOpportunity = $strategy->fetchOpportunity($parameters);\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);\n\n return $this->importOrUpdateOpportunity($hsOpportunity);\n }\n\n /**\n * Process webhook-collected opportunity batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportOpportunityBatch jobs for batch processing.\n *\n * @return int Number of opportunity IDs dispatched to jobs\n */\n public function batchSyncOpportunities(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,\n $configId\n );\n }\n\n /**\n * Import a batch of opportunities by their CRM IDs.\n * Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().\n *\n * @param array<string> $crmIds HubSpot deal CRM IDs\n *\n * @return array{success: array, failed_ids: array, errors?: array<string, string>}\n */\n public function importOpportunityBatchByIds(array $crmIds): array\n {\n $fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);\n\n $allDeals = [];\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n $deals = $this->client->getOpportunitiesByIds($chunk, $fields);\n foreach ($deals as $deal) {\n $allDeals[] = $deal;\n }\n }\n\n // IDs not returned by HubSpot are likely deleted or inaccessible deals.\n // These are not failures — retrying won't bring them back.\n $fetchedIds = array_map('strval', array_column($allDeals, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allDeals),\n ]);\n }\n\n if (empty($allDeals)) {\n return ['success' => [], 'failed_ids' => []];\n }\n\n return $this->importOpportunityBatch($allDeals);\n }\n\n private function getClosedDealStages(): array\n {\n if ($this->cachedClosedDealStages !== null) {\n return $this->cachedClosedDealStages;\n }\n\n $stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);\n $data = [\n 'lost' => [],\n 'won' => [],\n ];\n\n foreach ($stages as $stage) {\n if ($stage->probability == 0.00) {\n $data['lost'][] = $stage->crm_provider_id;\n }\n if ($stage->probability == 100.00) {\n $data['won'][] = $stage->crm_provider_id;\n }\n }\n\n $this->cachedClosedDealStages = $data;\n\n return $data;\n }\n\n /**\n * Import deals into the database with pre-fetched associations.\n *\n * API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT\n * caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()\n * where Laravel retries the whole job with backoff. After all retries exhausted,\n * failed() requeues all IDs to Redis.\n *\n * The per-deal loop catches exceptions individually. A deal can end up in three states:\n * - success: imported/updated successfully\n * - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)\n * These are permanent issues — retrying won't fix them.\n * - skipped (null): missing dependencies (no account, unknown pipeline/stage).\n * This is acceptable — the deal cannot be imported until those exist.\n */\n private function importOpportunityBatch(array $deals): array\n {\n $syncedOpportunities = [\n 'success' => [],\n 'failed_ids' => [],\n ];\n $dealIds = array_column($deals, 'id');\n\n // Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the\n // queue job retries the whole batch and eventually requeues all deal IDs back to Redis.\n try {\n $companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');\n $contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');\n\n $associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);\n\n $existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(\n $this->config,\n array_map('strval', $dealIds)\n );\n $existingCrmIdSet = array_flip($existingCrmIds);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [\n 'teamId' => $this->team->getId(),\n 'dealCount' => count($dealIds),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n foreach ($deals as $deal) {\n try {\n $deal['associations'] = $this->prepareAssociationsForOpportunity(\n $deal['id'],\n $companyAssociations,\n $contactAssociations,\n $associationsData\n );\n\n $syncedOpportunity = $this->importOrUpdateOpportunity(\n $deal,\n isset($existingCrmIdSet[(string) $deal['id']])\n );\n if ($syncedOpportunity) {\n $syncedOpportunities['success'][] = $syncedOpportunity;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $deal['id'],\n 'error' => $e->getMessage(),\n ]);\n $syncedOpportunities['failed_ids'][] = $deal['id'];\n $syncedOpportunities['errors'][$deal['id']] = $e->getMessage();\n }\n }\n\n return $syncedOpportunities;\n }\n\n /**\n * Prepare associated entities for opportunities with optimized batch processing\n * Returns structured data with CRM ID to DB ID mappings for each opportunity\n */\n private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array\n {\n // Step 1: Collect all unique company and contact IDs from associations\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n\n // Step 2: Batch sync missing entities and get CRM ID to DB ID mappings\n $companyIdMappings = [];\n $contactIdMappings = [];\n\n if (! empty($allCompanyIds)) {\n $companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);\n }\n\n if (! empty($allContactIds)) {\n $contactIdMappings = $this->prepareAssociatedContacts($allContactIds);\n }\n\n return [\n 'company_id_mappings' => $companyIdMappings,\n 'contact_id_mappings' => $contactIdMappings,\n ];\n }\n\n /**\n * Flatten association data to get unique IDs\n */\n private function flattenAssociationIds(array $associations): array\n {\n $ids = [];\n foreach ($associations as $dealAssociations) {\n if (is_array($dealAssociations)) {\n foreach ($dealAssociations as $id) {\n $ids[$id] = true;\n }\n }\n }\n\n return array_keys($ids);\n }\n\n /**\n * Batch sync missing accounts\n */\n private function prepareAssociatedAccounts(array $companyIds): array\n {\n // Find which accounts already exist\n $existingAccounts = $this->crmEntityRepository\n ->findAccountsByExternalIds($this->config, $companyIds);\n\n $existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();\n\n $existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {\n return [$account->getCrmProviderId() => $account->getId()];\n })->toArray();\n\n $missingCompanyIds = array_diff($companyIds, $existingCompanyIds);\n\n if (empty($missingCompanyIds)) {\n return $existingAccountsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [\n 'teamId' => $this->team->getUuid(),\n 'total_companies' => count($companyIds),\n 'existing_companies' => count($existingCompanyIds),\n 'missing_companies' => count($missingCompanyIds),\n ]);\n\n // we already have limit on opportunity ids count\n // Initialize variable before try block\n $syncedAccountsData = [];\n\n try {\n $syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [\n 'size' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedAccountsData = [];\n }\n\n return $existingAccountsData + $syncedAccountsData;\n }\n\n /**\n * Prepare associated contacts - find existing and sync missing ones\n * Returns mapping of CRM ID to DB ID\n */\n private function prepareAssociatedContacts(array $contactIds): array\n {\n // Find which contacts already exist\n $existingContacts = $this->crmEntityRepository\n ->findContactsByExternalIds($this->config, $contactIds);\n\n $existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();\n\n // Create mapping for existing contacts\n $existingContactsData = $existingContacts->mapWithKeys(function ($contact) {\n return [$contact->getCrmProviderId() => $contact->getId()];\n })->toArray();\n\n $missingContactIds = array_diff($contactIds, $existingContactIds);\n\n if (empty($missingContactIds)) {\n return $existingContactsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [\n 'teamId' => $this->team->getUuid(),\n 'total_contacts' => count($contactIds),\n 'existing_contacts' => count($existingContactIds),\n 'missing_contacts' => count($missingContactIds),\n ]);\n\n // Sync missing contacts using batch API\n try {\n $syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [\n 'size' => count($missingContactIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedContactsData = [];\n }\n\n return $existingContactsData + $syncedContactsData;\n }\n\n private function batchSyncCrmObjects(string $objectType, array $crmIds): array\n {\n $syncObjects = [];\n $crmObjectIds = array_values($crmIds);\n\n foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {\n try {\n $objects = $objectType === 'companies' ?\n $this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :\n $this->client->getContactsByIds($chunk, $this->getContactFields());\n\n foreach ($objects as $objectId => $objectData) {\n $this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [\n 'requested_count' => count($chunk),\n 'synced_count' => count($objects),\n ]);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [\n 'ids' => $chunk,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncObjects;\n }\n\n private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void\n {\n try {\n $object = $objectType === 'companies' ?\n $this->importAccount($objectData) :\n $this->importContact($objectData);\n\n if ($object) {\n $syncObjects[$object->getCrmProviderId()] = $object->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [\n 'id' => $objectId,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n /**\n * Prepare associations for a single opportunity\n *\n * The return value is an array with the following structure:\n * [\n * 'companies' => [\n * $companyCrmId => $companyId,\n * ...\n * ],\n * 'contacts' => [\n * $contactCrmId => $contactId,\n * ...\n * ],\n * 'account_id' => $accountId,\n * ]\n */\n private function prepareAssociationsForOpportunity(\n string $oppCrmId,\n array $companyAssociations,\n array $contactAssociations,\n array $associationsData\n ): array {\n $associations = [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n\n $oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];\n foreach ($oppCompanyIds as $companyCrmId) {\n if (isset($associationsData['company_id_mappings'][$companyCrmId])) {\n $associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];\n\n // Set primary account (first company becomes primary account)\n if ($associations['account_id'] === null) {\n $associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];\n }\n }\n }\n\n $oppContactIds = $contactAssociations[$oppCrmId] ?? [];\n foreach ($oppContactIds as $contactCrmId) {\n if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {\n $associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];\n }\n }\n\n return $associations;\n }\n\n /**\n * Update only associations for an opportunity\n */\n private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void\n {\n // Update contact associations\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n // Update company (account) associations\n $this->updateOpportunityAccount($opportunity, $associations['account_id']);\n }\n\n /**\n * Remove all contact associations from an opportunity\n */\n private function removeAllOpportunityContacts(Opportunity $opportunity): void\n {\n $currentCount = (int) $opportunity->contacts()->count();\n\n if ($currentCount > 0) {\n $opportunity->contacts()->detach();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_count' => $currentCount,\n ]);\n }\n }\n\n private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void\n {\n if ($accountId === null) {\n // No account ID provided - keep current account\n return;\n }\n\n $currentAccountId = $opportunity->getAccountId();\n\n // Only update if account has changed\n if ($currentAccountId !== $accountId) {\n $opportunity->account_id = $accountId;\n $opportunity->save();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [\n 'opportunity_id' => $opportunity->getId(),\n 'old_account_id' => $currentAccountId,\n 'new_account_id' => $accountId,\n ]);\n }\n }\n\n /**\n * Find existing opportunities by external IDs (OPTIMIZED VERSION)\n * Uses batch query for better performance\n */\n private function findExistingOpportunities(array $crmIds): Collection\n {\n return $this->crmEntityRepository\n ->findOpportunitiesByExternalIds($this->config, $crmIds);\n }\n\n private function processOpportunityBatch(array $opportunities): int\n {\n $syncedOpportunities = $this->importOpportunityBatch($opportunities);\n\n return count($syncedOpportunities['success'] ?? []);\n }\n\n /**\n * Convert single deal associations from HubSpot format to internal format\n * Handles both HubSpot SDK objects and array formats\n *\n * @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed\n *\n * @return array Processed associations with DB IDs\n */\n private function convertDealAssociations(array $opportunityAssociations): array\n {\n $associations = $this->initializeAssociationsStructure();\n\n if (empty($opportunityAssociations)) {\n return $associations;\n }\n\n $associationIds = $this->extractAssociationIds($opportunityAssociations);\n\n $this->processCompanyAssociations($associationIds, $associations);\n $this->processContactAssociations($associationIds, $associations);\n\n return $associations;\n }\n\n private function initializeAssociationsStructure(): array\n {\n return [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n }\n\n private function extractAssociationIds(array $opportunityAssociations): array\n {\n $associationIds = [];\n\n foreach ($opportunityAssociations as $type => $associationData) {\n if (! empty($associationData)) {\n $associationIds[$type] = $this->convertSingleDealAssociations($associationData);\n }\n }\n\n return $associationIds;\n }\n\n private function processCompanyAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['companies'])) {\n return;\n }\n\n $companyId = $associationIds['companies'][0];\n $account = $this->findOrSyncAccount($companyId);\n\n if ($account instanceof Account) {\n $associations['companies'][$companyId] = $account->getId();\n $associations['account_id'] = $account->getId();\n }\n }\n\n private function processContactAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['contacts'])) {\n return;\n }\n\n foreach ($associationIds['contacts'] as $contactId) {\n $contact = $this->findOrSyncContact($contactId);\n\n if ($contact instanceof Contact) {\n $associations['contacts'][$contactId] = $contact->getId();\n }\n }\n }\n\n private function findOrSyncAccount(string $companyId): ?Account\n {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);\n\n if (! $account instanceof Account) {\n $account = $this->syncAccount($companyId);\n }\n\n return $account;\n }\n\n private function findOrSyncContact(string $contactId): ?Contact\n {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);\n\n if (! $contact instanceof Contact) {\n $contact = $this->syncContact($contactId);\n }\n\n return $contact;\n }\n\n private function convertSingleDealAssociations($opportunityAssociations = null): array\n {\n $associationData = [];\n\n if ($opportunityAssociations === null) {\n return $associationData;\n }\n\n // Handle array input (from extractAssociationIds)\n if (is_array($opportunityAssociations)) {\n return $opportunityAssociations;\n }\n\n // Handle CollectionResponseAssociatedId object\n if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {\n foreach ($opportunityAssociations->getResults() as $association) {\n $associationData[] = $association->getId();\n }\n }\n\n return $associationData;\n }\n\n private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity\n {\n if (empty($crmData['properties'])) {\n return null;\n }\n\n $crmId = (string) $crmData['id'];\n $properties = $crmData['properties'];\n $associations = $crmData['associations'] ?? [];\n\n $opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(\n $this->config,\n $crmId\n );\n\n if ($opportunityExists) {\n return $this->updateOpportunity($crmId, $properties, $associations);\n } else {\n return $this->createOpportunity($crmId, $properties, $associations);\n }\n }\n\n /**\n * Create new opportunity\n */\n private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n if (! $accountId) {\n return null;\n }\n\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n if (! $businessProcess) {\n return null;\n }\n\n $stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);\n if (! $stage) {\n return null;\n }\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n if ($opportunity->wasRecentlyCreated) {\n MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());\n }\n\n return $opportunity;\n }\n\n /**\n * Update existing opportunity\n */\n private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n $stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->updateOpportunityAssociations($opportunity, $associations);\n\n return $opportunity;\n }\n\n private function resolveAccountId(array $associations): ?int\n {\n if (! empty($associations['accountId'])) {\n return $associations['accountId'];\n }\n\n if (empty($associations)) {\n return null;\n }\n\n // we can't resolve multiple account ids (currently SDK returns one company)\n foreach ($associations['companies'] as $accountId) {\n return $accountId;\n }\n\n return null;\n }\n\n private function buildOpportunityData(\n array $properties,\n ?int $accountId,\n ?BusinessProcess $businessProcess,\n ?Stage $stage\n ): array {\n $ownerId = null;\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $name = 'Unknown';\n if (isset($properties['dealname'])) {\n $name = mb_strimwidth($properties['dealname'], 0, 128);\n }\n\n $amount = $this->resolveAmount($properties);\n $currency = $properties['deal_currency_code'] ?? null;\n\n $closeDate = null;\n if (! empty($properties['closedate'])) {\n $closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');\n }\n\n $remotelyCreatedAt = null;\n if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {\n $date = $this->parseCleanDatetime($properties['createdate']);\n $remotelyCreatedAt = $date?->format('Y-m-d H:i:s');\n }\n\n $closedStages = $this->getClosedDealStages();\n $isWon = in_array($properties['dealstage'], $closedStages['won']);\n $isLost = in_array($properties['dealstage'], $closedStages['lost']);\n\n $data = [\n 'team_id' => $this->team->getId(),\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'value' => ! empty($amount) ? $amount : null,\n 'currency_code' => CurrencyFormatter::formatCode($currency),\n 'close_date' => $closeDate,\n 'is_closed' => $isWon || $isLost,\n 'is_won' => $isWon,\n 'remotely_created_at' => $remotelyCreatedAt,\n 'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),\n 'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),\n ];\n\n if ($accountId) {\n $data['account_id'] = $accountId;\n }\n\n if ($stage) {\n $data['stage_id'] = $stage->id;\n }\n\n if ($businessProcess) {\n $recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);\n if ($recordType) {\n $data['record_type_id'] = $recordType->id;\n }\n }\n\n return $data;\n }\n\n private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess\n {\n if ($pipelineId === null) {\n return null;\n }\n\n if (isset($this->cachedBusinessProcesses[$pipelineId])) {\n return $this->cachedBusinessProcesses[$pipelineId];\n }\n\n $businessProcess = $this->getBusinessProcess($pipelineId);\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->importStages();\n $businessProcess = $this->getBusinessProcess($pipelineId);\n }\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->logger->info(\n '[HubSpot] Deal is not attached to a pipeline',\n [\n 'pipeline' => $pipelineId]\n );\n }\n\n $this->cachedBusinessProcesses[$pipelineId] = $businessProcess;\n\n return $businessProcess;\n }\n\n private function getBusinessProcess(string $pipelineId): ?BusinessProcess\n {\n return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);\n }\n\n private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage\n {\n if (empty($stageId)) {\n return null;\n }\n\n $cacheKey = $businessProcess->getId() . ':' . $stageId;\n if (isset($this->cachedStages[$cacheKey])) {\n return $this->cachedStages[$cacheKey];\n }\n\n $stage = $this->crmEntityRepository->getPipelineStageByConditions(\n $businessProcess,\n [\n 'crm_provider_id' => $stageId,\n 'type' => Stage::TYPE_OPPORTUNITY,\n ]\n );\n\n if ($stage === null) {\n $this->importStages(null, $stageId);\n }\n\n if ($stage === null) {\n $this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);\n }\n\n $this->cachedStages[$cacheKey] = $stage;\n\n return $stage;\n }\n\n private function resolveAmount(array $properties): ?string\n {\n $amount = null;\n if (! empty($properties['amount'])) {\n $amount = str_replace(',', '', $properties['amount']);\n }\n\n if ($this->config->hasDefaultCurrencyFieldSet()) {\n $valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();\n $amount = $properties[$valueFieldName] ?? $amount;\n }\n\n return $amount;\n }\n\n private function parseCleanDatetime(string $datetime): ?Carbon\n {\n // Treat pre-1980 values as invalid\n $minValidDate = Carbon::parse('1980-01-01 00:00:00');\n\n try {\n $date = Carbon::parse($datetime);\n\n if ($minValidDate->gt($date)) {\n return null;\n }\n\n return $date;\n } catch (Exception) {\n return null; // On parse error, treat as null\n }\n }\n\n private function resolveDealProbability(?string $stageProbability): int\n {\n if ($stageProbability === null) {\n return 0;\n }\n\n $probability = (float) $stageProbability;\n\n return $probability > 1 ? 0 : (int) ($probability * 100);\n }\n\n private function resolveForecastCategory(?string $forecastCategory): string\n {\n if (! $forecastCategory) {\n return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;\n }\n\n $forecastCategory = str_replace('_', ' ', $forecastCategory);\n\n return ucwords(strtolower($forecastCategory));\n }\n\n private function importExternalFieldData(array $properties, int $opportunityId): void\n {\n $crmFields = $this->getOpportunitySyncableFields();\n $this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);\n }\n\n private function importOpportunityContacts(Opportunity $opportunity, array $associations): void\n {\n // Handle empty or missing contact associations\n if (empty($associations)) {\n // Remove all existing contact associations if none provided\n $this->removeAllOpportunityContacts($opportunity);\n\n return;\n }\n\n // Use differential sync approach for better performance and accuracy\n $this->syncOpportunityContactsDifferential($opportunity, $associations);\n }\n\n /**\n * Sync opportunity contacts using differential approach\n * This compares current vs new associations and only makes necessary changes\n */\n private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void\n {\n $currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);\n $contactAssociationIds = array_keys($contactAssociations);\n\n $contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);\n $contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);\n\n if (empty($contactsToAdd) && empty($contactsToRemove)) {\n return;\n }\n\n $this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);\n\n $this->removeContactAssociations($opportunity, $contactsToRemove);\n $this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);\n }\n\n private function getCurrentContactCrmIds(Opportunity $opportunity): array\n {\n return $opportunity->contacts()\n ->pluck('contacts.crm_provider_id')\n ->toArray();\n }\n\n private function logContactAssociationChanges(\n Opportunity $opportunity,\n array $currentContactCrmIds,\n array $contactAssociations,\n array $contactsToAdd,\n array $contactsToRemove\n ): void {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [\n 'opportunity_id' => $opportunity->getId(),\n 'current_contacts' => $currentContactCrmIds,\n 'new_contacts' => $contactAssociations,\n 'contacts_to_add' => $contactsToAdd,\n 'contacts_to_remove' => $contactsToRemove,\n ]);\n }\n\n private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void\n {\n if (empty($contactsToRemove)) {\n return;\n }\n\n $contactsToDetach = $opportunity->contacts()\n ->whereIn('contacts.crm_provider_id', $contactsToRemove)\n ->pluck('contacts.id')\n ->toArray();\n\n if (! empty($contactsToDetach)) {\n $opportunity->contacts()->detach($contactsToDetach);\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_contact_crm_ids' => $contactsToRemove,\n 'removed_contact_count' => count($contactsToDetach),\n ]);\n }\n }\n\n private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void\n {\n if (empty($contactsToAdd)) {\n return;\n }\n\n $contactsAdded = [];\n foreach ($contactsToAdd as $crmId) {\n $id = $contactAssociations[$crmId];\n\n if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {\n $contactsAdded[] = $crmId;\n }\n }\n\n $this->logAddedContacts($opportunity, $contactsAdded);\n }\n\n private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);\n\n if (! $contact) {\n return false;\n }\n\n return $this->performContactAttachment($opportunity, $contact, $crmId);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [\n 'opportunity_id' => $opportunity->getId(),\n 'contact_crm_id' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool\n {\n try {\n $opportunity->contacts()->attach($contact->getId(), [\n 'crm_provider_id' => $crmId,\n ]);\n\n return true;\n } catch (\\Illuminate\\Database\\QueryException $e) {\n if (str_contains($e->getMessage(), 'Duplicate entry')) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [\n 'contact_id' => $contact->getId(),\n 'contact_crm_id' => $crmId,\n 'opportunity_id' => $opportunity->getId(),\n ]);\n\n return false;\n }\n\n throw $e;\n }\n }\n\n private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void\n {\n if (! empty($contactsAdded)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'contacts_to_add_count' => count($contactsAdded),\n 'added_contact_crm_ids' => $contactsAdded,\n 'added_contacts_count' => count($contactsAdded),\n ]);\n }\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\CollectionResponseAssociatedId;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Models\\Account;\nuse Exception;\nuse Jiminny\\Component\\DealInsights\\Forecast\\Forecast;\nuse Jiminny\\Jobs\\Crm\\MatchActivitiesToNewOpportunity;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Crm\\BusinessProcess;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Models\\Opportunity;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DealFieldsService;\nuse Jiminny\\Services\\Crm\\Hubspot\\OpportunitySyncStrategy\\HubspotSingleSyncStrategy;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Services\\Crm\\OpportunitySyncStrategyResolver;\nuse Jiminny\\Utils\\CurrencyFormatter;\n\n/**\n * Optimized sync methods for better performance\n * These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains\n */\ntrait OpportunitySyncTrait\n{\n private const int BATCH_SIZE = 100;\n private const int BATCH_PROCESS_SIZE = 800;\n\n protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;\n protected CrmEntityRepository $crmEntityRepository;\n protected DealFieldsService $dealFieldsService;\n\n private ?array $cachedClosedDealStages = null;\n private array $cachedBusinessProcesses = [];\n private array $cachedStages = [];\n\n public function syncOpportunities(array $parameters, ?string $strategy = null): int\n {\n $strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);\n $parameters['config'] = $this->config;\n $syncCount = 0;\n $reportedTotal = 0;\n $lastSyncedId = [];\n\n try {\n foreach ($strategies as $strategyName => $syncStrategy) {\n $this->logger->info(\n '[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .\n $strategyName\n );\n\n $total = 0;\n $lastId = null;\n $buffer = [];\n\n // HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies\n foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {\n $buffer[] = $hsOpportunity;\n\n // process every 800 rows (fits < 1 000 association limit)\n if (\\count($buffer) >= self::BATCH_PROCESS_SIZE) {\n $syncCount += $this->processOpportunityBatch($buffer);\n $buffer = [];\n }\n }\n\n // leftovers\n if ($buffer) {\n $syncCount += $this->processOpportunityBatch($buffer);\n }\n\n $reportedTotal += $total;\n $lastSyncedId = $lastId;\n }\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException | CrmException $e) {\n $this->handleSyncException($e, $parameters);\n }\n\n $this->logger->info(\n '[HubSpot] Synced opportunities',\n [\n 'team' => $this->team->getId(),\n 'sync_count' => $syncCount,\n 'total' => $reportedTotal,\n 'last_synced_id' => $lastSyncedId,\n ]\n );\n\n return $reportedTotal;\n }\n\n private function handleSyncException(\\Throwable $e, array $parameters): void\n {\n if (($parameters['since'] ?? null) instanceof Carbon) {\n $parameters['since'] = $parameters['since']->toDateTimeString();\n }\n $parameters['config'] = $this->config->getId();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [\n 'teamId' => $this->team->getUuid(),\n 'parameters' => $parameters,\n 'reason' => $e->getMessage(),\n ]);\n }\n\n /**\n * @inheritdoc\n */\n public function syncOpportunity(string $crmId): ?Opportunity\n {\n $strategy = $this->opportunitySyncStrategyResolver->resolve(\n $this->config,\n OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,\n );\n\n $parameters = [\n 'config' => $this->config,\n 'crm_id' => $crmId,\n ];\n\n try {\n if (! $strategy instanceof HubspotSingleSyncStrategy) {\n throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');\n }\n\n $hsOpportunity = $strategy->fetchOpportunity($parameters);\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);\n\n return $this->importOrUpdateOpportunity($hsOpportunity);\n }\n\n /**\n * Process webhook-collected opportunity batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportOpportunityBatch jobs for batch processing.\n *\n * @return int Number of opportunity IDs dispatched to jobs\n */\n public function batchSyncOpportunities(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,\n $configId\n );\n }\n\n /**\n * Import a batch of opportunities by their CRM IDs.\n * Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().\n *\n * @param array<string> $crmIds HubSpot deal CRM IDs\n *\n * @return array{success: array, failed_ids: array, errors?: array<string, string>}\n */\n public function importOpportunityBatchByIds(array $crmIds): array\n {\n $fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);\n\n $allDeals = [];\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n $deals = $this->client->getOpportunitiesByIds($chunk, $fields);\n foreach ($deals as $deal) {\n $allDeals[] = $deal;\n }\n }\n\n // IDs not returned by HubSpot are likely deleted or inaccessible deals.\n // These are not failures — retrying won't bring them back.\n $fetchedIds = array_map('strval', array_column($allDeals, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allDeals),\n ]);\n }\n\n if (empty($allDeals)) {\n return ['success' => [], 'failed_ids' => []];\n }\n\n return $this->importOpportunityBatch($allDeals);\n }\n\n private function getClosedDealStages(): array\n {\n if ($this->cachedClosedDealStages !== null) {\n return $this->cachedClosedDealStages;\n }\n\n $stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);\n $data = [\n 'lost' => [],\n 'won' => [],\n ];\n\n foreach ($stages as $stage) {\n if ($stage->probability == 0.00) {\n $data['lost'][] = $stage->crm_provider_id;\n }\n if ($stage->probability == 100.00) {\n $data['won'][] = $stage->crm_provider_id;\n }\n }\n\n $this->cachedClosedDealStages = $data;\n\n return $data;\n }\n\n /**\n * Import deals into the database with pre-fetched associations.\n *\n * API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT\n * caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()\n * where Laravel retries the whole job with backoff. After all retries exhausted,\n * failed() requeues all IDs to Redis.\n *\n * The per-deal loop catches exceptions individually. A deal can end up in three states:\n * - success: imported/updated successfully\n * - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)\n * These are permanent issues — retrying won't fix them.\n * - skipped (null): missing dependencies (no account, unknown pipeline/stage).\n * This is acceptable — the deal cannot be imported until those exist.\n */\n private function importOpportunityBatch(array $deals): array\n {\n $syncedOpportunities = [\n 'success' => [],\n 'failed_ids' => [],\n ];\n $dealIds = array_column($deals, 'id');\n\n // Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the\n // queue job retries the whole batch and eventually requeues all deal IDs back to Redis.\n try {\n $companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');\n $contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');\n\n $associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);\n\n $existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(\n $this->config,\n array_map('strval', $dealIds)\n );\n $existingCrmIdSet = array_flip($existingCrmIds);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [\n 'teamId' => $this->team->getId(),\n 'dealCount' => count($dealIds),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n foreach ($deals as $deal) {\n try {\n $deal['associations'] = $this->prepareAssociationsForOpportunity(\n $deal['id'],\n $companyAssociations,\n $contactAssociations,\n $associationsData\n );\n\n $syncedOpportunity = $this->importOrUpdateOpportunity(\n $deal,\n isset($existingCrmIdSet[(string) $deal['id']])\n );\n if ($syncedOpportunity) {\n $syncedOpportunities['success'][] = $syncedOpportunity;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $deal['id'],\n 'error' => $e->getMessage(),\n ]);\n $syncedOpportunities['failed_ids'][] = $deal['id'];\n $syncedOpportunities['errors'][$deal['id']] = $e->getMessage();\n }\n }\n\n return $syncedOpportunities;\n }\n\n /**\n * Prepare associated entities for opportunities with optimized batch processing\n * Returns structured data with CRM ID to DB ID mappings for each opportunity\n */\n private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array\n {\n // Step 1: Collect all unique company and contact IDs from associations\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n\n // Step 2: Batch sync missing entities and get CRM ID to DB ID mappings\n $companyIdMappings = [];\n $contactIdMappings = [];\n\n if (! empty($allCompanyIds)) {\n $companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);\n }\n\n if (! empty($allContactIds)) {\n $contactIdMappings = $this->prepareAssociatedContacts($allContactIds);\n }\n\n return [\n 'company_id_mappings' => $companyIdMappings,\n 'contact_id_mappings' => $contactIdMappings,\n ];\n }\n\n /**\n * Flatten association data to get unique IDs\n */\n private function flattenAssociationIds(array $associations): array\n {\n $ids = [];\n foreach ($associations as $dealAssociations) {\n if (is_array($dealAssociations)) {\n foreach ($dealAssociations as $id) {\n $ids[$id] = true;\n }\n }\n }\n\n return array_keys($ids);\n }\n\n /**\n * Batch sync missing accounts\n */\n private function prepareAssociatedAccounts(array $companyIds): array\n {\n // Find which accounts already exist\n $existingAccounts = $this->crmEntityRepository\n ->findAccountsByExternalIds($this->config, $companyIds);\n\n $existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();\n\n $existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {\n return [$account->getCrmProviderId() => $account->getId()];\n })->toArray();\n\n $missingCompanyIds = array_diff($companyIds, $existingCompanyIds);\n\n if (empty($missingCompanyIds)) {\n return $existingAccountsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [\n 'teamId' => $this->team->getUuid(),\n 'total_companies' => count($companyIds),\n 'existing_companies' => count($existingCompanyIds),\n 'missing_companies' => count($missingCompanyIds),\n ]);\n\n // we already have limit on opportunity ids count\n // Initialize variable before try block\n $syncedAccountsData = [];\n\n try {\n $syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [\n 'size' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedAccountsData = [];\n }\n\n return $existingAccountsData + $syncedAccountsData;\n }\n\n /**\n * Prepare associated contacts - find existing and sync missing ones\n * Returns mapping of CRM ID to DB ID\n */\n private function prepareAssociatedContacts(array $contactIds): array\n {\n // Find which contacts already exist\n $existingContacts = $this->crmEntityRepository\n ->findContactsByExternalIds($this->config, $contactIds);\n\n $existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();\n\n // Create mapping for existing contacts\n $existingContactsData = $existingContacts->mapWithKeys(function ($contact) {\n return [$contact->getCrmProviderId() => $contact->getId()];\n })->toArray();\n\n $missingContactIds = array_diff($contactIds, $existingContactIds);\n\n if (empty($missingContactIds)) {\n return $existingContactsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [\n 'teamId' => $this->team->getUuid(),\n 'total_contacts' => count($contactIds),\n 'existing_contacts' => count($existingContactIds),\n 'missing_contacts' => count($missingContactIds),\n ]);\n\n // Sync missing contacts using batch API\n try {\n $syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [\n 'size' => count($missingContactIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedContactsData = [];\n }\n\n return $existingContactsData + $syncedContactsData;\n }\n\n private function batchSyncCrmObjects(string $objectType, array $crmIds): array\n {\n $syncObjects = [];\n $crmObjectIds = array_values($crmIds);\n\n foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {\n try {\n $objects = $objectType === 'companies' ?\n $this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :\n $this->client->getContactsByIds($chunk, $this->getContactFields());\n\n foreach ($objects as $objectId => $objectData) {\n $this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [\n 'requested_count' => count($chunk),\n 'synced_count' => count($objects),\n ]);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [\n 'ids' => $chunk,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncObjects;\n }\n\n private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void\n {\n try {\n $object = $objectType === 'companies' ?\n $this->importAccount($objectData) :\n $this->importContact($objectData);\n\n if ($object) {\n $syncObjects[$object->getCrmProviderId()] = $object->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [\n 'id' => $objectId,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n /**\n * Prepare associations for a single opportunity\n *\n * The return value is an array with the following structure:\n * [\n * 'companies' => [\n * $companyCrmId => $companyId,\n * ...\n * ],\n * 'contacts' => [\n * $contactCrmId => $contactId,\n * ...\n * ],\n * 'account_id' => $accountId,\n * ]\n */\n private function prepareAssociationsForOpportunity(\n string $oppCrmId,\n array $companyAssociations,\n array $contactAssociations,\n array $associationsData\n ): array {\n $associations = [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n\n $oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];\n foreach ($oppCompanyIds as $companyCrmId) {\n if (isset($associationsData['company_id_mappings'][$companyCrmId])) {\n $associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];\n\n // Set primary account (first company becomes primary account)\n if ($associations['account_id'] === null) {\n $associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];\n }\n }\n }\n\n $oppContactIds = $contactAssociations[$oppCrmId] ?? [];\n foreach ($oppContactIds as $contactCrmId) {\n if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {\n $associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];\n }\n }\n\n return $associations;\n }\n\n /**\n * Update only associations for an opportunity\n */\n private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void\n {\n // Update contact associations\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n // Update company (account) associations\n $this->updateOpportunityAccount($opportunity, $associations['account_id']);\n }\n\n /**\n * Remove all contact associations from an opportunity\n */\n private function removeAllOpportunityContacts(Opportunity $opportunity): void\n {\n $currentCount = (int) $opportunity->contacts()->count();\n\n if ($currentCount > 0) {\n $opportunity->contacts()->detach();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_count' => $currentCount,\n ]);\n }\n }\n\n private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void\n {\n if ($accountId === null) {\n // No account ID provided - keep current account\n return;\n }\n\n $currentAccountId = $opportunity->getAccountId();\n\n // Only update if account has changed\n if ($currentAccountId !== $accountId) {\n $opportunity->account_id = $accountId;\n $opportunity->save();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [\n 'opportunity_id' => $opportunity->getId(),\n 'old_account_id' => $currentAccountId,\n 'new_account_id' => $accountId,\n ]);\n }\n }\n\n /**\n * Find existing opportunities by external IDs (OPTIMIZED VERSION)\n * Uses batch query for better performance\n */\n private function findExistingOpportunities(array $crmIds): Collection\n {\n return $this->crmEntityRepository\n ->findOpportunitiesByExternalIds($this->config, $crmIds);\n }\n\n private function processOpportunityBatch(array $opportunities): int\n {\n $syncedOpportunities = $this->importOpportunityBatch($opportunities);\n\n return count($syncedOpportunities['success'] ?? []);\n }\n\n /**\n * Convert single deal associations from HubSpot format to internal format\n * Handles both HubSpot SDK objects and array formats\n *\n * @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed\n *\n * @return array Processed associations with DB IDs\n */\n private function convertDealAssociations(array $opportunityAssociations): array\n {\n $associations = $this->initializeAssociationsStructure();\n\n if (empty($opportunityAssociations)) {\n return $associations;\n }\n\n $associationIds = $this->extractAssociationIds($opportunityAssociations);\n\n $this->processCompanyAssociations($associationIds, $associations);\n $this->processContactAssociations($associationIds, $associations);\n\n return $associations;\n }\n\n private function initializeAssociationsStructure(): array\n {\n return [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n }\n\n private function extractAssociationIds(array $opportunityAssociations): array\n {\n $associationIds = [];\n\n foreach ($opportunityAssociations as $type => $associationData) {\n if (! empty($associationData)) {\n $associationIds[$type] = $this->convertSingleDealAssociations($associationData);\n }\n }\n\n return $associationIds;\n }\n\n private function processCompanyAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['companies'])) {\n return;\n }\n\n $companyId = $associationIds['companies'][0];\n $account = $this->findOrSyncAccount($companyId);\n\n if ($account instanceof Account) {\n $associations['companies'][$companyId] = $account->getId();\n $associations['account_id'] = $account->getId();\n }\n }\n\n private function processContactAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['contacts'])) {\n return;\n }\n\n foreach ($associationIds['contacts'] as $contactId) {\n $contact = $this->findOrSyncContact($contactId);\n\n if ($contact instanceof Contact) {\n $associations['contacts'][$contactId] = $contact->getId();\n }\n }\n }\n\n private function findOrSyncAccount(string $companyId): ?Account\n {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);\n\n if (! $account instanceof Account) {\n $account = $this->syncAccount($companyId);\n }\n\n return $account;\n }\n\n private function findOrSyncContact(string $contactId): ?Contact\n {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);\n\n if (! $contact instanceof Contact) {\n $contact = $this->syncContact($contactId);\n }\n\n return $contact;\n }\n\n private function convertSingleDealAssociations($opportunityAssociations = null): array\n {\n $associationData = [];\n\n if ($opportunityAssociations === null) {\n return $associationData;\n }\n\n // Handle array input (from extractAssociationIds)\n if (is_array($opportunityAssociations)) {\n return $opportunityAssociations;\n }\n\n // Handle CollectionResponseAssociatedId object\n if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {\n foreach ($opportunityAssociations->getResults() as $association) {\n $associationData[] = $association->getId();\n }\n }\n\n return $associationData;\n }\n\n private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity\n {\n if (empty($crmData['properties'])) {\n return null;\n }\n\n $crmId = (string) $crmData['id'];\n $properties = $crmData['properties'];\n $associations = $crmData['associations'] ?? [];\n\n $opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(\n $this->config,\n $crmId\n );\n\n if ($opportunityExists) {\n return $this->updateOpportunity($crmId, $properties, $associations);\n } else {\n return $this->createOpportunity($crmId, $properties, $associations);\n }\n }\n\n /**\n * Create new opportunity\n */\n private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n if (! $accountId) {\n return null;\n }\n\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n if (! $businessProcess) {\n return null;\n }\n\n $stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);\n if (! $stage) {\n return null;\n }\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n if ($opportunity->wasRecentlyCreated) {\n MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());\n }\n\n return $opportunity;\n }\n\n /**\n * Update existing opportunity\n */\n private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n $stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->updateOpportunityAssociations($opportunity, $associations);\n\n return $opportunity;\n }\n\n private function resolveAccountId(array $associations): ?int\n {\n if (! empty($associations['accountId'])) {\n return $associations['accountId'];\n }\n\n if (empty($associations)) {\n return null;\n }\n\n // we can't resolve multiple account ids (currently SDK returns one company)\n foreach ($associations['companies'] as $accountId) {\n return $accountId;\n }\n\n return null;\n }\n\n private function buildOpportunityData(\n array $properties,\n ?int $accountId,\n ?BusinessProcess $businessProcess,\n ?Stage $stage\n ): array {\n $ownerId = null;\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $name = 'Unknown';\n if (isset($properties['dealname'])) {\n $name = mb_strimwidth($properties['dealname'], 0, 128);\n }\n\n $amount = $this->resolveAmount($properties);\n $currency = $properties['deal_currency_code'] ?? null;\n\n $closeDate = null;\n if (! empty($properties['closedate'])) {\n $closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');\n }\n\n $remotelyCreatedAt = null;\n if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {\n $date = $this->parseCleanDatetime($properties['createdate']);\n $remotelyCreatedAt = $date?->format('Y-m-d H:i:s');\n }\n\n $closedStages = $this->getClosedDealStages();\n $isWon = in_array($properties['dealstage'], $closedStages['won']);\n $isLost = in_array($properties['dealstage'], $closedStages['lost']);\n\n $data = [\n 'team_id' => $this->team->getId(),\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'value' => ! empty($amount) ? $amount : null,\n 'currency_code' => CurrencyFormatter::formatCode($currency),\n 'close_date' => $closeDate,\n 'is_closed' => $isWon || $isLost,\n 'is_won' => $isWon,\n 'remotely_created_at' => $remotelyCreatedAt,\n 'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),\n 'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),\n ];\n\n if ($accountId) {\n $data['account_id'] = $accountId;\n }\n\n if ($stage) {\n $data['stage_id'] = $stage->id;\n }\n\n if ($businessProcess) {\n $recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);\n if ($recordType) {\n $data['record_type_id'] = $recordType->id;\n }\n }\n\n return $data;\n }\n\n private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess\n {\n if ($pipelineId === null) {\n return null;\n }\n\n if (isset($this->cachedBusinessProcesses[$pipelineId])) {\n return $this->cachedBusinessProcesses[$pipelineId];\n }\n\n $businessProcess = $this->getBusinessProcess($pipelineId);\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->importStages();\n $businessProcess = $this->getBusinessProcess($pipelineId);\n }\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->logger->info(\n '[HubSpot] Deal is not attached to a pipeline',\n [\n 'pipeline' => $pipelineId]\n );\n }\n\n $this->cachedBusinessProcesses[$pipelineId] = $businessProcess;\n\n return $businessProcess;\n }\n\n private function getBusinessProcess(string $pipelineId): ?BusinessProcess\n {\n return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);\n }\n\n private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage\n {\n if (empty($stageId)) {\n return null;\n }\n\n $cacheKey = $businessProcess->getId() . ':' . $stageId;\n if (isset($this->cachedStages[$cacheKey])) {\n return $this->cachedStages[$cacheKey];\n }\n\n $stage = $this->crmEntityRepository->getPipelineStageByConditions(\n $businessProcess,\n [\n 'crm_provider_id' => $stageId,\n 'type' => Stage::TYPE_OPPORTUNITY,\n ]\n );\n\n if ($stage === null) {\n $this->importStages(null, $stageId);\n }\n\n if ($stage === null) {\n $this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);\n }\n\n $this->cachedStages[$cacheKey] = $stage;\n\n return $stage;\n }\n\n private function resolveAmount(array $properties): ?string\n {\n $amount = null;\n if (! empty($properties['amount'])) {\n $amount = str_replace(',', '', $properties['amount']);\n }\n\n if ($this->config->hasDefaultCurrencyFieldSet()) {\n $valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();\n $amount = $properties[$valueFieldName] ?? $amount;\n }\n\n return $amount;\n }\n\n private function parseCleanDatetime(string $datetime): ?Carbon\n {\n // Treat pre-1980 values as invalid\n $minValidDate = Carbon::parse('1980-01-01 00:00:00');\n\n try {\n $date = Carbon::parse($datetime);\n\n if ($minValidDate->gt($date)) {\n return null;\n }\n\n return $date;\n } catch (Exception) {\n return null; // On parse error, treat as null\n }\n }\n\n private function resolveDealProbability(?string $stageProbability): int\n {\n if ($stageProbability === null) {\n return 0;\n }\n\n $probability = (float) $stageProbability;\n\n return $probability > 1 ? 0 : (int) ($probability * 100);\n }\n\n private function resolveForecastCategory(?string $forecastCategory): string\n {\n if (! $forecastCategory) {\n return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;\n }\n\n $forecastCategory = str_replace('_', ' ', $forecastCategory);\n\n return ucwords(strtolower($forecastCategory));\n }\n\n private function importExternalFieldData(array $properties, int $opportunityId): void\n {\n $crmFields = $this->getOpportunitySyncableFields();\n $this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);\n }\n\n private function importOpportunityContacts(Opportunity $opportunity, array $associations): void\n {\n // Handle empty or missing contact associations\n if (empty($associations)) {\n // Remove all existing contact associations if none provided\n $this->removeAllOpportunityContacts($opportunity);\n\n return;\n }\n\n // Use differential sync approach for better performance and accuracy\n $this->syncOpportunityContactsDifferential($opportunity, $associations);\n }\n\n /**\n * Sync opportunity contacts using differential approach\n * This compares current vs new associations and only makes necessary changes\n */\n private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void\n {\n $currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);\n $contactAssociationIds = array_keys($contactAssociations);\n\n $contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);\n $contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);\n\n if (empty($contactsToAdd) && empty($contactsToRemove)) {\n return;\n }\n\n $this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);\n\n $this->removeContactAssociations($opportunity, $contactsToRemove);\n $this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);\n }\n\n private function getCurrentContactCrmIds(Opportunity $opportunity): array\n {\n return $opportunity->contacts()\n ->pluck('contacts.crm_provider_id')\n ->toArray();\n }\n\n private function logContactAssociationChanges(\n Opportunity $opportunity,\n array $currentContactCrmIds,\n array $contactAssociations,\n array $contactsToAdd,\n array $contactsToRemove\n ): void {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [\n 'opportunity_id' => $opportunity->getId(),\n 'current_contacts' => $currentContactCrmIds,\n 'new_contacts' => $contactAssociations,\n 'contacts_to_add' => $contactsToAdd,\n 'contacts_to_remove' => $contactsToRemove,\n ]);\n }\n\n private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void\n {\n if (empty($contactsToRemove)) {\n return;\n }\n\n $contactsToDetach = $opportunity->contacts()\n ->whereIn('contacts.crm_provider_id', $contactsToRemove)\n ->pluck('contacts.id')\n ->toArray();\n\n if (! empty($contactsToDetach)) {\n $opportunity->contacts()->detach($contactsToDetach);\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_contact_crm_ids' => $contactsToRemove,\n 'removed_contact_count' => count($contactsToDetach),\n ]);\n }\n }\n\n private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void\n {\n if (empty($contactsToAdd)) {\n return;\n }\n\n $contactsAdded = [];\n foreach ($contactsToAdd as $crmId) {\n $id = $contactAssociations[$crmId];\n\n if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {\n $contactsAdded[] = $crmId;\n }\n }\n\n $this->logAddedContacts($opportunity, $contactsAdded);\n }\n\n private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);\n\n if (! $contact) {\n return false;\n }\n\n return $this->performContactAttachment($opportunity, $contact, $crmId);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [\n 'opportunity_id' => $opportunity->getId(),\n 'contact_crm_id' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool\n {\n try {\n $opportunity->contacts()->attach($contact->getId(), [\n 'crm_provider_id' => $crmId,\n ]);\n\n return true;\n } catch (\\Illuminate\\Database\\QueryException $e) {\n if (str_contains($e->getMessage(), 'Duplicate entry')) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [\n 'contact_id' => $contact->getId(),\n 'contact_crm_id' => $crmId,\n 'opportunity_id' => $opportunity->getId(),\n ]);\n\n return false;\n }\n\n throw $e;\n }\n }\n\n private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void\n {\n if (! empty($contactsAdded)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'contacts_to_add_count' => count($contactsAdded),\n 'added_contact_crm_ids' => $contactsAdded,\n 'added_contacts_count' => count($contactsAdded),\n ]);\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.48359376,"top":0.08611111,"width":0.01015625,"height":0.016666668},"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.49375,"top":0.08611111,"width":0.01015625,"height":0.016666668},"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.5066406,"top":0.08611111,"width":0.01015625,"height":0.016666668},"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.5167969,"top":0.08611111,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"bounds":{"left":0.5269531,"top":0.08611111,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"bounds":{"left":0.53984374,"top":0.08611111,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"bounds":{"left":0.5527344,"top":0.08611111,"width":0.028515626,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"bounds":{"left":0.5839844,"top":0.08611111,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"bounds":{"left":0.596875,"top":0.08611111,"width":0.034765624,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"bounds":{"left":0.9515625,"top":0.08611111,"width":0.033203125,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.049609374,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"26","depth":4,"bounds":{"left":0.9015625,"top":0.10763889,"width":0.012109375,"height":0.013194445},"role_description":"text"},{"role":"AXStaticText","text":"9","depth":4,"bounds":{"left":0.9160156,"top":0.10763889,"width":0.009375,"height":0.013194445},"role_description":"text"},{"role":"AXStaticText","text":"22","depth":4,"bounds":{"left":0.9277344,"top":0.10763889,"width":0.01171875,"height":0.013194445},"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.9417969,"top":0.10763889,"width":0.009375,"height":0.013194445},"role_description":"text"},{"role":"AXStaticText","text":"103","depth":4,"bounds":{"left":0.95351565,"top":0.10763889,"width":0.0140625,"height":0.013194445},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.96953124,"top":0.10625,"width":0.00859375,"height":0.015972223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.978125,"top":0.10625,"width":0.008203125,"height":0.015972223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
5675544803509266871
|
-8178087410993360602
|
app_switch
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
"podcast_audio_url"
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
0 results
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Code changed:
Hide
Sync Changes
Hide This Notification
32
2
19
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use HubSpot\Client\Crm\Deals\Model\CollectionResponseAssociatedId;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Models\Account;
use Exception;
use Jiminny\Component\DealInsights\Forecast\Forecast;
use Jiminny\Jobs\Crm\MatchActivitiesToNewOpportunity;
use Jiminny\Models\Contact;
use Jiminny\Models\Crm\BusinessProcess;
use Jiminny\Exceptions\CrmException;
use Jiminny\Models\Opportunity;
use Illuminate\Support\Collection;
use Jiminny\Models\Stage;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Crm\Hubspot\DealFieldsService;
use Jiminny\Services\Crm\Hubspot\OpportunitySyncStrategy\HubspotSingleSyncStrategy;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Services\Crm\OpportunitySyncStrategyResolver;
use Jiminny\Utils\CurrencyFormatter;
/**
* Optimized sync methods for better performance
* These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains
*/
trait OpportunitySyncTrait
{
private const int BATCH_SIZE = 100;
private const int BATCH_PROCESS_SIZE = 800;
protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;
protected CrmEntityRepository $crmEntityRepository;
protected DealFieldsService $dealFieldsService;
private ?array $cachedClosedDealStages = null;
private array $cachedBusinessProcesses = [];
private array $cachedStages = [];
public function syncOpportunities(array $parameters, ?string $strategy = null): int
{
$strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);
$parameters['config'] = $this->config;
$syncCount = 0;
$reportedTotal = 0;
$lastSyncedId = [];
try {
foreach ($strategies as $strategyName => $syncStrategy) {
$this->logger->info(
'[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .
$strategyName
);
$total = 0;
$lastId = null;
$buffer = [];
// HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies
foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {
$buffer[] = $hsOpportunity;
// process every 800 rows (fits < 1 000 association limit)
if (\count($buffer) >= self::BATCH_PROCESS_SIZE) {
$syncCount += $this->processOpportunityBatch($buffer);
$buffer = [];
}
}
// leftovers
if ($buffer) {
$syncCount += $this->processOpportunityBatch($buffer);
}
$reportedTotal += $total;
$lastSyncedId = $lastId;
}
} catch (\HubSpot\Client\Crm\Deals\ApiException | CrmException $e) {
$this->handleSyncException($e, $parameters);
}
$this->logger->info(
'[HubSpot] Synced opportunities',
[
'team' => $this->team->getId(),
'sync_count' => $syncCount,
'total' => $reportedTotal,
'last_synced_id' => $lastSyncedId,
]
);
return $reportedTotal;
}
private function handleSyncException(\Throwable $e, array $parameters): void
{
if (($parameters['since'] ?? null) instanceof Carbon) {
$parameters['since'] = $parameters['since']->toDateTimeString();
}
$parameters['config'] = $this->config->getId();
$this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [
'teamId' => $this->team->getUuid(),
'parameters' => $parameters,
'reason' => $e->getMessage(),
]);
}
/**
* @inheritdoc
*/
public function syncOpportunity(string $crmId): ?Opportunity
{
$strategy = $this->opportunitySyncStrategyResolver->resolve(
$this->config,
OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,
);
$parameters = [
'config' => $this->config,
'crm_id' => $crmId,
];
try {
if (! $strategy instanceof HubspotSingleSyncStrategy) {
throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');
}
$hsOpportunity = $strategy->fetchOpportunity($parameters);
} catch (\HubSpot\Client\Crm\Deals\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
$hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);
return $this->importOrUpdateOpportunity($hsOpportunity);
}
/**
* Process webhook-collected opportunity batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportOpportunityBatch jobs for batch processing.
*
* @return int Number of opportunity IDs dispatched to jobs
*/
public function batchSyncOpportunities(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,
$configId
);
}
/**
* Import a batch of opportunities by their CRM IDs.
* Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().
*
* @param array<string> $crmIds HubSpot deal CRM IDs
*
* @return array{success: array, failed_ids: array, errors?: array<string, string>}
*/
public function importOpportunityBatchByIds(array $crmIds): array
{
$fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);
$allDeals = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
$deals = $this->client->getOpportunitiesByIds($chunk, $fields);
foreach ($deals as $deal) {
$allDeals[] = $deal;
}
}
// IDs not returned by HubSpot are likely deleted or inaccessible deals.
// These are not failures — retrying won't bring them back.
$fetchedIds = array_map('strval', array_column($allDeals, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allDeals),
]);
}
if (empty($allDeals)) {
return ['success' => [], 'failed_ids' => []];
}
return $this->importOpportunityBatch($allDeals);
}
private function getClosedDealStages(): array
{
if ($this->cachedClosedDealStages !== null) {
return $this->cachedClosedDealStages;
}
$stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);
$data = [
'lost' => [],
'won' => [],
];
foreach ($stages as $stage) {
if ($stage->probability == 0.00) {
$data['lost'][] = $stage->crm_provider_id;
}
if ($stage->probability == 100.00) {
$data['won'][] = $stage->crm_provider_id;
}
}
$this->cachedClosedDealStages = $data;
return $data;
}
/**
* Import deals into the database with pre-fetched associations.
*
* API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT
* caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()
* where Laravel retries the whole job with backoff. After all retries exhausted,
* failed() requeues all IDs to Redis.
*
* The per-deal loop catches exceptions individually. A deal can end up in three states:
* - success: imported/updated successfully
* - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)
* These are permanent issues — retrying won't fix them.
* - skipped (null): missing dependencies (no account, unknown pipeline/stage).
* This is acceptable — the deal cannot be imported until those exist.
*/
private function importOpportunityBatch(array $deals): array
{
$syncedOpportunities = [
'success' => [],
'failed_ids' => [],
];
$dealIds = array_column($deals, 'id');
// Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the
// queue job retries the whole batch and eventually requeues all deal IDs back to Redis.
try {
$companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');
$contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');
$associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);
$existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(
$this->config,
array_map('strval', $dealIds)
);
$existingCrmIdSet = array_flip($existingCrmIds);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [
'teamId' => $this->team->getId(),
'dealCount' => count($dealIds),
'error' => $e->getMessage(),
]);
throw $e;
}
foreach ($deals as $deal) {
try {
$deal['associations'] = $this->prepareAssociationsForOpportunity(
$deal['id'],
$companyAssociations,
$contactAssociations,
$associationsData
);
$syncedOpportunity = $this->importOrUpdateOpportunity(
$deal,
isset($existingCrmIdSet[(string) $deal['id']])
);
if ($syncedOpportunity) {
$syncedOpportunities['success'][] = $syncedOpportunity;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [
'teamId' => $this->team->getId(),
'crmId' => $deal['id'],
'error' => $e->getMessage(),
]);
$syncedOpportunities['failed_ids'][] = $deal['id'];
$syncedOpportunities['errors'][$deal['id']] = $e->getMessage();
}
}
return $syncedOpportunities;
}
/**
* Prepare associated entities for opportunities with optimized batch processing
* Returns structured data with CRM ID to DB ID mappings for each opportunity
*/
private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array
{
// Step 1: Collect all unique company and contact IDs from associations
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
// Step 2: Batch sync missing entities and get CRM ID to DB ID mappings
$companyIdMappings = [];
$contactIdMappings = [];
if (! empty($allCompanyIds)) {
$companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);
}
if (! empty($allContactIds)) {
$contactIdMappings = $this->prepareAssociatedContacts($allContactIds);
}
return [
'company_id_mappings' => $companyIdMappings,
'contact_id_mappings' => $contactIdMappings,
];
}
/**
* Flatten association data to get unique IDs
*/
private function flattenAssociationIds(array $associations): array
{
$ids = [];
foreach ($associations as $dealAssociations) {
if (is_array($dealAssociations)) {
foreach ($dealAssociations as $id) {
$ids[$id] = true;
}
}
}
return array_keys($ids);
}
/**
* Batch sync missing accounts
*/
private function prepareAssociatedAccounts(array $companyIds): array
{
// Find which accounts already exist
$existingAccounts = $this->crmEntityRepository
->findAccountsByExternalIds($this->config, $companyIds);
$existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();
$existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {
return [$account->getCrmProviderId() => $account->getId()];
})->toArray();
$missingCompanyIds = array_diff($companyIds, $existingCompanyIds);
if (empty($missingCompanyIds)) {
return $existingAccountsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [
'teamId' => $this->team->getUuid(),
'total_companies' => count($companyIds),
'existing_companies' => count($existingCompanyIds),
'missing_companies' => count($missingCompanyIds),
]);
// we already have limit on opportunity ids count
// Initialize variable before try block
$syncedAccountsData = [];
try {
$syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [
'size' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
$syncedAccountsData = [];
}
return $existingAccountsData + $syncedAccountsData;
}
/**
* Prepare associated contacts - find existing and sync missing ones
* Returns mapping of CRM ID to DB ID
*/
private function prepareAssociatedContacts(array $contactIds): array
{
// Find which contacts already exist
$existingContacts = $this->crmEntityRepository
->findContactsByExternalIds($this->config, $contactIds);
$existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();
// Create mapping for existing contacts
$existingContactsData = $existingContacts->mapWithKeys(function ($contact) {
return [$contact->getCrmProviderId() => $contact->getId()];
})->toArray();
$missingContactIds = array_diff($contactIds, $existingContactIds);
if (empty($missingContactIds)) {
return $existingContactsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [
'teamId' => $this->team->getUuid(),
'total_contacts' => count($contactIds),
'existing_contacts' => count($existingContactIds),
'missing_contacts' => count($missingContactIds),
]);
// Sync missing contacts using batch API
try {
$syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [
'size' => count($missingContactIds),
'error' => $e->getMessage(),
]);
$syncedContactsData = [];
}
return $existingContactsData + $syncedContactsData;
}
private function batchSyncCrmObjects(string $objectType, array $crmIds): array
{
$syncObjects = [];
$crmObjectIds = array_values($crmIds);
foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {
try {
$objects = $objectType === 'companies' ?
$this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :
$this->client->getContactsByIds($chunk, $this->getContactFields());
foreach ($objects as $objectId => $objectData) {
$this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [
'requested_count' => count($chunk),
'synced_count' => count($objects),
]);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [
'ids' => $chunk,
'error' => $e->getMessage(),
]);
}
}
return $syncObjects;
}
private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void
{
try {
$object = $objectType === 'companies' ?
$this->importAccount($objectData) :
$this->importContact($objectData);
if ($object) {
$syncObjects[$object->getCrmProviderId()] = $object->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [
'id' => $objectId,
'error' => $e->getMessage(),
]);
}
}
/**
* Prepare associations for a single opportunity
*
* The return value is an array with the following structure:
* [
* 'companies' => [
* $companyCrmId => $companyId,
* ...
* ],
* 'contacts' => [
* $contactCrmId => $contactId,
* ...
* ],
* 'account_id' => $accountId,
* ]
*/
private function prepareAssociationsForOpportunity(
string $oppCrmId,
array $companyAssociations,
array $contactAssociations,
array $associationsData
): array {
$associations = [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
$oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];
foreach ($oppCompanyIds as $companyCrmId) {
if (isset($associationsData['company_id_mappings'][$companyCrmId])) {
$associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];
// Set primary account (first company becomes primary account)
if ($associations['account_id'] === null) {
$associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];
}
}
}
$oppContactIds = $contactAssociations[$oppCrmId] ?? [];
foreach ($oppContactIds as $contactCrmId) {
if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {
$associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];
}
}
return $associations;
}
/**
* Update only associations for an opportunity
*/
private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void
{
// Update contact associations
$this->importOpportunityContacts($opportunity, $associations['contacts']);
// Update company (account) associations
$this->updateOpportunityAccount($opportunity, $associations['account_id']);
}
/**
* Remove all contact associations from an opportunity
*/
private function removeAllOpportunityContacts(Opportunity $opportunity): void
{
$currentCount = (int) $opportunity->contacts()->count();
if ($currentCount > 0) {
$opportunity->contacts()->detach();
$this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_count' => $currentCount,
]);
}
}
private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void
{
if ($accountId === null) {
// No account ID provided - keep current account
return;
}
$currentAccountId = $opportunity->getAccountId();
// Only update if account has changed
if ($currentAccountId !== $accountId) {
$opportunity->account_id = $accountId;
$opportunity->save();
$this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [
'opportunity_id' => $opportunity->getId(),
'old_account_id' => $currentAccountId,
'new_account_id' => $accountId,
]);
}
}
/**
* Find existing opportunities by external IDs (OPTIMIZED VERSION)
* Uses batch query for better performance
*/
private function findExistingOpportunities(array $crmIds): Collection
{
return $this->crmEntityRepository
->findOpportunitiesByExternalIds($this->config, $crmIds);
}
private function processOpportunityBatch(array $opportunities): int
{
$syncedOpportunities = $this->importOpportunityBatch($opportunities);
return count($syncedOpportunities['success'] ?? []);
}
/**
* Convert single deal associations from HubSpot format to internal format
* Handles both HubSpot SDK objects and array formats
*
* @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed
*
* @return array Processed associations with DB IDs
*/
private function convertDealAssociations(array $opportunityAssociations): array
{
$associations = $this->initializeAssociationsStructure();
if (empty($opportunityAssociations)) {
return $associations;
}
$associationIds = $this->extractAssociationIds($opportunityAssociations);
$this->processCompanyAssociations($associationIds, $associations);
$this->processContactAssociations($associationIds, $associations);
return $associations;
}
private function initializeAssociationsStructure(): array
{
return [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
}
private function extractAssociationIds(array $opportunityAssociations): array
{
$associationIds = [];
foreach ($opportunityAssociations as $type => $associationData) {
if (! empty($associationData)) {
$associationIds[$type] = $this->convertSingleDealAssociations($associationData);
}
}
return $associationIds;
}
private function processCompanyAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['companies'])) {
return;
}
$companyId = $associationIds['companies'][0];
$account = $this->findOrSyncAccount($companyId);
if ($account instanceof Account) {
$associations['companies'][$companyId] = $account->getId();
$associations['account_id'] = $account->getId();
}
}
private function processContactAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['contacts'])) {
return;
}
foreach ($associationIds['contacts'] as $contactId) {
$contact = $this->findOrSyncContact($contactId);
if ($contact instanceof Contact) {
$associations['contacts'][$contactId] = $contact->getId();
}
}
}
private function findOrSyncAccount(string $companyId): ?Account
{
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);
if (! $account instanceof Account) {
$account = $this->syncAccount($companyId);
}
return $account;
}
private function findOrSyncContact(string $contactId): ?Contact
{
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);
if (! $contact instanceof Contact) {
$contact = $this->syncContact($contactId);
}
return $contact;
}
private function convertSingleDealAssociations($opportunityAssociations = null): array
{
$associationData = [];
if ($opportunityAssociations === null) {
return $associationData;
}
// Handle array input (from extractAssociationIds)
if (is_array($opportunityAssociations)) {
return $opportunityAssociations;
}
// Handle CollectionResponseAssociatedId object
if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {
foreach ($opportunityAssociations->getResults() as $association) {
$associationData[] = $association->getId();
}
}
return $associationData;
}
private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity
{
if (empty($crmData['properties'])) {
return null;
}
$crmId = (string) $crmData['id'];
$properties = $crmData['properties'];
$associations = $crmData['associations'] ?? [];
$opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(
$this->config,
$crmId
);
if ($opportunityExists) {
return $this->updateOpportunity($crmId, $properties, $associations);
} else {
return $this->createOpportunity($crmId, $properties, $associations);
}
}
/**
* Create new opportunity
*/
private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity
{
$accountId = $this->resolveAccountId($associations);
if (! $accountId) {
return null;
}
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
if (! $businessProcess) {
return null;
}
$stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);
if (! $stage) {
return null;
}
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->importOpportunityContacts($opportunity, $associations['contacts']);
if ($opportunity->wasRecentlyCreated) {
MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());
}
return $opportunity;
}
/**
* Update existing opportunity
*/
private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity
{
$accountId = $this->resolveAccountId($associations);
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
$stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->updateOpportunityAssociations($opportunity, $associations);
return $opportunity;
}
private function resolveAccountId(array $associations): ?int
{
if (! empty($associations['accountId'])) {
return $associations['accountId'];
}
if (empty($associations)) {
return null;
}
// we can't resolve multiple account ids (currently SDK returns one company)
foreach ($associations['companies'] as $accountId) {
return $accountId;
}
return null;
}
private function buildOpportunityData(
array $properties,
?int $accountId,
?BusinessProcess $businessProcess,
?Stage $stage
): array {
$ownerId = null;
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$name = 'Unknown';
if (isset($properties['dealname'])) {
$name = mb_strimwidth($properties['dealname'], 0, 128);
}
$amount = $this->resolveAmount($properties);
$currency = $properties['deal_currency_code'] ?? null;
$closeDate = null;
if (! empty($properties['closedate'])) {
$closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');
}
$remotelyCreatedAt = null;
if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {
$date = $this->parseCleanDatetime($properties['createdate']);
$remotelyCreatedAt = $date?->format('Y-m-d H:i:s');
}
$closedStages = $this->getClosedDealStages();
$isWon = in_array($properties['dealstage'], $closedStages['won']);
$isLost = in_array($properties['dealstage'], $closedStages['lost']);
$data = [
'team_id' => $this->team->getId(),
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => $name,
'value' => ! empty($amount) ? $amount : null,
'currency_code' => CurrencyFormatter::formatCode($currency),
'close_date' => $closeDate,
'is_closed' => $isWon || $isLost,
'is_won' => $isWon,
'remotely_created_at' => $remotelyCreatedAt,
'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),
'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),
];
if ($accountId) {
$data['account_id'] = $accountId;
}
if ($stage) {
$data['stage_id'] = $stage->id;
}
if ($businessProcess) {
$recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);
if ($recordType) {
$data['record_type_id'] = $recordType->id;
}
}
return $data;
}
private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess
{
if ($pipelineId === null) {
return null;
}
if (isset($this->cachedBusinessProcesses[$pipelineId])) {
return $this->cachedBusinessProcesses[$pipelineId];
}
$businessProcess = $this->getBusinessProcess($pipelineId);
if (! $businessProcess instanceof BusinessProcess) {
$this->importStages();
$businessProcess = $this->getBusinessProcess($pipelineId);
}
if (! $businessProcess instanceof BusinessProcess) {
$this->logger->info(
'[HubSpot] Deal is not attached to a pipeline',
[
'pipeline' => $pipelineId]
);
}
$this->cachedBusinessProcesses[$pipelineId] = $businessProcess;
return $businessProcess;
}
private function getBusinessProcess(string $pipelineId): ?BusinessProcess
{
return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);
}
private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage
{
if (empty($stageId)) {
return null;
}
$cacheKey = $businessProcess->getId() . ':' . $stageId;
if (isset($this->cachedStages[$cacheKey])) {
return $this->cachedStages[$cacheKey];
}
$stage = $this->crmEntityRepository->getPipelineStageByConditions(
$businessProcess,
[
'crm_provider_id' => $stageId,
'type' => Stage::TYPE_OPPORTUNITY,
]
);
if ($stage === null) {
$this->importStages(null, $stageId);
}
if ($stage === null) {
$this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);
}
$this->cachedStages[$cacheKey] = $stage;
return $stage;
}
private function resolveAmount(array $properties): ?string
{
$amount = null;
if (! empty($properties['amount'])) {
$amount = str_replace(',', '', $properties['amount']);
}
if ($this->config->hasDefaultCurrencyFieldSet()) {
$valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();
$amount = $properties[$valueFieldName] ?? $amount;
}
return $amount;
}
private function parseCleanDatetime(string $datetime): ?Carbon
{
// Treat pre-1980 values as invalid
$minValidDate = Carbon::parse('1980-01-01 00:00:00');
try {
$date = Carbon::parse($datetime);
if ($minValidDate->gt($date)) {
return null;
}
return $date;
} catch (Exception) {
return null; // On parse error, treat as null
}
}
private function resolveDealProbability(?string $stageProbability): int
{
if ($stageProbability === null) {
return 0;
}
$probability = (float) $stageProbability;
return $probability > 1 ? 0 : (int) ($probability * 100);
}
private function resolveForecastCategory(?string $forecastCategory): string
{
if (! $forecastCategory) {
return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;
}
$forecastCategory = str_replace('_', ' ', $forecastCategory);
return ucwords(strtolower($forecastCategory));
}
private function importExternalFieldData(array $properties, int $opportunityId): void
{
$crmFields = $this->getOpportunitySyncableFields();
$this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);
}
private function importOpportunityContacts(Opportunity $opportunity, array $associations): void
{
// Handle empty or missing contact associations
if (empty($associations)) {
// Remove all existing contact associations if none provided
$this->removeAllOpportunityContacts($opportunity);
return;
}
// Use differential sync approach for better performance and accuracy
$this->syncOpportunityContactsDifferential($opportunity, $associations);
}
/**
* Sync opportunity contacts using differential approach
* This compares current vs new associations and only makes necessary changes
*/
private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void
{
$currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);
$contactAssociationIds = array_keys($contactAssociations);
$contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);
$contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);
if (empty($contactsToAdd) && empty($contactsToRemove)) {
return;
}
$this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);
$this->removeContactAssociations($opportunity, $contactsToRemove);
$this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);
}
private function getCurrentContactCrmIds(Opportunity $opportunity): array
{
return $opportunity->contacts()
->pluck('contacts.crm_provider_id')
->toArray();
}
private function logContactAssociationChanges(
Opportunity $opportunity,
array $currentContactCrmIds,
array $contactAssociations,
array $contactsToAdd,
array $contactsToRemove
): void {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [
'opportunity_id' => $opportunity->getId(),
'current_contacts' => $currentContactCrmIds,
'new_contacts' => $contactAssociations,
'contacts_to_add' => $contactsToAdd,
'contacts_to_remove' => $contactsToRemove,
]);
}
private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void
{
if (empty($contactsToRemove)) {
return;
}
$contactsToDetach = $opportunity->contacts()
->whereIn('contacts.crm_provider_id', $contactsToRemove)
->pluck('contacts.id')
->toArray();
if (! empty($contactsToDetach)) {
$opportunity->contacts()->detach($contactsToDetach);
$this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_contact_crm_ids' => $contactsToRemove,
'removed_contact_count' => count($contactsToDetach),
]);
}
}
private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void
{
if (empty($contactsToAdd)) {
return;
}
$contactsAdded = [];
foreach ($contactsToAdd as $crmId) {
$id = $contactAssociations[$crmId];
if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {
$contactsAdded[] = $crmId;
}
}
$this->logAddedContacts($opportunity, $contactsAdded);
}
private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool
{
try {
$contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);
if (! $contact) {
return false;
}
return $this->performContactAttachment($opportunity, $contact, $crmId);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [
'opportunity_id' => $opportunity->getId(),
'contact_crm_id' => $crmId,
'error' => $e->getMessage(),
]);
return false;
}
}
private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool
{
try {
$opportunity->contacts()->attach($contact->getId(), [
'crm_provider_id' => $crmId,
]);
return true;
} catch (\Illuminate\Database\QueryException $e) {
if (str_contains($e->getMessage(), 'Duplicate entry')) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [
'contact_id' => $contact->getId(),
'contact_crm_id' => $crmId,
'opportunity_id' => $opportunity->getId(),
]);
return false;
}
throw $e;
}
}
private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void
{
if (! empty($contactsAdded)) {
$this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [
'opportunity_id' => $opportunity->getId(),
'contacts_to_add_count' => count($contactsAdded),
'added_contact_crm_ids' => $contactsAdded,
'added_contacts_count' => count($contactsAdded),
]);
}
}
}
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
26
9
22
3
103
Previous Highlighted Error
Next Highlighted Error...
|
NULL
|
|
43285
|
921
|
47
|
2026-04-17T08:00:28.379237+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776412828379_m2.jpg...
|
PhpStorm
|
NULL
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
1TUnAJoado 00 08AaTunauote 0ee1TUnqJoado 00 0AaTun 1TUnAJoado 00 08AaTunauote 0ee1TUnqJoado 00 0AaTuniuode mee1TUnqJoado 00 S6-runooALTUIODNOлadл2pauantianauar -ornrnoafoud tawsesAoeaun sane ma penmeoear saeseoanAeuun sanes ni pe1501 pasoeUOM pasOlaOanoH09H elauarsletosewMauad 0t algelteny1502 pasoladn nas noafouduoM pasolasletogedse ssoulsiltKaTlrqegod coA ASOLZ:00:LL Idy LL|J •8C4 %00Lnal SMol 771s01 pasoneUOM pAsonasletogewesмauad 01 оlдelтелyA501 pasoladn nes 1oapouuoM pasolaan-ts sopunsletouawe9TS967SLTT L9S9T99489901 295898789SS L95SYCO0--00Cь -908-2465-2097-09641C40 109ST.0809-X8-1105-2000- 260216 29T9LT9SZLT L9SSLT9SZLT L9SELT9SZLT L9STLS9L70T L9SZLT9SZLT L9STLT9SZLT L9SOLT9SZLT L9Sconnaconalvoc21Z10677 L98palopayosonaenaesane comi8+0-PÇ08-5887-1L0ITE6Sb6976976976976976S76976976976Sb6976976976Sb6976976SZ65701 Clpan NTOл'**esIIewа"py aldeyaos es→ pummommame mno'*"es"tewa'nA punoloeidл OnH *xIaoral alosuosrБорелелеаадотI=E6STIIIZ6ST =T6STO6ST68ST88STL8STORCTAIS8ST78ST28STZ8STVELX ZTZEEcuc"allonuonstocaruulwicystdydsepiingeas0800 %pe7e e ega6-00a-0a9 120e708 19193S86006PS0500-2206-822-296S-TETLSJTS L989T04967009000-9442-447-908 -900694499ĐĐ221815961-TV6-Q92-039)-B560 TTĐT•8L00-0024-664-4247-0700-8707472007274X60441640-4448-4979-9029-7699449 697940.676829582J2- p9ee-A811-T901 M010065T 25291esuos fISPpytqpaZLaelz-9esteeGSS9T6799Tsu csss7 aosuos 26320 €eaddens tomn tn pe tnnapaandano g[naliaF(suo,aenoooses .famere.e.fpenetoesttenseddesepdes-stussF(OpIaale-Aamunauogdoe1fam a%eme.freaalanneesasesed.ss-stttsST87T8£T8ZT8TT8AT.OKARIAIANION TEIHLEYOOHCEM AIN"OTINNC <-OT HOTO NO.Ad15el5sunsal ai"un oipne iseopod•uc'asiwacyoeqlenstoca oarewornyalexe Nwl•u sastasltl•Ud"IoR dudlAies YOOHE-M ToNeC NITW1 1oajoladjaнмopuiмS00lnuulul-ise-sho0o.-ooewo.ne-o068 uo ro8lljanesenepogAebinenMAIA4P3wionstet...
|
NULL
|
2481057895470113109
|
NULL
|
app_switch
|
ocr
|
NULL
|
1TUnAJoado 00 08AaTunauote 0ee1TUnqJoado 00 0AaTun 1TUnAJoado 00 08AaTunauote 0ee1TUnqJoado 00 0AaTuniuode mee1TUnqJoado 00 S6-runooALTUIODNOлadл2pauantianauar -ornrnoafoud tawsesAoeaun sane ma penmeoear saeseoanAeuun sanes ni pe1501 pasoeUOM pasOlaOanoH09H elauarsletosewMauad 0t algelteny1502 pasoladn nas noafouduoM pasolasletogedse ssoulsiltKaTlrqegod coA ASOLZ:00:LL Idy LL|J •8C4 %00Lnal SMol 771s01 pasoneUOM pAsonasletogewesмauad 01 оlдelтелyA501 pasoladn nes 1oapouuoM pasolaan-ts sopunsletouawe9TS967SLTT L9S9T99489901 295898789SS L95SYCO0--00Cь -908-2465-2097-09641C40 109ST.0809-X8-1105-2000- 260216 29T9LT9SZLT L9SSLT9SZLT L9SELT9SZLT L9STLS9L70T L9SZLT9SZLT L9STLT9SZLT L9SOLT9SZLT L9Sconnaconalvoc21Z10677 L98palopayosonaenaesane comi8+0-PÇ08-5887-1L0ITE6Sb6976976976976976S76976976976Sb6976976976Sb6976976SZ65701 Clpan NTOл'**esIIewа"py aldeyaos es→ pummommame mno'*"es"tewa'nA punoloeidл OnH *xIaoral alosuosrБорелелеаадотI=E6STIIIZ6ST =T6STO6ST68ST88STL8STORCTAIS8ST78ST28STZ8STVELX ZTZEEcuc"allonuonstocaruulwicystdydsepiingeas0800 %pe7e e ega6-00a-0a9 120e708 19193S86006PS0500-2206-822-296S-TETLSJTS L989T04967009000-9442-447-908 -900694499ĐĐ221815961-TV6-Q92-039)-B560 TTĐT•8L00-0024-664-4247-0700-8707472007274X60441640-4448-4979-9029-7699449 697940.676829582J2- p9ee-A811-T901 M010065T 25291esuos fISPpytqpaZLaelz-9esteeGSS9T6799Tsu csss7 aosuos 26320 €eaddens tomn tn pe tnnapaandano g[naliaF(suo,aenoooses .famere.e.fpenetoesttenseddesepdes-stussF(OpIaale-Aamunauogdoe1fam a%eme.freaalanneesasesed.ss-stttsST87T8£T8ZT8TT8AT.OKARIAIANION TEIHLEYOOHCEM AIN"OTINNC <-OT HOTO NO.Ad15el5sunsal ai"un oipne iseopod•uc'asiwacyoeqlenstoca oarewornyalexe Nwl•u sastasltl•Ud"IoR dudlAies YOOHE-M ToNeC NITW1 1oajoladjaнмopuiмS00lnuulul-ise-sho0o.-ooewo.ne-o068 uo ro8lljanesenepogAebinenMAIA4P3wionstet...
|
NULL
|
|
43324
|
922
|
13
|
2026-04-17T08:03:18.934240+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776412998934_m1.jpg...
|
PhpStorm
|
faVsco.js – OpportunitySyncTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
"podcast_audio_url"
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
0 results
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Code changed:
Hide
Sync Changes
Hide This Notification
32
2
19
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use HubSpot\Client\Crm\Deals\Model\CollectionResponseAssociatedId;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Models\Account;
use Exception;
use Jiminny\Component\DealInsights\Forecast\Forecast;
use Jiminny\Jobs\Crm\MatchActivitiesToNewOpportunity;
use Jiminny\Models\Contact;
use Jiminny\Models\Crm\BusinessProcess;
use Jiminny\Exceptions\CrmException;
use Jiminny\Models\Opportunity;
use Illuminate\Support\Collection;
use Jiminny\Models\Stage;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Crm\Hubspot\DealFieldsService;
use Jiminny\Services\Crm\Hubspot\OpportunitySyncStrategy\HubspotSingleSyncStrategy;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Services\Crm\OpportunitySyncStrategyResolver;
use Jiminny\Utils\CurrencyFormatter;
/**
* Optimized sync methods for better performance
* These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains
*/
trait OpportunitySyncTrait
{
private const int BATCH_SIZE = 100;
private const int BATCH_PROCESS_SIZE = 800;
protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;
protected CrmEntityRepository $crmEntityRepository;
protected DealFieldsService $dealFieldsService;
private ?array $cachedClosedDealStages = null;
private array $cachedBusinessProcesses = [];
private array $cachedStages = [];
public function syncOpportunities(array $parameters, ?string $strategy = null): int
{
$strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);
$parameters['config'] = $this->config;
$syncCount = 0;
$reportedTotal = 0;
$lastSyncedId = [];
try {
foreach ($strategies as $strategyName => $syncStrategy) {
$this->logger->info(
'[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .
$strategyName
);
$total = 0;
$lastId = null;
$buffer = [];
// HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies
foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {
$buffer[] = $hsOpportunity;
// process every 800 rows (fits < 1 000 association limit)
if (\count($buffer) >= self::BATCH_PROCESS_SIZE) {
$syncCount += $this->processOpportunityBatch($buffer);
$buffer = [];
}
}
// leftovers
if ($buffer) {
$syncCount += $this->processOpportunityBatch($buffer);
}
$reportedTotal += $total;
$lastSyncedId = $lastId;
}
} catch (\HubSpot\Client\Crm\Deals\ApiException | CrmException $e) {
$this->handleSyncException($e, $parameters);
}
$this->logger->info(
'[HubSpot] Synced opportunities',
[
'team' => $this->team->getId(),
'sync_count' => $syncCount,
'total' => $reportedTotal,
'last_synced_id' => $lastSyncedId,
]
);
return $reportedTotal;
}
private function handleSyncException(\Throwable $e, array $parameters): void
{
if (($parameters['since'] ?? null) instanceof Carbon) {
$parameters['since'] = $parameters['since']->toDateTimeString();
}
$parameters['config'] = $this->config->getId();
$this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [
'teamId' => $this->team->getUuid(),
'parameters' => $parameters,
'reason' => $e->getMessage(),
]);
}
/**
* @inheritdoc
*/
public function syncOpportunity(string $crmId): ?Opportunity
{
$strategy = $this->opportunitySyncStrategyResolver->resolve(
$this->config,
OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,
);
$parameters = [
'config' => $this->config,
'crm_id' => $crmId,
];
try {
if (! $strategy instanceof HubspotSingleSyncStrategy) {
throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');
}
$hsOpportunity = $strategy->fetchOpportunity($parameters);
} catch (\HubSpot\Client\Crm\Deals\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
$hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);
return $this->importOrUpdateOpportunity($hsOpportunity);
}
/**
* Process webhook-collected opportunity batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportOpportunityBatch jobs for batch processing.
*
* @return int Number of opportunity IDs dispatched to jobs
*/
public function batchSyncOpportunities(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,
$configId
);
}
/**
* Import a batch of opportunities by their CRM IDs.
* Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().
*
* @param array<string> $crmIds HubSpot deal CRM IDs
*
* @return array{success: array, failed_ids: array, errors?: array<string, string>}
*/
public function importOpportunityBatchByIds(array $crmIds): array
{
$fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);
$allDeals = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
$deals = $this->client->getOpportunitiesByIds($chunk, $fields);
foreach ($deals as $deal) {
$allDeals[] = $deal;
}
}
// IDs not returned by HubSpot are likely deleted or inaccessible deals.
// These are not failures — retrying won't bring them back.
$fetchedIds = array_map('strval', array_column($allDeals, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allDeals),
]);
}
if (empty($allDeals)) {
return ['success' => [], 'failed_ids' => []];
}
return $this->importOpportunityBatch($allDeals);
}
private function getClosedDealStages(): array
{
if ($this->cachedClosedDealStages !== null) {
return $this->cachedClosedDealStages;
}
$stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);
$data = [
'lost' => [],
'won' => [],
];
foreach ($stages as $stage) {
if ($stage->probability == 0.00) {
$data['lost'][] = $stage->crm_provider_id;
}
if ($stage->probability == 100.00) {
$data['won'][] = $stage->crm_provider_id;
}
}
$this->cachedClosedDealStages = $data;
return $data;
}
/**
* Import deals into the database with pre-fetched associations.
*
* API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT
* caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()
* where Laravel retries the whole job with backoff. After all retries exhausted,
* failed() requeues all IDs to Redis.
*
* The per-deal loop catches exceptions individually. A deal can end up in three states:
* - success: imported/updated successfully
* - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)
* These are permanent issues — retrying won't fix them.
* - skipped (null): missing dependencies (no account, unknown pipeline/stage).
* This is acceptable — the deal cannot be imported until those exist.
*/
private function importOpportunityBatch(array $deals): array
{
$syncedOpportunities = [
'success' => [],
'failed_ids' => [],
];
$dealIds = array_column($deals, 'id');
// Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the
// queue job retries the whole batch and eventually requeues all deal IDs back to Redis.
try {
$companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');
$contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');
$associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);
$existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(
$this->config,
array_map('strval', $dealIds)
);
$existingCrmIdSet = array_flip($existingCrmIds);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [
'teamId' => $this->team->getId(),
'dealCount' => count($dealIds),
'error' => $e->getMessage(),
]);
throw $e;
}
foreach ($deals as $deal) {
try {
$deal['associations'] = $this->prepareAssociationsForOpportunity(
$deal['id'],
$companyAssociations,
$contactAssociations,
$associationsData
);
$syncedOpportunity = $this->importOrUpdateOpportunity(
$deal,
isset($existingCrmIdSet[(string) $deal['id']])
);
if ($syncedOpportunity) {
$syncedOpportunities['success'][] = $syncedOpportunity;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [
'teamId' => $this->team->getId(),
'crmId' => $deal['id'],
'error' => $e->getMessage(),
]);
$syncedOpportunities['failed_ids'][] = $deal['id'];
$syncedOpportunities['errors'][$deal['id']] = $e->getMessage();
}
}
return $syncedOpportunities;
}
/**
* Prepare associated entities for opportunities with optimized batch processing
* Returns structured data with CRM ID to DB ID mappings for each opportunity
*/
private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array
{
// Step 1: Collect all unique company and contact IDs from associations
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
// Step 2: Batch sync missing entities and get CRM ID to DB ID mappings
$companyIdMappings = [];
$contactIdMappings = [];
if (! empty($allCompanyIds)) {
$companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);
}
if (! empty($allContactIds)) {
$contactIdMappings = $this->prepareAssociatedContacts($allContactIds);
}
return [
'company_id_mappings' => $companyIdMappings,
'contact_id_mappings' => $contactIdMappings,
];
}
/**
* Flatten association data to get unique IDs
*/
private function flattenAssociationIds(array $associations): array
{
$ids = [];
foreach ($associations as $dealAssociations) {
if (is_array($dealAssociations)) {
foreach ($dealAssociations as $id) {
$ids[$id] = true;
}
}
}
return array_keys($ids);
}
/**
* Batch sync missing accounts
*/
private function prepareAssociatedAccounts(array $companyIds): array
{
// Find which accounts already exist
$existingAccounts = $this->crmEntityRepository
->findAccountsByExternalIds($this->config, $companyIds);
$existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();
$existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {
return [$account->getCrmProviderId() => $account->getId()];
})->toArray();
$missingCompanyIds = array_diff($companyIds, $existingCompanyIds);
if (empty($missingCompanyIds)) {
return $existingAccountsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [
'teamId' => $this->team->getUuid(),
'total_companies' => count($companyIds),
'existing_companies' => count($existingCompanyIds),
'missing_companies' => count($missingCompanyIds),
]);
// we already have limit on opportunity ids count
// Initialize variable before try block
$syncedAccountsData = [];
try {
$syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [
'size' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
$syncedAccountsData = [];
}
return $existingAccountsData + $syncedAccountsData;
}
/**
* Prepare associated contacts - find existing and sync missing ones
* Returns mapping of CRM ID to DB ID
*/
private function prepareAssociatedContacts(array $contactIds): array
{
// Find which contacts already exist
$existingContacts = $this->crmEntityRepository
->findContactsByExternalIds($this->config, $contactIds);
$existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();
// Create mapping for existing contacts
$existingContactsData = $existingContacts->mapWithKeys(function ($contact) {
return [$contact->getCrmProviderId() => $contact->getId()];
})->toArray();
$missingContactIds = array_diff($contactIds, $existingContactIds);
if (empty($missingContactIds)) {
return $existingContactsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [
'teamId' => $this->team->getUuid(),
'total_contacts' => count($contactIds),
'existing_contacts' => count($existingContactIds),
'missing_contacts' => count($missingContactIds),
]);
// Sync missing contacts using batch API
try {
$syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [
'size' => count($missingContactIds),
'error' => $e->getMessage(),
]);
$syncedContactsData = [];
}
return $existingContactsData + $syncedContactsData;
}
private function batchSyncCrmObjects(string $objectType, array $crmIds): array
{
$syncObjects = [];
$crmObjectIds = array_values($crmIds);
foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {
try {
$objects = $objectType === 'companies' ?
$this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :
$this->client->getContactsByIds($chunk, $this->getContactFields());
foreach ($objects as $objectId => $objectData) {
$this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [
'requested_count' => count($chunk),
'synced_count' => count($objects),
]);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [
'ids' => $chunk,
'error' => $e->getMessage(),
]);
}
}
return $syncObjects;
}
private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void
{
try {
$object = $objectType === 'companies' ?
$this->importAccount($objectData) :
$this->importContact($objectData);
if ($object) {
$syncObjects[$object->getCrmProviderId()] = $object->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [
'id' => $objectId,
'error' => $e->getMessage(),
]);
}
}
/**
* Prepare associations for a single opportunity
*
* The return value is an array with the following structure:
* [
* 'companies' => [
* $companyCrmId => $companyId,
* ...
* ],
* 'contacts' => [
* $contactCrmId => $contactId,
* ...
* ],
* 'account_id' => $accountId,
* ]
*/
private function prepareAssociationsForOpportunity(
string $oppCrmId,
array $companyAssociations,
array $contactAssociations,
array $associationsData
): array {
$associations = [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
$oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];
foreach ($oppCompanyIds as $companyCrmId) {
if (isset($associationsData['company_id_mappings'][$companyCrmId])) {
$associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];
// Set primary account (first company becomes primary account)
if ($associations['account_id'] === null) {
$associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];
}
}
}
$oppContactIds = $contactAssociations[$oppCrmId] ?? [];
foreach ($oppContactIds as $contactCrmId) {
if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {
$associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];
}
}
return $associations;
}
/**
* Update only associations for an opportunity
*/
private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void
{
// Update contact associations
$this->importOpportunityContacts($opportunity, $associations['contacts']);
// Update company (account) associations
$this->updateOpportunityAccount($opportunity, $associations['account_id']);
}
/**
* Remove all contact associations from an opportunity
*/
private function removeAllOpportunityContacts(Opportunity $opportunity): void
{
$currentCount = (int) $opportunity->contacts()->count();
if ($currentCount > 0) {
$opportunity->contacts()->detach();
$this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_count' => $currentCount,
]);
}
}
private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void
{
if ($accountId === null) {
// No account ID provided - keep current account
return;
}
$currentAccountId = $opportunity->getAccountId();
// Only update if account has changed
if ($currentAccountId !== $accountId) {
$opportunity->account_id = $accountId;
$opportunity->save();
$this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [
'opportunity_id' => $opportunity->getId(),
'old_account_id' => $currentAccountId,
'new_account_id' => $accountId,
]);
}
}
/**
* Find existing opportunities by external IDs (OPTIMIZED VERSION)
* Uses batch query for better performance
*/
private function findExistingOpportunities(array $crmIds): Collection
{
return $this->crmEntityRepository
->findOpportunitiesByExternalIds($this->config, $crmIds);
}
private function processOpportunityBatch(array $opportunities): int
{
$syncedOpportunities = $this->importOpportunityBatch($opportunities);
return count($syncedOpportunities['success'] ?? []);
}
/**
* Convert single deal associations from HubSpot format to internal format
* Handles both HubSpot SDK objects and array formats
*
* @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed
*
* @return array Processed associations with DB IDs
*/
private function convertDealAssociations(array $opportunityAssociations): array
{
$associations = $this->initializeAssociationsStructure();
if (empty($opportunityAssociations)) {
return $associations;
}
$associationIds = $this->extractAssociationIds($opportunityAssociations);
$this->processCompanyAssociations($associationIds, $associations);
$this->processContactAssociations($associationIds, $associations);
return $associations;
}
private function initializeAssociationsStructure(): array
{
return [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
}
private function extractAssociationIds(array $opportunityAssociations): array
{
$associationIds = [];
foreach ($opportunityAssociations as $type => $associationData) {
if (! empty($associationData)) {
$associationIds[$type] = $this->convertSingleDealAssociations($associationData);
}
}
return $associationIds;
}
private function processCompanyAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['companies'])) {
return;
}
$companyId = $associationIds['companies'][0];
$account = $this->findOrSyncAccount($companyId);
if ($account instanceof Account) {
$associations['companies'][$companyId] = $account->getId();
$associations['account_id'] = $account->getId();
}
}
private function processContactAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['contacts'])) {
return;
}
foreach ($associationIds['contacts'] as $contactId) {
$contact = $this->findOrSyncContact($contactId);
if ($contact instanceof Contact) {
$associations['contacts'][$contactId] = $contact->getId();
}
}
}
private function findOrSyncAccount(string $companyId): ?Account
{
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);
if (! $account instanceof Account) {
$account = $this->syncAccount($companyId);
}
return $account;
}
private function findOrSyncContact(string $contactId): ?Contact
{
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);
if (! $contact instanceof Contact) {
$contact = $this->syncContact($contactId);
}
return $contact;
}
private function convertSingleDealAssociations($opportunityAssociations = null): array
{
$associationData = [];
if ($opportunityAssociations === null) {
return $associationData;
}
// Handle array input (from extractAssociationIds)
if (is_array($opportunityAssociations)) {
return $opportunityAssociations;
}
// Handle CollectionResponseAssociatedId object
if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {
foreach ($opportunityAssociations->getResults() as $association) {
$associationData[] = $association->getId();
}
}
return $associationData;
}
private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity
{
if (empty($crmData['properties'])) {
return null;
}
$crmId = (string) $crmData['id'];
$properties = $crmData['properties'];
$associations = $crmData['associations'] ?? [];
$opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(
$this->config,
$crmId
);
if ($opportunityExists) {
return $this->updateOpportunity($crmId, $properties, $associations);
} else {
return $this->createOpportunity($crmId, $properties, $associations);
}
}
/**
* Create new opportunity
*/
private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity
{
$accountId = $this->resolveAccountId($associations);
if (! $accountId) {
return null;
}
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
if (! $businessProcess) {
return null;
}
$stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);
if (! $stage) {
return null;
}
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->importOpportunityContacts($opportunity, $associations['contacts']);
if ($opportunity->wasRecentlyCreated) {
MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());
}
return $opportunity;
}
/**
* Update existing opportunity
*/
private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity
{
$accountId = $this->resolveAccountId($associations);
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
$stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->updateOpportunityAssociations($opportunity, $associations);
return $opportunity;
}
private function resolveAccountId(array $associations): ?int
{
if (! empty($associations['accountId'])) {
return $associations['accountId'];
}
if (empty($associations)) {
return null;
}
// we can't resolve multiple account ids (currently SDK returns one company)
foreach ($associations['companies'] as $accountId) {
return $accountId;
}
return null;
}
private function buildOpportunityData(
array $properties,
?int $accountId,
?BusinessProcess $businessProcess,
?Stage $stage
): array {
$ownerId = null;
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$name = 'Unknown';
if (isset($properties['dealname'])) {
$name = mb_strimwidth($properties['dealname'], 0, 128);
}
$amount = $this->resolveAmount($properties);
$currency = $properties['deal_currency_code'] ?? null;
$closeDate = null;
if (! empty($properties['closedate'])) {
$closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');
}
$remotelyCreatedAt = null;
if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {
$date = $this->parseCleanDatetime($properties['createdate']);
$remotelyCreatedAt = $date?->format('Y-m-d H:i:s');
}
$closedStages = $this->getClosedDealStages();
$isWon = in_array($properties['dealstage'], $closedStages['won']);
$isLost = in_array($properties['dealstage'], $closedStages['lost']);
$data = [
'team_id' => $this->team->getId(),
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => $name,
'value' => ! empty($amount) ? $amount : null,
'currency_code' => CurrencyFormatter::formatCode($currency),
'close_date' => $closeDate,
'is_closed' => $isWon || $isLost,
'is_won' => $isWon,
'remotely_created_at' => $remotelyCreatedAt,
'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),
'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),
];
if ($accountId) {
$data['account_id'] = $accountId;
}
if ($stage) {
$data['stage_id'] = $stage->id;
}
if ($businessProcess) {
$recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);
if ($recordType) {
$data['record_type_id'] = $recordType->id;
}
}
return $data;
}
private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess
{
if ($pipelineId === null) {
return null;
}
if (isset($this->cachedBusinessProcesses[$pipelineId])) {
return $this->cachedBusinessProcesses[$pipelineId];
}
$businessProcess = $this->getBusinessProcess($pipelineId);
if (! $businessProcess instanceof BusinessProcess) {
$this->importStages();
$businessProcess = $this->getBusinessProcess($pipelineId);
}
if (! $businessProcess instanceof BusinessProcess) {
$this->logger->info(
'[HubSpot] Deal is not attached to a pipeline',
[
'pipeline' => $pipelineId]
);
}
$this->cachedBusinessProcesses[$pipelineId] = $businessProcess;
return $businessProcess;
}
private function getBusinessProcess(string $pipelineId): ?BusinessProcess
{
return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);
}
private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage
{
if (empty($stageId)) {
return null;
}
$cacheKey = $businessProcess->getId() . ':' . $stageId;
if (isset($this->cachedStages[$cacheKey])) {
return $this->cachedStages[$cacheKey];
}
$stage = $this->crmEntityRepository->getPipelineStageByConditions(
$businessProcess,
[
'crm_provider_id' => $stageId,
'type' => Stage::TYPE_OPPORTUNITY,
]
);
if ($stage === null) {
$this->importStages(null, $stageId);
}
if ($stage === null) {
$this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);
}
$this->cachedStages[$cacheKey] = $stage;
return $stage;
}
private function resolveAmount(array $properties): ?string
{
$amount = null;
if (! empty($properties['amount'])) {
$amount = str_replace(',', '', $properties['amount']);
}
if ($this->config->hasDefaultCurrencyFieldSet()) {
$valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();
$amount = $properties[$valueFieldName] ?? $amount;
}
return $amount;
}
private function parseCleanDatetime(string $datetime): ?Carbon
{
// Treat pre-1980 values as invalid
$minValidDate = Carbon::parse('1980-01-01 00:00:00');
try {
$date = Carbon::parse($datetime);
if ($minValidDate->gt($date)) {
return null;
}
return $date;
} catch (Exception) {
return null; // On parse error, treat as null
}
}
private function resolveDealProbability(?string $stageProbability): int
{
if ($stageProbability === null) {
return 0;
}
$probability = (float) $stageProbability;
return $probability > 1 ? 0 : (int) ($probability * 100);
}
private function resolveForecastCategory(?string $forecastCategory): string
{
if (! $forecastCategory) {
return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;
}
$forecastCategory = str_replace('_', ' ', $forecastCategory);
return ucwords(strtolower($forecastCategory));
}
private function importExternalFieldData(array $properties, int $opportunityId): void
{
$crmFields = $this->getOpportunitySyncableFields();
$this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);
}
private function importOpportunityContacts(Opportunity $opportunity, array $associations): void
{
// Handle empty or missing contact associations
if (empty($associations)) {
// Remove all existing contact associations if none provided
$this->removeAllOpportunityContacts($opportunity);
return;
}
// Use differential sync approach for better performance and accuracy
$this->syncOpportunityContactsDifferential($opportunity, $associations);
}
/**
* Sync opportunity contacts using differential approach
* This compares current vs new associations and only makes necessary changes
*/
private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void
{
$currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);
$contactAssociationIds = array_keys($contactAssociations);
$contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);
$contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);
if (empty($contactsToAdd) && empty($contactsToRemove)) {
return;
}
$this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);
$this->removeContactAssociations($opportunity, $contactsToRemove);
$this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);
}
private function getCurrentContactCrmIds(Opportunity $opportunity): array
{
return $opportunity->contacts()
->pluck('contacts.crm_provider_id')
->toArray();
}
private function logContactAssociationChanges(
Opportunity $opportunity,
array $currentContactCrmIds,
array $contactAssociations,
array $contactsToAdd,
array $contactsToRemove
): void {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [
'opportunity_id' => $opportunity->getId(),
'current_contacts' => $currentContactCrmIds,
'new_contacts' => $contactAssociations,
'contacts_to_add' => $contactsToAdd,
'contacts_to_remove' => $contactsToRemove,
]);
}
private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void
{
if (empty($contactsToRemove)) {
return;
}
$contactsToDetach = $opportunity->contacts()
->whereIn('contacts.crm_provider_id', $contactsToRemove)
->pluck('contacts.id')
->toArray();
if (! empty($contactsToDetach)) {
$opportunity->contacts()->detach($contactsToDetach);
$this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_contact_crm_ids' => $contactsToRemove,
'removed_contact_count' => count($contactsToDetach),
]);
}
}
private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void
{
if (empty($contactsToAdd)) {
return;
}
$contactsAdded = [];
foreach ($contactsToAdd as $crmId) {
$id = $contactAssociations[$crmId];
if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {
$contactsAdded[] = $crmId;
}
}
$this->logAddedContacts($opportunity, $contactsAdded);
}
private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool
{
try {
$contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);
if (! $contact) {
return false;
}
return $this->performContactAttachment($opportunity, $contact, $crmId);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [
'opportunity_id' => $opportunity->getId(),
'contact_crm_id' => $crmId,
'error' => $e->getMessage(),
]);
return false;
}
}
private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool
{
try {
$opportunity->contacts()->attach($contact->getId(), [
'crm_provider_id' => $crmId,
]);
return true;
} catch (\Illuminate\Database\QueryException $e) {
if (str_contains($e->getMessage(), 'Duplicate entry')) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [
'contact_id' => $contact->getId(),
'contact_crm_id' => $crmId,
'opportunity_id' => $opportunity->getId(),
]);
return false;
}
throw $e;
}
}
private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void
{
if (! empty($contactsAdded)) {
$this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [
'opportunity_id' => $opportunity->getId(),
'contacts_to_add_count' => count($contactsAdded),
'added_contact_crm_ids' => $contactsAdded,
'added_contacts_count' => count($contactsAdded),
]);
}
}
}
Execute
Explain Plan...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsCommandTest","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsCommandTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsCommandTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show Replace Field","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Search History","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"\"podcast_audio_url\"","depth":4,"value":"\"podcast_audio_url\"","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Match Case","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Words","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Regex","depth":3,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Replace History","depth":3,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Replace","depth":4,"role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Preserve case","depth":3,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"0 results","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Occurrence","depth":4,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Occurrence","depth":4,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Filter Search Results","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open in Window, Multiple Cursors","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Click to highlight","depth":4,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"32","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"19","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\CollectionResponseAssociatedId;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Models\\Account;\nuse Exception;\nuse Jiminny\\Component\\DealInsights\\Forecast\\Forecast;\nuse Jiminny\\Jobs\\Crm\\MatchActivitiesToNewOpportunity;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Crm\\BusinessProcess;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Models\\Opportunity;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DealFieldsService;\nuse Jiminny\\Services\\Crm\\Hubspot\\OpportunitySyncStrategy\\HubspotSingleSyncStrategy;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Services\\Crm\\OpportunitySyncStrategyResolver;\nuse Jiminny\\Utils\\CurrencyFormatter;\n\n/**\n * Optimized sync methods for better performance\n * These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains\n */\ntrait OpportunitySyncTrait\n{\n private const int BATCH_SIZE = 100;\n private const int BATCH_PROCESS_SIZE = 800;\n\n protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;\n protected CrmEntityRepository $crmEntityRepository;\n protected DealFieldsService $dealFieldsService;\n\n private ?array $cachedClosedDealStages = null;\n private array $cachedBusinessProcesses = [];\n private array $cachedStages = [];\n\n public function syncOpportunities(array $parameters, ?string $strategy = null): int\n {\n $strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);\n $parameters['config'] = $this->config;\n $syncCount = 0;\n $reportedTotal = 0;\n $lastSyncedId = [];\n\n try {\n foreach ($strategies as $strategyName => $syncStrategy) {\n $this->logger->info(\n '[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .\n $strategyName\n );\n\n $total = 0;\n $lastId = null;\n $buffer = [];\n\n // HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies\n foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {\n $buffer[] = $hsOpportunity;\n\n // process every 800 rows (fits < 1 000 association limit)\n if (\\count($buffer) >= self::BATCH_PROCESS_SIZE) {\n $syncCount += $this->processOpportunityBatch($buffer);\n $buffer = [];\n }\n }\n\n // leftovers\n if ($buffer) {\n $syncCount += $this->processOpportunityBatch($buffer);\n }\n\n $reportedTotal += $total;\n $lastSyncedId = $lastId;\n }\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException | CrmException $e) {\n $this->handleSyncException($e, $parameters);\n }\n\n $this->logger->info(\n '[HubSpot] Synced opportunities',\n [\n 'team' => $this->team->getId(),\n 'sync_count' => $syncCount,\n 'total' => $reportedTotal,\n 'last_synced_id' => $lastSyncedId,\n ]\n );\n\n return $reportedTotal;\n }\n\n private function handleSyncException(\\Throwable $e, array $parameters): void\n {\n if (($parameters['since'] ?? null) instanceof Carbon) {\n $parameters['since'] = $parameters['since']->toDateTimeString();\n }\n $parameters['config'] = $this->config->getId();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [\n 'teamId' => $this->team->getUuid(),\n 'parameters' => $parameters,\n 'reason' => $e->getMessage(),\n ]);\n }\n\n /**\n * @inheritdoc\n */\n public function syncOpportunity(string $crmId): ?Opportunity\n {\n $strategy = $this->opportunitySyncStrategyResolver->resolve(\n $this->config,\n OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,\n );\n\n $parameters = [\n 'config' => $this->config,\n 'crm_id' => $crmId,\n ];\n\n try {\n if (! $strategy instanceof HubspotSingleSyncStrategy) {\n throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');\n }\n\n $hsOpportunity = $strategy->fetchOpportunity($parameters);\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);\n\n return $this->importOrUpdateOpportunity($hsOpportunity);\n }\n\n /**\n * Process webhook-collected opportunity batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportOpportunityBatch jobs for batch processing.\n *\n * @return int Number of opportunity IDs dispatched to jobs\n */\n public function batchSyncOpportunities(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,\n $configId\n );\n }\n\n /**\n * Import a batch of opportunities by their CRM IDs.\n * Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().\n *\n * @param array<string> $crmIds HubSpot deal CRM IDs\n *\n * @return array{success: array, failed_ids: array, errors?: array<string, string>}\n */\n public function importOpportunityBatchByIds(array $crmIds): array\n {\n $fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);\n\n $allDeals = [];\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n $deals = $this->client->getOpportunitiesByIds($chunk, $fields);\n foreach ($deals as $deal) {\n $allDeals[] = $deal;\n }\n }\n\n // IDs not returned by HubSpot are likely deleted or inaccessible deals.\n // These are not failures — retrying won't bring them back.\n $fetchedIds = array_map('strval', array_column($allDeals, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allDeals),\n ]);\n }\n\n if (empty($allDeals)) {\n return ['success' => [], 'failed_ids' => []];\n }\n\n return $this->importOpportunityBatch($allDeals);\n }\n\n private function getClosedDealStages(): array\n {\n if ($this->cachedClosedDealStages !== null) {\n return $this->cachedClosedDealStages;\n }\n\n $stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);\n $data = [\n 'lost' => [],\n 'won' => [],\n ];\n\n foreach ($stages as $stage) {\n if ($stage->probability == 0.00) {\n $data['lost'][] = $stage->crm_provider_id;\n }\n if ($stage->probability == 100.00) {\n $data['won'][] = $stage->crm_provider_id;\n }\n }\n\n $this->cachedClosedDealStages = $data;\n\n return $data;\n }\n\n /**\n * Import deals into the database with pre-fetched associations.\n *\n * API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT\n * caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()\n * where Laravel retries the whole job with backoff. After all retries exhausted,\n * failed() requeues all IDs to Redis.\n *\n * The per-deal loop catches exceptions individually. A deal can end up in three states:\n * - success: imported/updated successfully\n * - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)\n * These are permanent issues — retrying won't fix them.\n * - skipped (null): missing dependencies (no account, unknown pipeline/stage).\n * This is acceptable — the deal cannot be imported until those exist.\n */\n private function importOpportunityBatch(array $deals): array\n {\n $syncedOpportunities = [\n 'success' => [],\n 'failed_ids' => [],\n ];\n $dealIds = array_column($deals, 'id');\n\n // Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the\n // queue job retries the whole batch and eventually requeues all deal IDs back to Redis.\n try {\n $companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');\n $contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');\n\n $associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);\n\n $existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(\n $this->config,\n array_map('strval', $dealIds)\n );\n $existingCrmIdSet = array_flip($existingCrmIds);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [\n 'teamId' => $this->team->getId(),\n 'dealCount' => count($dealIds),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n foreach ($deals as $deal) {\n try {\n $deal['associations'] = $this->prepareAssociationsForOpportunity(\n $deal['id'],\n $companyAssociations,\n $contactAssociations,\n $associationsData\n );\n\n $syncedOpportunity = $this->importOrUpdateOpportunity(\n $deal,\n isset($existingCrmIdSet[(string) $deal['id']])\n );\n if ($syncedOpportunity) {\n $syncedOpportunities['success'][] = $syncedOpportunity;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $deal['id'],\n 'error' => $e->getMessage(),\n ]);\n $syncedOpportunities['failed_ids'][] = $deal['id'];\n $syncedOpportunities['errors'][$deal['id']] = $e->getMessage();\n }\n }\n\n return $syncedOpportunities;\n }\n\n /**\n * Prepare associated entities for opportunities with optimized batch processing\n * Returns structured data with CRM ID to DB ID mappings for each opportunity\n */\n private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array\n {\n // Step 1: Collect all unique company and contact IDs from associations\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n\n // Step 2: Batch sync missing entities and get CRM ID to DB ID mappings\n $companyIdMappings = [];\n $contactIdMappings = [];\n\n if (! empty($allCompanyIds)) {\n $companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);\n }\n\n if (! empty($allContactIds)) {\n $contactIdMappings = $this->prepareAssociatedContacts($allContactIds);\n }\n\n return [\n 'company_id_mappings' => $companyIdMappings,\n 'contact_id_mappings' => $contactIdMappings,\n ];\n }\n\n /**\n * Flatten association data to get unique IDs\n */\n private function flattenAssociationIds(array $associations): array\n {\n $ids = [];\n foreach ($associations as $dealAssociations) {\n if (is_array($dealAssociations)) {\n foreach ($dealAssociations as $id) {\n $ids[$id] = true;\n }\n }\n }\n\n return array_keys($ids);\n }\n\n /**\n * Batch sync missing accounts\n */\n private function prepareAssociatedAccounts(array $companyIds): array\n {\n // Find which accounts already exist\n $existingAccounts = $this->crmEntityRepository\n ->findAccountsByExternalIds($this->config, $companyIds);\n\n $existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();\n\n $existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {\n return [$account->getCrmProviderId() => $account->getId()];\n })->toArray();\n\n $missingCompanyIds = array_diff($companyIds, $existingCompanyIds);\n\n if (empty($missingCompanyIds)) {\n return $existingAccountsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [\n 'teamId' => $this->team->getUuid(),\n 'total_companies' => count($companyIds),\n 'existing_companies' => count($existingCompanyIds),\n 'missing_companies' => count($missingCompanyIds),\n ]);\n\n // we already have limit on opportunity ids count\n // Initialize variable before try block\n $syncedAccountsData = [];\n\n try {\n $syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [\n 'size' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedAccountsData = [];\n }\n\n return $existingAccountsData + $syncedAccountsData;\n }\n\n /**\n * Prepare associated contacts - find existing and sync missing ones\n * Returns mapping of CRM ID to DB ID\n */\n private function prepareAssociatedContacts(array $contactIds): array\n {\n // Find which contacts already exist\n $existingContacts = $this->crmEntityRepository\n ->findContactsByExternalIds($this->config, $contactIds);\n\n $existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();\n\n // Create mapping for existing contacts\n $existingContactsData = $existingContacts->mapWithKeys(function ($contact) {\n return [$contact->getCrmProviderId() => $contact->getId()];\n })->toArray();\n\n $missingContactIds = array_diff($contactIds, $existingContactIds);\n\n if (empty($missingContactIds)) {\n return $existingContactsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [\n 'teamId' => $this->team->getUuid(),\n 'total_contacts' => count($contactIds),\n 'existing_contacts' => count($existingContactIds),\n 'missing_contacts' => count($missingContactIds),\n ]);\n\n // Sync missing contacts using batch API\n try {\n $syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [\n 'size' => count($missingContactIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedContactsData = [];\n }\n\n return $existingContactsData + $syncedContactsData;\n }\n\n private function batchSyncCrmObjects(string $objectType, array $crmIds): array\n {\n $syncObjects = [];\n $crmObjectIds = array_values($crmIds);\n\n foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {\n try {\n $objects = $objectType === 'companies' ?\n $this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :\n $this->client->getContactsByIds($chunk, $this->getContactFields());\n\n foreach ($objects as $objectId => $objectData) {\n $this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [\n 'requested_count' => count($chunk),\n 'synced_count' => count($objects),\n ]);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [\n 'ids' => $chunk,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncObjects;\n }\n\n private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void\n {\n try {\n $object = $objectType === 'companies' ?\n $this->importAccount($objectData) :\n $this->importContact($objectData);\n\n if ($object) {\n $syncObjects[$object->getCrmProviderId()] = $object->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [\n 'id' => $objectId,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n /**\n * Prepare associations for a single opportunity\n *\n * The return value is an array with the following structure:\n * [\n * 'companies' => [\n * $companyCrmId => $companyId,\n * ...\n * ],\n * 'contacts' => [\n * $contactCrmId => $contactId,\n * ...\n * ],\n * 'account_id' => $accountId,\n * ]\n */\n private function prepareAssociationsForOpportunity(\n string $oppCrmId,\n array $companyAssociations,\n array $contactAssociations,\n array $associationsData\n ): array {\n $associations = [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n\n $oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];\n foreach ($oppCompanyIds as $companyCrmId) {\n if (isset($associationsData['company_id_mappings'][$companyCrmId])) {\n $associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];\n\n // Set primary account (first company becomes primary account)\n if ($associations['account_id'] === null) {\n $associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];\n }\n }\n }\n\n $oppContactIds = $contactAssociations[$oppCrmId] ?? [];\n foreach ($oppContactIds as $contactCrmId) {\n if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {\n $associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];\n }\n }\n\n return $associations;\n }\n\n /**\n * Update only associations for an opportunity\n */\n private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void\n {\n // Update contact associations\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n // Update company (account) associations\n $this->updateOpportunityAccount($opportunity, $associations['account_id']);\n }\n\n /**\n * Remove all contact associations from an opportunity\n */\n private function removeAllOpportunityContacts(Opportunity $opportunity): void\n {\n $currentCount = (int) $opportunity->contacts()->count();\n\n if ($currentCount > 0) {\n $opportunity->contacts()->detach();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_count' => $currentCount,\n ]);\n }\n }\n\n private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void\n {\n if ($accountId === null) {\n // No account ID provided - keep current account\n return;\n }\n\n $currentAccountId = $opportunity->getAccountId();\n\n // Only update if account has changed\n if ($currentAccountId !== $accountId) {\n $opportunity->account_id = $accountId;\n $opportunity->save();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [\n 'opportunity_id' => $opportunity->getId(),\n 'old_account_id' => $currentAccountId,\n 'new_account_id' => $accountId,\n ]);\n }\n }\n\n /**\n * Find existing opportunities by external IDs (OPTIMIZED VERSION)\n * Uses batch query for better performance\n */\n private function findExistingOpportunities(array $crmIds): Collection\n {\n return $this->crmEntityRepository\n ->findOpportunitiesByExternalIds($this->config, $crmIds);\n }\n\n private function processOpportunityBatch(array $opportunities): int\n {\n $syncedOpportunities = $this->importOpportunityBatch($opportunities);\n\n return count($syncedOpportunities['success'] ?? []);\n }\n\n /**\n * Convert single deal associations from HubSpot format to internal format\n * Handles both HubSpot SDK objects and array formats\n *\n * @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed\n *\n * @return array Processed associations with DB IDs\n */\n private function convertDealAssociations(array $opportunityAssociations): array\n {\n $associations = $this->initializeAssociationsStructure();\n\n if (empty($opportunityAssociations)) {\n return $associations;\n }\n\n $associationIds = $this->extractAssociationIds($opportunityAssociations);\n\n $this->processCompanyAssociations($associationIds, $associations);\n $this->processContactAssociations($associationIds, $associations);\n\n return $associations;\n }\n\n private function initializeAssociationsStructure(): array\n {\n return [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n }\n\n private function extractAssociationIds(array $opportunityAssociations): array\n {\n $associationIds = [];\n\n foreach ($opportunityAssociations as $type => $associationData) {\n if (! empty($associationData)) {\n $associationIds[$type] = $this->convertSingleDealAssociations($associationData);\n }\n }\n\n return $associationIds;\n }\n\n private function processCompanyAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['companies'])) {\n return;\n }\n\n $companyId = $associationIds['companies'][0];\n $account = $this->findOrSyncAccount($companyId);\n\n if ($account instanceof Account) {\n $associations['companies'][$companyId] = $account->getId();\n $associations['account_id'] = $account->getId();\n }\n }\n\n private function processContactAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['contacts'])) {\n return;\n }\n\n foreach ($associationIds['contacts'] as $contactId) {\n $contact = $this->findOrSyncContact($contactId);\n\n if ($contact instanceof Contact) {\n $associations['contacts'][$contactId] = $contact->getId();\n }\n }\n }\n\n private function findOrSyncAccount(string $companyId): ?Account\n {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);\n\n if (! $account instanceof Account) {\n $account = $this->syncAccount($companyId);\n }\n\n return $account;\n }\n\n private function findOrSyncContact(string $contactId): ?Contact\n {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);\n\n if (! $contact instanceof Contact) {\n $contact = $this->syncContact($contactId);\n }\n\n return $contact;\n }\n\n private function convertSingleDealAssociations($opportunityAssociations = null): array\n {\n $associationData = [];\n\n if ($opportunityAssociations === null) {\n return $associationData;\n }\n\n // Handle array input (from extractAssociationIds)\n if (is_array($opportunityAssociations)) {\n return $opportunityAssociations;\n }\n\n // Handle CollectionResponseAssociatedId object\n if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {\n foreach ($opportunityAssociations->getResults() as $association) {\n $associationData[] = $association->getId();\n }\n }\n\n return $associationData;\n }\n\n private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity\n {\n if (empty($crmData['properties'])) {\n return null;\n }\n\n $crmId = (string) $crmData['id'];\n $properties = $crmData['properties'];\n $associations = $crmData['associations'] ?? [];\n\n $opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(\n $this->config,\n $crmId\n );\n\n if ($opportunityExists) {\n return $this->updateOpportunity($crmId, $properties, $associations);\n } else {\n return $this->createOpportunity($crmId, $properties, $associations);\n }\n }\n\n /**\n * Create new opportunity\n */\n private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n if (! $accountId) {\n return null;\n }\n\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n if (! $businessProcess) {\n return null;\n }\n\n $stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);\n if (! $stage) {\n return null;\n }\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n if ($opportunity->wasRecentlyCreated) {\n MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());\n }\n\n return $opportunity;\n }\n\n /**\n * Update existing opportunity\n */\n private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n $stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->updateOpportunityAssociations($opportunity, $associations);\n\n return $opportunity;\n }\n\n private function resolveAccountId(array $associations): ?int\n {\n if (! empty($associations['accountId'])) {\n return $associations['accountId'];\n }\n\n if (empty($associations)) {\n return null;\n }\n\n // we can't resolve multiple account ids (currently SDK returns one company)\n foreach ($associations['companies'] as $accountId) {\n return $accountId;\n }\n\n return null;\n }\n\n private function buildOpportunityData(\n array $properties,\n ?int $accountId,\n ?BusinessProcess $businessProcess,\n ?Stage $stage\n ): array {\n $ownerId = null;\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $name = 'Unknown';\n if (isset($properties['dealname'])) {\n $name = mb_strimwidth($properties['dealname'], 0, 128);\n }\n\n $amount = $this->resolveAmount($properties);\n $currency = $properties['deal_currency_code'] ?? null;\n\n $closeDate = null;\n if (! empty($properties['closedate'])) {\n $closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');\n }\n\n $remotelyCreatedAt = null;\n if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {\n $date = $this->parseCleanDatetime($properties['createdate']);\n $remotelyCreatedAt = $date?->format('Y-m-d H:i:s');\n }\n\n $closedStages = $this->getClosedDealStages();\n $isWon = in_array($properties['dealstage'], $closedStages['won']);\n $isLost = in_array($properties['dealstage'], $closedStages['lost']);\n\n $data = [\n 'team_id' => $this->team->getId(),\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'value' => ! empty($amount) ? $amount : null,\n 'currency_code' => CurrencyFormatter::formatCode($currency),\n 'close_date' => $closeDate,\n 'is_closed' => $isWon || $isLost,\n 'is_won' => $isWon,\n 'remotely_created_at' => $remotelyCreatedAt,\n 'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),\n 'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),\n ];\n\n if ($accountId) {\n $data['account_id'] = $accountId;\n }\n\n if ($stage) {\n $data['stage_id'] = $stage->id;\n }\n\n if ($businessProcess) {\n $recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);\n if ($recordType) {\n $data['record_type_id'] = $recordType->id;\n }\n }\n\n return $data;\n }\n\n private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess\n {\n if ($pipelineId === null) {\n return null;\n }\n\n if (isset($this->cachedBusinessProcesses[$pipelineId])) {\n return $this->cachedBusinessProcesses[$pipelineId];\n }\n\n $businessProcess = $this->getBusinessProcess($pipelineId);\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->importStages();\n $businessProcess = $this->getBusinessProcess($pipelineId);\n }\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->logger->info(\n '[HubSpot] Deal is not attached to a pipeline',\n [\n 'pipeline' => $pipelineId]\n );\n }\n\n $this->cachedBusinessProcesses[$pipelineId] = $businessProcess;\n\n return $businessProcess;\n }\n\n private function getBusinessProcess(string $pipelineId): ?BusinessProcess\n {\n return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);\n }\n\n private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage\n {\n if (empty($stageId)) {\n return null;\n }\n\n $cacheKey = $businessProcess->getId() . ':' . $stageId;\n if (isset($this->cachedStages[$cacheKey])) {\n return $this->cachedStages[$cacheKey];\n }\n\n $stage = $this->crmEntityRepository->getPipelineStageByConditions(\n $businessProcess,\n [\n 'crm_provider_id' => $stageId,\n 'type' => Stage::TYPE_OPPORTUNITY,\n ]\n );\n\n if ($stage === null) {\n $this->importStages(null, $stageId);\n }\n\n if ($stage === null) {\n $this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);\n }\n\n $this->cachedStages[$cacheKey] = $stage;\n\n return $stage;\n }\n\n private function resolveAmount(array $properties): ?string\n {\n $amount = null;\n if (! empty($properties['amount'])) {\n $amount = str_replace(',', '', $properties['amount']);\n }\n\n if ($this->config->hasDefaultCurrencyFieldSet()) {\n $valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();\n $amount = $properties[$valueFieldName] ?? $amount;\n }\n\n return $amount;\n }\n\n private function parseCleanDatetime(string $datetime): ?Carbon\n {\n // Treat pre-1980 values as invalid\n $minValidDate = Carbon::parse('1980-01-01 00:00:00');\n\n try {\n $date = Carbon::parse($datetime);\n\n if ($minValidDate->gt($date)) {\n return null;\n }\n\n return $date;\n } catch (Exception) {\n return null; // On parse error, treat as null\n }\n }\n\n private function resolveDealProbability(?string $stageProbability): int\n {\n if ($stageProbability === null) {\n return 0;\n }\n\n $probability = (float) $stageProbability;\n\n return $probability > 1 ? 0 : (int) ($probability * 100);\n }\n\n private function resolveForecastCategory(?string $forecastCategory): string\n {\n if (! $forecastCategory) {\n return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;\n }\n\n $forecastCategory = str_replace('_', ' ', $forecastCategory);\n\n return ucwords(strtolower($forecastCategory));\n }\n\n private function importExternalFieldData(array $properties, int $opportunityId): void\n {\n $crmFields = $this->getOpportunitySyncableFields();\n $this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);\n }\n\n private function importOpportunityContacts(Opportunity $opportunity, array $associations): void\n {\n // Handle empty or missing contact associations\n if (empty($associations)) {\n // Remove all existing contact associations if none provided\n $this->removeAllOpportunityContacts($opportunity);\n\n return;\n }\n\n // Use differential sync approach for better performance and accuracy\n $this->syncOpportunityContactsDifferential($opportunity, $associations);\n }\n\n /**\n * Sync opportunity contacts using differential approach\n * This compares current vs new associations and only makes necessary changes\n */\n private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void\n {\n $currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);\n $contactAssociationIds = array_keys($contactAssociations);\n\n $contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);\n $contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);\n\n if (empty($contactsToAdd) && empty($contactsToRemove)) {\n return;\n }\n\n $this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);\n\n $this->removeContactAssociations($opportunity, $contactsToRemove);\n $this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);\n }\n\n private function getCurrentContactCrmIds(Opportunity $opportunity): array\n {\n return $opportunity->contacts()\n ->pluck('contacts.crm_provider_id')\n ->toArray();\n }\n\n private function logContactAssociationChanges(\n Opportunity $opportunity,\n array $currentContactCrmIds,\n array $contactAssociations,\n array $contactsToAdd,\n array $contactsToRemove\n ): void {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [\n 'opportunity_id' => $opportunity->getId(),\n 'current_contacts' => $currentContactCrmIds,\n 'new_contacts' => $contactAssociations,\n 'contacts_to_add' => $contactsToAdd,\n 'contacts_to_remove' => $contactsToRemove,\n ]);\n }\n\n private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void\n {\n if (empty($contactsToRemove)) {\n return;\n }\n\n $contactsToDetach = $opportunity->contacts()\n ->whereIn('contacts.crm_provider_id', $contactsToRemove)\n ->pluck('contacts.id')\n ->toArray();\n\n if (! empty($contactsToDetach)) {\n $opportunity->contacts()->detach($contactsToDetach);\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_contact_crm_ids' => $contactsToRemove,\n 'removed_contact_count' => count($contactsToDetach),\n ]);\n }\n }\n\n private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void\n {\n if (empty($contactsToAdd)) {\n return;\n }\n\n $contactsAdded = [];\n foreach ($contactsToAdd as $crmId) {\n $id = $contactAssociations[$crmId];\n\n if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {\n $contactsAdded[] = $crmId;\n }\n }\n\n $this->logAddedContacts($opportunity, $contactsAdded);\n }\n\n private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);\n\n if (! $contact) {\n return false;\n }\n\n return $this->performContactAttachment($opportunity, $contact, $crmId);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [\n 'opportunity_id' => $opportunity->getId(),\n 'contact_crm_id' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool\n {\n try {\n $opportunity->contacts()->attach($contact->getId(), [\n 'crm_provider_id' => $crmId,\n ]);\n\n return true;\n } catch (\\Illuminate\\Database\\QueryException $e) {\n if (str_contains($e->getMessage(), 'Duplicate entry')) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [\n 'contact_id' => $contact->getId(),\n 'contact_crm_id' => $crmId,\n 'opportunity_id' => $opportunity->getId(),\n ]);\n\n return false;\n }\n\n throw $e;\n }\n }\n\n private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void\n {\n if (! empty($contactsAdded)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'contacts_to_add_count' => count($contactsAdded),\n 'added_contact_crm_ids' => $contactsAdded,\n 'added_contacts_count' => count($contactsAdded),\n ]);\n }\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\CollectionResponseAssociatedId;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Models\\Account;\nuse Exception;\nuse Jiminny\\Component\\DealInsights\\Forecast\\Forecast;\nuse Jiminny\\Jobs\\Crm\\MatchActivitiesToNewOpportunity;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Crm\\BusinessProcess;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Models\\Opportunity;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DealFieldsService;\nuse Jiminny\\Services\\Crm\\Hubspot\\OpportunitySyncStrategy\\HubspotSingleSyncStrategy;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Services\\Crm\\OpportunitySyncStrategyResolver;\nuse Jiminny\\Utils\\CurrencyFormatter;\n\n/**\n * Optimized sync methods for better performance\n * These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains\n */\ntrait OpportunitySyncTrait\n{\n private const int BATCH_SIZE = 100;\n private const int BATCH_PROCESS_SIZE = 800;\n\n protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;\n protected CrmEntityRepository $crmEntityRepository;\n protected DealFieldsService $dealFieldsService;\n\n private ?array $cachedClosedDealStages = null;\n private array $cachedBusinessProcesses = [];\n private array $cachedStages = [];\n\n public function syncOpportunities(array $parameters, ?string $strategy = null): int\n {\n $strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);\n $parameters['config'] = $this->config;\n $syncCount = 0;\n $reportedTotal = 0;\n $lastSyncedId = [];\n\n try {\n foreach ($strategies as $strategyName => $syncStrategy) {\n $this->logger->info(\n '[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .\n $strategyName\n );\n\n $total = 0;\n $lastId = null;\n $buffer = [];\n\n // HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies\n foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {\n $buffer[] = $hsOpportunity;\n\n // process every 800 rows (fits < 1 000 association limit)\n if (\\count($buffer) >= self::BATCH_PROCESS_SIZE) {\n $syncCount += $this->processOpportunityBatch($buffer);\n $buffer = [];\n }\n }\n\n // leftovers\n if ($buffer) {\n $syncCount += $this->processOpportunityBatch($buffer);\n }\n\n $reportedTotal += $total;\n $lastSyncedId = $lastId;\n }\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException | CrmException $e) {\n $this->handleSyncException($e, $parameters);\n }\n\n $this->logger->info(\n '[HubSpot] Synced opportunities',\n [\n 'team' => $this->team->getId(),\n 'sync_count' => $syncCount,\n 'total' => $reportedTotal,\n 'last_synced_id' => $lastSyncedId,\n ]\n );\n\n return $reportedTotal;\n }\n\n private function handleSyncException(\\Throwable $e, array $parameters): void\n {\n if (($parameters['since'] ?? null) instanceof Carbon) {\n $parameters['since'] = $parameters['since']->toDateTimeString();\n }\n $parameters['config'] = $this->config->getId();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [\n 'teamId' => $this->team->getUuid(),\n 'parameters' => $parameters,\n 'reason' => $e->getMessage(),\n ]);\n }\n\n /**\n * @inheritdoc\n */\n public function syncOpportunity(string $crmId): ?Opportunity\n {\n $strategy = $this->opportunitySyncStrategyResolver->resolve(\n $this->config,\n OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,\n );\n\n $parameters = [\n 'config' => $this->config,\n 'crm_id' => $crmId,\n ];\n\n try {\n if (! $strategy instanceof HubspotSingleSyncStrategy) {\n throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');\n }\n\n $hsOpportunity = $strategy->fetchOpportunity($parameters);\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);\n\n return $this->importOrUpdateOpportunity($hsOpportunity);\n }\n\n /**\n * Process webhook-collected opportunity batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportOpportunityBatch jobs for batch processing.\n *\n * @return int Number of opportunity IDs dispatched to jobs\n */\n public function batchSyncOpportunities(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,\n $configId\n );\n }\n\n /**\n * Import a batch of opportunities by their CRM IDs.\n * Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().\n *\n * @param array<string> $crmIds HubSpot deal CRM IDs\n *\n * @return array{success: array, failed_ids: array, errors?: array<string, string>}\n */\n public function importOpportunityBatchByIds(array $crmIds): array\n {\n $fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);\n\n $allDeals = [];\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n $deals = $this->client->getOpportunitiesByIds($chunk, $fields);\n foreach ($deals as $deal) {\n $allDeals[] = $deal;\n }\n }\n\n // IDs not returned by HubSpot are likely deleted or inaccessible deals.\n // These are not failures — retrying won't bring them back.\n $fetchedIds = array_map('strval', array_column($allDeals, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allDeals),\n ]);\n }\n\n if (empty($allDeals)) {\n return ['success' => [], 'failed_ids' => []];\n }\n\n return $this->importOpportunityBatch($allDeals);\n }\n\n private function getClosedDealStages(): array\n {\n if ($this->cachedClosedDealStages !== null) {\n return $this->cachedClosedDealStages;\n }\n\n $stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);\n $data = [\n 'lost' => [],\n 'won' => [],\n ];\n\n foreach ($stages as $stage) {\n if ($stage->probability == 0.00) {\n $data['lost'][] = $stage->crm_provider_id;\n }\n if ($stage->probability == 100.00) {\n $data['won'][] = $stage->crm_provider_id;\n }\n }\n\n $this->cachedClosedDealStages = $data;\n\n return $data;\n }\n\n /**\n * Import deals into the database with pre-fetched associations.\n *\n * API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT\n * caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()\n * where Laravel retries the whole job with backoff. After all retries exhausted,\n * failed() requeues all IDs to Redis.\n *\n * The per-deal loop catches exceptions individually. A deal can end up in three states:\n * - success: imported/updated successfully\n * - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)\n * These are permanent issues — retrying won't fix them.\n * - skipped (null): missing dependencies (no account, unknown pipeline/stage).\n * This is acceptable — the deal cannot be imported until those exist.\n */\n private function importOpportunityBatch(array $deals): array\n {\n $syncedOpportunities = [\n 'success' => [],\n 'failed_ids' => [],\n ];\n $dealIds = array_column($deals, 'id');\n\n // Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the\n // queue job retries the whole batch and eventually requeues all deal IDs back to Redis.\n try {\n $companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');\n $contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');\n\n $associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);\n\n $existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(\n $this->config,\n array_map('strval', $dealIds)\n );\n $existingCrmIdSet = array_flip($existingCrmIds);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [\n 'teamId' => $this->team->getId(),\n 'dealCount' => count($dealIds),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n foreach ($deals as $deal) {\n try {\n $deal['associations'] = $this->prepareAssociationsForOpportunity(\n $deal['id'],\n $companyAssociations,\n $contactAssociations,\n $associationsData\n );\n\n $syncedOpportunity = $this->importOrUpdateOpportunity(\n $deal,\n isset($existingCrmIdSet[(string) $deal['id']])\n );\n if ($syncedOpportunity) {\n $syncedOpportunities['success'][] = $syncedOpportunity;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $deal['id'],\n 'error' => $e->getMessage(),\n ]);\n $syncedOpportunities['failed_ids'][] = $deal['id'];\n $syncedOpportunities['errors'][$deal['id']] = $e->getMessage();\n }\n }\n\n return $syncedOpportunities;\n }\n\n /**\n * Prepare associated entities for opportunities with optimized batch processing\n * Returns structured data with CRM ID to DB ID mappings for each opportunity\n */\n private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array\n {\n // Step 1: Collect all unique company and contact IDs from associations\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n\n // Step 2: Batch sync missing entities and get CRM ID to DB ID mappings\n $companyIdMappings = [];\n $contactIdMappings = [];\n\n if (! empty($allCompanyIds)) {\n $companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);\n }\n\n if (! empty($allContactIds)) {\n $contactIdMappings = $this->prepareAssociatedContacts($allContactIds);\n }\n\n return [\n 'company_id_mappings' => $companyIdMappings,\n 'contact_id_mappings' => $contactIdMappings,\n ];\n }\n\n /**\n * Flatten association data to get unique IDs\n */\n private function flattenAssociationIds(array $associations): array\n {\n $ids = [];\n foreach ($associations as $dealAssociations) {\n if (is_array($dealAssociations)) {\n foreach ($dealAssociations as $id) {\n $ids[$id] = true;\n }\n }\n }\n\n return array_keys($ids);\n }\n\n /**\n * Batch sync missing accounts\n */\n private function prepareAssociatedAccounts(array $companyIds): array\n {\n // Find which accounts already exist\n $existingAccounts = $this->crmEntityRepository\n ->findAccountsByExternalIds($this->config, $companyIds);\n\n $existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();\n\n $existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {\n return [$account->getCrmProviderId() => $account->getId()];\n })->toArray();\n\n $missingCompanyIds = array_diff($companyIds, $existingCompanyIds);\n\n if (empty($missingCompanyIds)) {\n return $existingAccountsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [\n 'teamId' => $this->team->getUuid(),\n 'total_companies' => count($companyIds),\n 'existing_companies' => count($existingCompanyIds),\n 'missing_companies' => count($missingCompanyIds),\n ]);\n\n // we already have limit on opportunity ids count\n // Initialize variable before try block\n $syncedAccountsData = [];\n\n try {\n $syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [\n 'size' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedAccountsData = [];\n }\n\n return $existingAccountsData + $syncedAccountsData;\n }\n\n /**\n * Prepare associated contacts - find existing and sync missing ones\n * Returns mapping of CRM ID to DB ID\n */\n private function prepareAssociatedContacts(array $contactIds): array\n {\n // Find which contacts already exist\n $existingContacts = $this->crmEntityRepository\n ->findContactsByExternalIds($this->config, $contactIds);\n\n $existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();\n\n // Create mapping for existing contacts\n $existingContactsData = $existingContacts->mapWithKeys(function ($contact) {\n return [$contact->getCrmProviderId() => $contact->getId()];\n })->toArray();\n\n $missingContactIds = array_diff($contactIds, $existingContactIds);\n\n if (empty($missingContactIds)) {\n return $existingContactsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [\n 'teamId' => $this->team->getUuid(),\n 'total_contacts' => count($contactIds),\n 'existing_contacts' => count($existingContactIds),\n 'missing_contacts' => count($missingContactIds),\n ]);\n\n // Sync missing contacts using batch API\n try {\n $syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [\n 'size' => count($missingContactIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedContactsData = [];\n }\n\n return $existingContactsData + $syncedContactsData;\n }\n\n private function batchSyncCrmObjects(string $objectType, array $crmIds): array\n {\n $syncObjects = [];\n $crmObjectIds = array_values($crmIds);\n\n foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {\n try {\n $objects = $objectType === 'companies' ?\n $this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :\n $this->client->getContactsByIds($chunk, $this->getContactFields());\n\n foreach ($objects as $objectId => $objectData) {\n $this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [\n 'requested_count' => count($chunk),\n 'synced_count' => count($objects),\n ]);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [\n 'ids' => $chunk,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncObjects;\n }\n\n private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void\n {\n try {\n $object = $objectType === 'companies' ?\n $this->importAccount($objectData) :\n $this->importContact($objectData);\n\n if ($object) {\n $syncObjects[$object->getCrmProviderId()] = $object->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [\n 'id' => $objectId,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n /**\n * Prepare associations for a single opportunity\n *\n * The return value is an array with the following structure:\n * [\n * 'companies' => [\n * $companyCrmId => $companyId,\n * ...\n * ],\n * 'contacts' => [\n * $contactCrmId => $contactId,\n * ...\n * ],\n * 'account_id' => $accountId,\n * ]\n */\n private function prepareAssociationsForOpportunity(\n string $oppCrmId,\n array $companyAssociations,\n array $contactAssociations,\n array $associationsData\n ): array {\n $associations = [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n\n $oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];\n foreach ($oppCompanyIds as $companyCrmId) {\n if (isset($associationsData['company_id_mappings'][$companyCrmId])) {\n $associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];\n\n // Set primary account (first company becomes primary account)\n if ($associations['account_id'] === null) {\n $associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];\n }\n }\n }\n\n $oppContactIds = $contactAssociations[$oppCrmId] ?? [];\n foreach ($oppContactIds as $contactCrmId) {\n if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {\n $associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];\n }\n }\n\n return $associations;\n }\n\n /**\n * Update only associations for an opportunity\n */\n private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void\n {\n // Update contact associations\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n // Update company (account) associations\n $this->updateOpportunityAccount($opportunity, $associations['account_id']);\n }\n\n /**\n * Remove all contact associations from an opportunity\n */\n private function removeAllOpportunityContacts(Opportunity $opportunity): void\n {\n $currentCount = (int) $opportunity->contacts()->count();\n\n if ($currentCount > 0) {\n $opportunity->contacts()->detach();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_count' => $currentCount,\n ]);\n }\n }\n\n private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void\n {\n if ($accountId === null) {\n // No account ID provided - keep current account\n return;\n }\n\n $currentAccountId = $opportunity->getAccountId();\n\n // Only update if account has changed\n if ($currentAccountId !== $accountId) {\n $opportunity->account_id = $accountId;\n $opportunity->save();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [\n 'opportunity_id' => $opportunity->getId(),\n 'old_account_id' => $currentAccountId,\n 'new_account_id' => $accountId,\n ]);\n }\n }\n\n /**\n * Find existing opportunities by external IDs (OPTIMIZED VERSION)\n * Uses batch query for better performance\n */\n private function findExistingOpportunities(array $crmIds): Collection\n {\n return $this->crmEntityRepository\n ->findOpportunitiesByExternalIds($this->config, $crmIds);\n }\n\n private function processOpportunityBatch(array $opportunities): int\n {\n $syncedOpportunities = $this->importOpportunityBatch($opportunities);\n\n return count($syncedOpportunities['success'] ?? []);\n }\n\n /**\n * Convert single deal associations from HubSpot format to internal format\n * Handles both HubSpot SDK objects and array formats\n *\n * @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed\n *\n * @return array Processed associations with DB IDs\n */\n private function convertDealAssociations(array $opportunityAssociations): array\n {\n $associations = $this->initializeAssociationsStructure();\n\n if (empty($opportunityAssociations)) {\n return $associations;\n }\n\n $associationIds = $this->extractAssociationIds($opportunityAssociations);\n\n $this->processCompanyAssociations($associationIds, $associations);\n $this->processContactAssociations($associationIds, $associations);\n\n return $associations;\n }\n\n private function initializeAssociationsStructure(): array\n {\n return [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n }\n\n private function extractAssociationIds(array $opportunityAssociations): array\n {\n $associationIds = [];\n\n foreach ($opportunityAssociations as $type => $associationData) {\n if (! empty($associationData)) {\n $associationIds[$type] = $this->convertSingleDealAssociations($associationData);\n }\n }\n\n return $associationIds;\n }\n\n private function processCompanyAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['companies'])) {\n return;\n }\n\n $companyId = $associationIds['companies'][0];\n $account = $this->findOrSyncAccount($companyId);\n\n if ($account instanceof Account) {\n $associations['companies'][$companyId] = $account->getId();\n $associations['account_id'] = $account->getId();\n }\n }\n\n private function processContactAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['contacts'])) {\n return;\n }\n\n foreach ($associationIds['contacts'] as $contactId) {\n $contact = $this->findOrSyncContact($contactId);\n\n if ($contact instanceof Contact) {\n $associations['contacts'][$contactId] = $contact->getId();\n }\n }\n }\n\n private function findOrSyncAccount(string $companyId): ?Account\n {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);\n\n if (! $account instanceof Account) {\n $account = $this->syncAccount($companyId);\n }\n\n return $account;\n }\n\n private function findOrSyncContact(string $contactId): ?Contact\n {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);\n\n if (! $contact instanceof Contact) {\n $contact = $this->syncContact($contactId);\n }\n\n return $contact;\n }\n\n private function convertSingleDealAssociations($opportunityAssociations = null): array\n {\n $associationData = [];\n\n if ($opportunityAssociations === null) {\n return $associationData;\n }\n\n // Handle array input (from extractAssociationIds)\n if (is_array($opportunityAssociations)) {\n return $opportunityAssociations;\n }\n\n // Handle CollectionResponseAssociatedId object\n if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {\n foreach ($opportunityAssociations->getResults() as $association) {\n $associationData[] = $association->getId();\n }\n }\n\n return $associationData;\n }\n\n private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity\n {\n if (empty($crmData['properties'])) {\n return null;\n }\n\n $crmId = (string) $crmData['id'];\n $properties = $crmData['properties'];\n $associations = $crmData['associations'] ?? [];\n\n $opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(\n $this->config,\n $crmId\n );\n\n if ($opportunityExists) {\n return $this->updateOpportunity($crmId, $properties, $associations);\n } else {\n return $this->createOpportunity($crmId, $properties, $associations);\n }\n }\n\n /**\n * Create new opportunity\n */\n private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n if (! $accountId) {\n return null;\n }\n\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n if (! $businessProcess) {\n return null;\n }\n\n $stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);\n if (! $stage) {\n return null;\n }\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n if ($opportunity->wasRecentlyCreated) {\n MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());\n }\n\n return $opportunity;\n }\n\n /**\n * Update existing opportunity\n */\n private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n $stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->updateOpportunityAssociations($opportunity, $associations);\n\n return $opportunity;\n }\n\n private function resolveAccountId(array $associations): ?int\n {\n if (! empty($associations['accountId'])) {\n return $associations['accountId'];\n }\n\n if (empty($associations)) {\n return null;\n }\n\n // we can't resolve multiple account ids (currently SDK returns one company)\n foreach ($associations['companies'] as $accountId) {\n return $accountId;\n }\n\n return null;\n }\n\n private function buildOpportunityData(\n array $properties,\n ?int $accountId,\n ?BusinessProcess $businessProcess,\n ?Stage $stage\n ): array {\n $ownerId = null;\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $name = 'Unknown';\n if (isset($properties['dealname'])) {\n $name = mb_strimwidth($properties['dealname'], 0, 128);\n }\n\n $amount = $this->resolveAmount($properties);\n $currency = $properties['deal_currency_code'] ?? null;\n\n $closeDate = null;\n if (! empty($properties['closedate'])) {\n $closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');\n }\n\n $remotelyCreatedAt = null;\n if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {\n $date = $this->parseCleanDatetime($properties['createdate']);\n $remotelyCreatedAt = $date?->format('Y-m-d H:i:s');\n }\n\n $closedStages = $this->getClosedDealStages();\n $isWon = in_array($properties['dealstage'], $closedStages['won']);\n $isLost = in_array($properties['dealstage'], $closedStages['lost']);\n\n $data = [\n 'team_id' => $this->team->getId(),\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'value' => ! empty($amount) ? $amount : null,\n 'currency_code' => CurrencyFormatter::formatCode($currency),\n 'close_date' => $closeDate,\n 'is_closed' => $isWon || $isLost,\n 'is_won' => $isWon,\n 'remotely_created_at' => $remotelyCreatedAt,\n 'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),\n 'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),\n ];\n\n if ($accountId) {\n $data['account_id'] = $accountId;\n }\n\n if ($stage) {\n $data['stage_id'] = $stage->id;\n }\n\n if ($businessProcess) {\n $recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);\n if ($recordType) {\n $data['record_type_id'] = $recordType->id;\n }\n }\n\n return $data;\n }\n\n private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess\n {\n if ($pipelineId === null) {\n return null;\n }\n\n if (isset($this->cachedBusinessProcesses[$pipelineId])) {\n return $this->cachedBusinessProcesses[$pipelineId];\n }\n\n $businessProcess = $this->getBusinessProcess($pipelineId);\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->importStages();\n $businessProcess = $this->getBusinessProcess($pipelineId);\n }\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->logger->info(\n '[HubSpot] Deal is not attached to a pipeline',\n [\n 'pipeline' => $pipelineId]\n );\n }\n\n $this->cachedBusinessProcesses[$pipelineId] = $businessProcess;\n\n return $businessProcess;\n }\n\n private function getBusinessProcess(string $pipelineId): ?BusinessProcess\n {\n return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);\n }\n\n private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage\n {\n if (empty($stageId)) {\n return null;\n }\n\n $cacheKey = $businessProcess->getId() . ':' . $stageId;\n if (isset($this->cachedStages[$cacheKey])) {\n return $this->cachedStages[$cacheKey];\n }\n\n $stage = $this->crmEntityRepository->getPipelineStageByConditions(\n $businessProcess,\n [\n 'crm_provider_id' => $stageId,\n 'type' => Stage::TYPE_OPPORTUNITY,\n ]\n );\n\n if ($stage === null) {\n $this->importStages(null, $stageId);\n }\n\n if ($stage === null) {\n $this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);\n }\n\n $this->cachedStages[$cacheKey] = $stage;\n\n return $stage;\n }\n\n private function resolveAmount(array $properties): ?string\n {\n $amount = null;\n if (! empty($properties['amount'])) {\n $amount = str_replace(',', '', $properties['amount']);\n }\n\n if ($this->config->hasDefaultCurrencyFieldSet()) {\n $valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();\n $amount = $properties[$valueFieldName] ?? $amount;\n }\n\n return $amount;\n }\n\n private function parseCleanDatetime(string $datetime): ?Carbon\n {\n // Treat pre-1980 values as invalid\n $minValidDate = Carbon::parse('1980-01-01 00:00:00');\n\n try {\n $date = Carbon::parse($datetime);\n\n if ($minValidDate->gt($date)) {\n return null;\n }\n\n return $date;\n } catch (Exception) {\n return null; // On parse error, treat as null\n }\n }\n\n private function resolveDealProbability(?string $stageProbability): int\n {\n if ($stageProbability === null) {\n return 0;\n }\n\n $probability = (float) $stageProbability;\n\n return $probability > 1 ? 0 : (int) ($probability * 100);\n }\n\n private function resolveForecastCategory(?string $forecastCategory): string\n {\n if (! $forecastCategory) {\n return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;\n }\n\n $forecastCategory = str_replace('_', ' ', $forecastCategory);\n\n return ucwords(strtolower($forecastCategory));\n }\n\n private function importExternalFieldData(array $properties, int $opportunityId): void\n {\n $crmFields = $this->getOpportunitySyncableFields();\n $this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);\n }\n\n private function importOpportunityContacts(Opportunity $opportunity, array $associations): void\n {\n // Handle empty or missing contact associations\n if (empty($associations)) {\n // Remove all existing contact associations if none provided\n $this->removeAllOpportunityContacts($opportunity);\n\n return;\n }\n\n // Use differential sync approach for better performance and accuracy\n $this->syncOpportunityContactsDifferential($opportunity, $associations);\n }\n\n /**\n * Sync opportunity contacts using differential approach\n * This compares current vs new associations and only makes necessary changes\n */\n private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void\n {\n $currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);\n $contactAssociationIds = array_keys($contactAssociations);\n\n $contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);\n $contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);\n\n if (empty($contactsToAdd) && empty($contactsToRemove)) {\n return;\n }\n\n $this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);\n\n $this->removeContactAssociations($opportunity, $contactsToRemove);\n $this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);\n }\n\n private function getCurrentContactCrmIds(Opportunity $opportunity): array\n {\n return $opportunity->contacts()\n ->pluck('contacts.crm_provider_id')\n ->toArray();\n }\n\n private function logContactAssociationChanges(\n Opportunity $opportunity,\n array $currentContactCrmIds,\n array $contactAssociations,\n array $contactsToAdd,\n array $contactsToRemove\n ): void {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [\n 'opportunity_id' => $opportunity->getId(),\n 'current_contacts' => $currentContactCrmIds,\n 'new_contacts' => $contactAssociations,\n 'contacts_to_add' => $contactsToAdd,\n 'contacts_to_remove' => $contactsToRemove,\n ]);\n }\n\n private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void\n {\n if (empty($contactsToRemove)) {\n return;\n }\n\n $contactsToDetach = $opportunity->contacts()\n ->whereIn('contacts.crm_provider_id', $contactsToRemove)\n ->pluck('contacts.id')\n ->toArray();\n\n if (! empty($contactsToDetach)) {\n $opportunity->contacts()->detach($contactsToDetach);\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_contact_crm_ids' => $contactsToRemove,\n 'removed_contact_count' => count($contactsToDetach),\n ]);\n }\n }\n\n private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void\n {\n if (empty($contactsToAdd)) {\n return;\n }\n\n $contactsAdded = [];\n foreach ($contactsToAdd as $crmId) {\n $id = $contactAssociations[$crmId];\n\n if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {\n $contactsAdded[] = $crmId;\n }\n }\n\n $this->logAddedContacts($opportunity, $contactsAdded);\n }\n\n private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);\n\n if (! $contact) {\n return false;\n }\n\n return $this->performContactAttachment($opportunity, $contact, $crmId);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [\n 'opportunity_id' => $opportunity->getId(),\n 'contact_crm_id' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool\n {\n try {\n $opportunity->contacts()->attach($contact->getId(), [\n 'crm_provider_id' => $crmId,\n ]);\n\n return true;\n } catch (\\Illuminate\\Database\\QueryException $e) {\n if (str_contains($e->getMessage(), 'Duplicate entry')) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [\n 'contact_id' => $contact->getId(),\n 'contact_crm_id' => $crmId,\n 'opportunity_id' => $opportunity->getId(),\n ]);\n\n return false;\n }\n\n throw $e;\n }\n }\n\n private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void\n {\n if (! empty($contactsAdded)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'contacts_to_add_count' => count($contactsAdded),\n 'added_contact_crm_ids' => $contactsAdded,\n 'added_contacts_count' => count($contactsAdded),\n ]);\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-1275708447305567427
|
-8178087410993426138
|
app_switch
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
"podcast_audio_url"
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
0 results
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Code changed:
Hide
Sync Changes
Hide This Notification
32
2
19
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use HubSpot\Client\Crm\Deals\Model\CollectionResponseAssociatedId;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Models\Account;
use Exception;
use Jiminny\Component\DealInsights\Forecast\Forecast;
use Jiminny\Jobs\Crm\MatchActivitiesToNewOpportunity;
use Jiminny\Models\Contact;
use Jiminny\Models\Crm\BusinessProcess;
use Jiminny\Exceptions\CrmException;
use Jiminny\Models\Opportunity;
use Illuminate\Support\Collection;
use Jiminny\Models\Stage;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Crm\Hubspot\DealFieldsService;
use Jiminny\Services\Crm\Hubspot\OpportunitySyncStrategy\HubspotSingleSyncStrategy;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Services\Crm\OpportunitySyncStrategyResolver;
use Jiminny\Utils\CurrencyFormatter;
/**
* Optimized sync methods for better performance
* These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains
*/
trait OpportunitySyncTrait
{
private const int BATCH_SIZE = 100;
private const int BATCH_PROCESS_SIZE = 800;
protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;
protected CrmEntityRepository $crmEntityRepository;
protected DealFieldsService $dealFieldsService;
private ?array $cachedClosedDealStages = null;
private array $cachedBusinessProcesses = [];
private array $cachedStages = [];
public function syncOpportunities(array $parameters, ?string $strategy = null): int
{
$strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);
$parameters['config'] = $this->config;
$syncCount = 0;
$reportedTotal = 0;
$lastSyncedId = [];
try {
foreach ($strategies as $strategyName => $syncStrategy) {
$this->logger->info(
'[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .
$strategyName
);
$total = 0;
$lastId = null;
$buffer = [];
// HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies
foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {
$buffer[] = $hsOpportunity;
// process every 800 rows (fits < 1 000 association limit)
if (\count($buffer) >= self::BATCH_PROCESS_SIZE) {
$syncCount += $this->processOpportunityBatch($buffer);
$buffer = [];
}
}
// leftovers
if ($buffer) {
$syncCount += $this->processOpportunityBatch($buffer);
}
$reportedTotal += $total;
$lastSyncedId = $lastId;
}
} catch (\HubSpot\Client\Crm\Deals\ApiException | CrmException $e) {
$this->handleSyncException($e, $parameters);
}
$this->logger->info(
'[HubSpot] Synced opportunities',
[
'team' => $this->team->getId(),
'sync_count' => $syncCount,
'total' => $reportedTotal,
'last_synced_id' => $lastSyncedId,
]
);
return $reportedTotal;
}
private function handleSyncException(\Throwable $e, array $parameters): void
{
if (($parameters['since'] ?? null) instanceof Carbon) {
$parameters['since'] = $parameters['since']->toDateTimeString();
}
$parameters['config'] = $this->config->getId();
$this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [
'teamId' => $this->team->getUuid(),
'parameters' => $parameters,
'reason' => $e->getMessage(),
]);
}
/**
* @inheritdoc
*/
public function syncOpportunity(string $crmId): ?Opportunity
{
$strategy = $this->opportunitySyncStrategyResolver->resolve(
$this->config,
OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,
);
$parameters = [
'config' => $this->config,
'crm_id' => $crmId,
];
try {
if (! $strategy instanceof HubspotSingleSyncStrategy) {
throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');
}
$hsOpportunity = $strategy->fetchOpportunity($parameters);
} catch (\HubSpot\Client\Crm\Deals\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
$hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);
return $this->importOrUpdateOpportunity($hsOpportunity);
}
/**
* Process webhook-collected opportunity batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportOpportunityBatch jobs for batch processing.
*
* @return int Number of opportunity IDs dispatched to jobs
*/
public function batchSyncOpportunities(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,
$configId
);
}
/**
* Import a batch of opportunities by their CRM IDs.
* Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().
*
* @param array<string> $crmIds HubSpot deal CRM IDs
*
* @return array{success: array, failed_ids: array, errors?: array<string, string>}
*/
public function importOpportunityBatchByIds(array $crmIds): array
{
$fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);
$allDeals = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
$deals = $this->client->getOpportunitiesByIds($chunk, $fields);
foreach ($deals as $deal) {
$allDeals[] = $deal;
}
}
// IDs not returned by HubSpot are likely deleted or inaccessible deals.
// These are not failures — retrying won't bring them back.
$fetchedIds = array_map('strval', array_column($allDeals, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allDeals),
]);
}
if (empty($allDeals)) {
return ['success' => [], 'failed_ids' => []];
}
return $this->importOpportunityBatch($allDeals);
}
private function getClosedDealStages(): array
{
if ($this->cachedClosedDealStages !== null) {
return $this->cachedClosedDealStages;
}
$stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);
$data = [
'lost' => [],
'won' => [],
];
foreach ($stages as $stage) {
if ($stage->probability == 0.00) {
$data['lost'][] = $stage->crm_provider_id;
}
if ($stage->probability == 100.00) {
$data['won'][] = $stage->crm_provider_id;
}
}
$this->cachedClosedDealStages = $data;
return $data;
}
/**
* Import deals into the database with pre-fetched associations.
*
* API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT
* caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()
* where Laravel retries the whole job with backoff. After all retries exhausted,
* failed() requeues all IDs to Redis.
*
* The per-deal loop catches exceptions individually. A deal can end up in three states:
* - success: imported/updated successfully
* - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)
* These are permanent issues — retrying won't fix them.
* - skipped (null): missing dependencies (no account, unknown pipeline/stage).
* This is acceptable — the deal cannot be imported until those exist.
*/
private function importOpportunityBatch(array $deals): array
{
$syncedOpportunities = [
'success' => [],
'failed_ids' => [],
];
$dealIds = array_column($deals, 'id');
// Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the
// queue job retries the whole batch and eventually requeues all deal IDs back to Redis.
try {
$companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');
$contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');
$associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);
$existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(
$this->config,
array_map('strval', $dealIds)
);
$existingCrmIdSet = array_flip($existingCrmIds);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [
'teamId' => $this->team->getId(),
'dealCount' => count($dealIds),
'error' => $e->getMessage(),
]);
throw $e;
}
foreach ($deals as $deal) {
try {
$deal['associations'] = $this->prepareAssociationsForOpportunity(
$deal['id'],
$companyAssociations,
$contactAssociations,
$associationsData
);
$syncedOpportunity = $this->importOrUpdateOpportunity(
$deal,
isset($existingCrmIdSet[(string) $deal['id']])
);
if ($syncedOpportunity) {
$syncedOpportunities['success'][] = $syncedOpportunity;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [
'teamId' => $this->team->getId(),
'crmId' => $deal['id'],
'error' => $e->getMessage(),
]);
$syncedOpportunities['failed_ids'][] = $deal['id'];
$syncedOpportunities['errors'][$deal['id']] = $e->getMessage();
}
}
return $syncedOpportunities;
}
/**
* Prepare associated entities for opportunities with optimized batch processing
* Returns structured data with CRM ID to DB ID mappings for each opportunity
*/
private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array
{
// Step 1: Collect all unique company and contact IDs from associations
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
// Step 2: Batch sync missing entities and get CRM ID to DB ID mappings
$companyIdMappings = [];
$contactIdMappings = [];
if (! empty($allCompanyIds)) {
$companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);
}
if (! empty($allContactIds)) {
$contactIdMappings = $this->prepareAssociatedContacts($allContactIds);
}
return [
'company_id_mappings' => $companyIdMappings,
'contact_id_mappings' => $contactIdMappings,
];
}
/**
* Flatten association data to get unique IDs
*/
private function flattenAssociationIds(array $associations): array
{
$ids = [];
foreach ($associations as $dealAssociations) {
if (is_array($dealAssociations)) {
foreach ($dealAssociations as $id) {
$ids[$id] = true;
}
}
}
return array_keys($ids);
}
/**
* Batch sync missing accounts
*/
private function prepareAssociatedAccounts(array $companyIds): array
{
// Find which accounts already exist
$existingAccounts = $this->crmEntityRepository
->findAccountsByExternalIds($this->config, $companyIds);
$existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();
$existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {
return [$account->getCrmProviderId() => $account->getId()];
})->toArray();
$missingCompanyIds = array_diff($companyIds, $existingCompanyIds);
if (empty($missingCompanyIds)) {
return $existingAccountsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [
'teamId' => $this->team->getUuid(),
'total_companies' => count($companyIds),
'existing_companies' => count($existingCompanyIds),
'missing_companies' => count($missingCompanyIds),
]);
// we already have limit on opportunity ids count
// Initialize variable before try block
$syncedAccountsData = [];
try {
$syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [
'size' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
$syncedAccountsData = [];
}
return $existingAccountsData + $syncedAccountsData;
}
/**
* Prepare associated contacts - find existing and sync missing ones
* Returns mapping of CRM ID to DB ID
*/
private function prepareAssociatedContacts(array $contactIds): array
{
// Find which contacts already exist
$existingContacts = $this->crmEntityRepository
->findContactsByExternalIds($this->config, $contactIds);
$existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();
// Create mapping for existing contacts
$existingContactsData = $existingContacts->mapWithKeys(function ($contact) {
return [$contact->getCrmProviderId() => $contact->getId()];
})->toArray();
$missingContactIds = array_diff($contactIds, $existingContactIds);
if (empty($missingContactIds)) {
return $existingContactsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [
'teamId' => $this->team->getUuid(),
'total_contacts' => count($contactIds),
'existing_contacts' => count($existingContactIds),
'missing_contacts' => count($missingContactIds),
]);
// Sync missing contacts using batch API
try {
$syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [
'size' => count($missingContactIds),
'error' => $e->getMessage(),
]);
$syncedContactsData = [];
}
return $existingContactsData + $syncedContactsData;
}
private function batchSyncCrmObjects(string $objectType, array $crmIds): array
{
$syncObjects = [];
$crmObjectIds = array_values($crmIds);
foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {
try {
$objects = $objectType === 'companies' ?
$this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :
$this->client->getContactsByIds($chunk, $this->getContactFields());
foreach ($objects as $objectId => $objectData) {
$this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [
'requested_count' => count($chunk),
'synced_count' => count($objects),
]);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [
'ids' => $chunk,
'error' => $e->getMessage(),
]);
}
}
return $syncObjects;
}
private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void
{
try {
$object = $objectType === 'companies' ?
$this->importAccount($objectData) :
$this->importContact($objectData);
if ($object) {
$syncObjects[$object->getCrmProviderId()] = $object->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [
'id' => $objectId,
'error' => $e->getMessage(),
]);
}
}
/**
* Prepare associations for a single opportunity
*
* The return value is an array with the following structure:
* [
* 'companies' => [
* $companyCrmId => $companyId,
* ...
* ],
* 'contacts' => [
* $contactCrmId => $contactId,
* ...
* ],
* 'account_id' => $accountId,
* ]
*/
private function prepareAssociationsForOpportunity(
string $oppCrmId,
array $companyAssociations,
array $contactAssociations,
array $associationsData
): array {
$associations = [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
$oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];
foreach ($oppCompanyIds as $companyCrmId) {
if (isset($associationsData['company_id_mappings'][$companyCrmId])) {
$associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];
// Set primary account (first company becomes primary account)
if ($associations['account_id'] === null) {
$associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];
}
}
}
$oppContactIds = $contactAssociations[$oppCrmId] ?? [];
foreach ($oppContactIds as $contactCrmId) {
if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {
$associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];
}
}
return $associations;
}
/**
* Update only associations for an opportunity
*/
private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void
{
// Update contact associations
$this->importOpportunityContacts($opportunity, $associations['contacts']);
// Update company (account) associations
$this->updateOpportunityAccount($opportunity, $associations['account_id']);
}
/**
* Remove all contact associations from an opportunity
*/
private function removeAllOpportunityContacts(Opportunity $opportunity): void
{
$currentCount = (int) $opportunity->contacts()->count();
if ($currentCount > 0) {
$opportunity->contacts()->detach();
$this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_count' => $currentCount,
]);
}
}
private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void
{
if ($accountId === null) {
// No account ID provided - keep current account
return;
}
$currentAccountId = $opportunity->getAccountId();
// Only update if account has changed
if ($currentAccountId !== $accountId) {
$opportunity->account_id = $accountId;
$opportunity->save();
$this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [
'opportunity_id' => $opportunity->getId(),
'old_account_id' => $currentAccountId,
'new_account_id' => $accountId,
]);
}
}
/**
* Find existing opportunities by external IDs (OPTIMIZED VERSION)
* Uses batch query for better performance
*/
private function findExistingOpportunities(array $crmIds): Collection
{
return $this->crmEntityRepository
->findOpportunitiesByExternalIds($this->config, $crmIds);
}
private function processOpportunityBatch(array $opportunities): int
{
$syncedOpportunities = $this->importOpportunityBatch($opportunities);
return count($syncedOpportunities['success'] ?? []);
}
/**
* Convert single deal associations from HubSpot format to internal format
* Handles both HubSpot SDK objects and array formats
*
* @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed
*
* @return array Processed associations with DB IDs
*/
private function convertDealAssociations(array $opportunityAssociations): array
{
$associations = $this->initializeAssociationsStructure();
if (empty($opportunityAssociations)) {
return $associations;
}
$associationIds = $this->extractAssociationIds($opportunityAssociations);
$this->processCompanyAssociations($associationIds, $associations);
$this->processContactAssociations($associationIds, $associations);
return $associations;
}
private function initializeAssociationsStructure(): array
{
return [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
}
private function extractAssociationIds(array $opportunityAssociations): array
{
$associationIds = [];
foreach ($opportunityAssociations as $type => $associationData) {
if (! empty($associationData)) {
$associationIds[$type] = $this->convertSingleDealAssociations($associationData);
}
}
return $associationIds;
}
private function processCompanyAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['companies'])) {
return;
}
$companyId = $associationIds['companies'][0];
$account = $this->findOrSyncAccount($companyId);
if ($account instanceof Account) {
$associations['companies'][$companyId] = $account->getId();
$associations['account_id'] = $account->getId();
}
}
private function processContactAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['contacts'])) {
return;
}
foreach ($associationIds['contacts'] as $contactId) {
$contact = $this->findOrSyncContact($contactId);
if ($contact instanceof Contact) {
$associations['contacts'][$contactId] = $contact->getId();
}
}
}
private function findOrSyncAccount(string $companyId): ?Account
{
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);
if (! $account instanceof Account) {
$account = $this->syncAccount($companyId);
}
return $account;
}
private function findOrSyncContact(string $contactId): ?Contact
{
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);
if (! $contact instanceof Contact) {
$contact = $this->syncContact($contactId);
}
return $contact;
}
private function convertSingleDealAssociations($opportunityAssociations = null): array
{
$associationData = [];
if ($opportunityAssociations === null) {
return $associationData;
}
// Handle array input (from extractAssociationIds)
if (is_array($opportunityAssociations)) {
return $opportunityAssociations;
}
// Handle CollectionResponseAssociatedId object
if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {
foreach ($opportunityAssociations->getResults() as $association) {
$associationData[] = $association->getId();
}
}
return $associationData;
}
private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity
{
if (empty($crmData['properties'])) {
return null;
}
$crmId = (string) $crmData['id'];
$properties = $crmData['properties'];
$associations = $crmData['associations'] ?? [];
$opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(
$this->config,
$crmId
);
if ($opportunityExists) {
return $this->updateOpportunity($crmId, $properties, $associations);
} else {
return $this->createOpportunity($crmId, $properties, $associations);
}
}
/**
* Create new opportunity
*/
private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity
{
$accountId = $this->resolveAccountId($associations);
if (! $accountId) {
return null;
}
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
if (! $businessProcess) {
return null;
}
$stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);
if (! $stage) {
return null;
}
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->importOpportunityContacts($opportunity, $associations['contacts']);
if ($opportunity->wasRecentlyCreated) {
MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());
}
return $opportunity;
}
/**
* Update existing opportunity
*/
private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity
{
$accountId = $this->resolveAccountId($associations);
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
$stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->updateOpportunityAssociations($opportunity, $associations);
return $opportunity;
}
private function resolveAccountId(array $associations): ?int
{
if (! empty($associations['accountId'])) {
return $associations['accountId'];
}
if (empty($associations)) {
return null;
}
// we can't resolve multiple account ids (currently SDK returns one company)
foreach ($associations['companies'] as $accountId) {
return $accountId;
}
return null;
}
private function buildOpportunityData(
array $properties,
?int $accountId,
?BusinessProcess $businessProcess,
?Stage $stage
): array {
$ownerId = null;
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$name = 'Unknown';
if (isset($properties['dealname'])) {
$name = mb_strimwidth($properties['dealname'], 0, 128);
}
$amount = $this->resolveAmount($properties);
$currency = $properties['deal_currency_code'] ?? null;
$closeDate = null;
if (! empty($properties['closedate'])) {
$closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');
}
$remotelyCreatedAt = null;
if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {
$date = $this->parseCleanDatetime($properties['createdate']);
$remotelyCreatedAt = $date?->format('Y-m-d H:i:s');
}
$closedStages = $this->getClosedDealStages();
$isWon = in_array($properties['dealstage'], $closedStages['won']);
$isLost = in_array($properties['dealstage'], $closedStages['lost']);
$data = [
'team_id' => $this->team->getId(),
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => $name,
'value' => ! empty($amount) ? $amount : null,
'currency_code' => CurrencyFormatter::formatCode($currency),
'close_date' => $closeDate,
'is_closed' => $isWon || $isLost,
'is_won' => $isWon,
'remotely_created_at' => $remotelyCreatedAt,
'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),
'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),
];
if ($accountId) {
$data['account_id'] = $accountId;
}
if ($stage) {
$data['stage_id'] = $stage->id;
}
if ($businessProcess) {
$recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);
if ($recordType) {
$data['record_type_id'] = $recordType->id;
}
}
return $data;
}
private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess
{
if ($pipelineId === null) {
return null;
}
if (isset($this->cachedBusinessProcesses[$pipelineId])) {
return $this->cachedBusinessProcesses[$pipelineId];
}
$businessProcess = $this->getBusinessProcess($pipelineId);
if (! $businessProcess instanceof BusinessProcess) {
$this->importStages();
$businessProcess = $this->getBusinessProcess($pipelineId);
}
if (! $businessProcess instanceof BusinessProcess) {
$this->logger->info(
'[HubSpot] Deal is not attached to a pipeline',
[
'pipeline' => $pipelineId]
);
}
$this->cachedBusinessProcesses[$pipelineId] = $businessProcess;
return $businessProcess;
}
private function getBusinessProcess(string $pipelineId): ?BusinessProcess
{
return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);
}
private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage
{
if (empty($stageId)) {
return null;
}
$cacheKey = $businessProcess->getId() . ':' . $stageId;
if (isset($this->cachedStages[$cacheKey])) {
return $this->cachedStages[$cacheKey];
}
$stage = $this->crmEntityRepository->getPipelineStageByConditions(
$businessProcess,
[
'crm_provider_id' => $stageId,
'type' => Stage::TYPE_OPPORTUNITY,
]
);
if ($stage === null) {
$this->importStages(null, $stageId);
}
if ($stage === null) {
$this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);
}
$this->cachedStages[$cacheKey] = $stage;
return $stage;
}
private function resolveAmount(array $properties): ?string
{
$amount = null;
if (! empty($properties['amount'])) {
$amount = str_replace(',', '', $properties['amount']);
}
if ($this->config->hasDefaultCurrencyFieldSet()) {
$valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();
$amount = $properties[$valueFieldName] ?? $amount;
}
return $amount;
}
private function parseCleanDatetime(string $datetime): ?Carbon
{
// Treat pre-1980 values as invalid
$minValidDate = Carbon::parse('1980-01-01 00:00:00');
try {
$date = Carbon::parse($datetime);
if ($minValidDate->gt($date)) {
return null;
}
return $date;
} catch (Exception) {
return null; // On parse error, treat as null
}
}
private function resolveDealProbability(?string $stageProbability): int
{
if ($stageProbability === null) {
return 0;
}
$probability = (float) $stageProbability;
return $probability > 1 ? 0 : (int) ($probability * 100);
}
private function resolveForecastCategory(?string $forecastCategory): string
{
if (! $forecastCategory) {
return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;
}
$forecastCategory = str_replace('_', ' ', $forecastCategory);
return ucwords(strtolower($forecastCategory));
}
private function importExternalFieldData(array $properties, int $opportunityId): void
{
$crmFields = $this->getOpportunitySyncableFields();
$this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);
}
private function importOpportunityContacts(Opportunity $opportunity, array $associations): void
{
// Handle empty or missing contact associations
if (empty($associations)) {
// Remove all existing contact associations if none provided
$this->removeAllOpportunityContacts($opportunity);
return;
}
// Use differential sync approach for better performance and accuracy
$this->syncOpportunityContactsDifferential($opportunity, $associations);
}
/**
* Sync opportunity contacts using differential approach
* This compares current vs new associations and only makes necessary changes
*/
private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void
{
$currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);
$contactAssociationIds = array_keys($contactAssociations);
$contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);
$contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);
if (empty($contactsToAdd) && empty($contactsToRemove)) {
return;
}
$this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);
$this->removeContactAssociations($opportunity, $contactsToRemove);
$this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);
}
private function getCurrentContactCrmIds(Opportunity $opportunity): array
{
return $opportunity->contacts()
->pluck('contacts.crm_provider_id')
->toArray();
}
private function logContactAssociationChanges(
Opportunity $opportunity,
array $currentContactCrmIds,
array $contactAssociations,
array $contactsToAdd,
array $contactsToRemove
): void {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [
'opportunity_id' => $opportunity->getId(),
'current_contacts' => $currentContactCrmIds,
'new_contacts' => $contactAssociations,
'contacts_to_add' => $contactsToAdd,
'contacts_to_remove' => $contactsToRemove,
]);
}
private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void
{
if (empty($contactsToRemove)) {
return;
}
$contactsToDetach = $opportunity->contacts()
->whereIn('contacts.crm_provider_id', $contactsToRemove)
->pluck('contacts.id')
->toArray();
if (! empty($contactsToDetach)) {
$opportunity->contacts()->detach($contactsToDetach);
$this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_contact_crm_ids' => $contactsToRemove,
'removed_contact_count' => count($contactsToDetach),
]);
}
}
private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void
{
if (empty($contactsToAdd)) {
return;
}
$contactsAdded = [];
foreach ($contactsToAdd as $crmId) {
$id = $contactAssociations[$crmId];
if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {
$contactsAdded[] = $crmId;
}
}
$this->logAddedContacts($opportunity, $contactsAdded);
}
private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool
{
try {
$contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);
if (! $contact) {
return false;
}
return $this->performContactAttachment($opportunity, $contact, $crmId);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [
'opportunity_id' => $opportunity->getId(),
'contact_crm_id' => $crmId,
'error' => $e->getMessage(),
]);
return false;
}
}
private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool
{
try {
$opportunity->contacts()->attach($contact->getId(), [
'crm_provider_id' => $crmId,
]);
return true;
} catch (\Illuminate\Database\QueryException $e) {
if (str_contains($e->getMessage(), 'Duplicate entry')) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [
'contact_id' => $contact->getId(),
'contact_crm_id' => $crmId,
'opportunity_id' => $opportunity->getId(),
]);
return false;
}
throw $e;
}
}
private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void
{
if (! empty($contactsAdded)) {
$this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [
'opportunity_id' => $opportunity->getId(),
'contacts_to_add_count' => count($contactsAdded),
'added_contact_crm_ids' => $contactsAdded,
'added_contacts_count' => count($contactsAdded),
]);
}
}
}
Execute
Explain Plan...
|
NULL
|
|
43325
|
923
|
4
|
2026-04-17T08:03:18.934253+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776412998934_m2.jpg...
|
PhpStorm
|
faVsco.js – OpportunitySyncTrait.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
"podcast_audio_url"
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
0 results
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Code changed:
Hide
Sync Changes
Hide This Notification
32
2
19
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use HubSpot\Client\Crm\Deals\Model\CollectionResponseAssociatedId;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Models\Account;
use Exception;
use Jiminny\Component\DealInsights\Forecast\Forecast;
use Jiminny\Jobs\Crm\MatchActivitiesToNewOpportunity;
use Jiminny\Models\Contact;
use Jiminny\Models\Crm\BusinessProcess;
use Jiminny\Exceptions\CrmException;
use Jiminny\Models\Opportunity;
use Illuminate\Support\Collection;
use Jiminny\Models\Stage;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Crm\Hubspot\DealFieldsService;
use Jiminny\Services\Crm\Hubspot\OpportunitySyncStrategy\HubspotSingleSyncStrategy;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Services\Crm\OpportunitySyncStrategyResolver;
use Jiminny\Utils\CurrencyFormatter;
/**
* Optimized sync methods for better performance
* These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains
*/
trait OpportunitySyncTrait
{
private const int BATCH_SIZE = 100;
private const int BATCH_PROCESS_SIZE = 800;
protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;
protected CrmEntityRepository $crmEntityRepository;
protected DealFieldsService $dealFieldsService;
private ?array $cachedClosedDealStages = null;
private array $cachedBusinessProcesses = [];
private array $cachedStages = [];
public function syncOpportunities(array $parameters, ?string $strategy = null): int
{
$strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);
$parameters['config'] = $this->config;
$syncCount = 0;
$reportedTotal = 0;
$lastSyncedId = [];
try {
foreach ($strategies as $strategyName => $syncStrategy) {
$this->logger->info(
'[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .
$strategyName
);
$total = 0;
$lastId = null;
$buffer = [];
// HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies
foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {
$buffer[] = $hsOpportunity;
// process every 800 rows (fits < 1 000 association limit)
if (\count($buffer) >= self::BATCH_PROCESS_SIZE) {
$syncCount += $this->processOpportunityBatch($buffer);
$buffer = [];
}
}
// leftovers
if ($buffer) {
$syncCount += $this->processOpportunityBatch($buffer);
}
$reportedTotal += $total;
$lastSyncedId = $lastId;
}
} catch (\HubSpot\Client\Crm\Deals\ApiException | CrmException $e) {
$this->handleSyncException($e, $parameters);
}
$this->logger->info(
'[HubSpot] Synced opportunities',
[
'team' => $this->team->getId(),
'sync_count' => $syncCount,
'total' => $reportedTotal,
'last_synced_id' => $lastSyncedId,
]
);
return $reportedTotal;
}
private function handleSyncException(\Throwable $e, array $parameters): void
{
if (($parameters['since'] ?? null) instanceof Carbon) {
$parameters['since'] = $parameters['since']->toDateTimeString();
}
$parameters['config'] = $this->config->getId();
$this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [
'teamId' => $this->team->getUuid(),
'parameters' => $parameters,
'reason' => $e->getMessage(),
]);
}
/**
* @inheritdoc
*/
public function syncOpportunity(string $crmId): ?Opportunity
{
$strategy = $this->opportunitySyncStrategyResolver->resolve(
$this->config,
OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,
);
$parameters = [
'config' => $this->config,
'crm_id' => $crmId,
];
try {
if (! $strategy instanceof HubspotSingleSyncStrategy) {
throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');
}
$hsOpportunity = $strategy->fetchOpportunity($parameters);
} catch (\HubSpot\Client\Crm\Deals\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
$hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);
return $this->importOrUpdateOpportunity($hsOpportunity);
}
/**
* Process webhook-collected opportunity batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportOpportunityBatch jobs for batch processing.
*
* @return int Number of opportunity IDs dispatched to jobs
*/
public function batchSyncOpportunities(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,
$configId
);
}
/**
* Import a batch of opportunities by their CRM IDs.
* Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().
*
* @param array<string> $crmIds HubSpot deal CRM IDs
*
* @return array{success: array, failed_ids: array, errors?: array<string, string>}
*/
public function importOpportunityBatchByIds(array $crmIds): array
{
$fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);
$allDeals = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
$deals = $this->client->getOpportunitiesByIds($chunk, $fields);
foreach ($deals as $deal) {
$allDeals[] = $deal;
}
}
// IDs not returned by HubSpot are likely deleted or inaccessible deals.
// These are not failures — retrying won't bring them back.
$fetchedIds = array_map('strval', array_column($allDeals, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allDeals),
]);
}
if (empty($allDeals)) {
return ['success' => [], 'failed_ids' => []];
}
return $this->importOpportunityBatch($allDeals);
}
private function getClosedDealStages(): array
{
if ($this->cachedClosedDealStages !== null) {
return $this->cachedClosedDealStages;
}
$stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);
$data = [
'lost' => [],
'won' => [],
];
foreach ($stages as $stage) {
if ($stage->probability == 0.00) {
$data['lost'][] = $stage->crm_provider_id;
}
if ($stage->probability == 100.00) {
$data['won'][] = $stage->crm_provider_id;
}
}
$this->cachedClosedDealStages = $data;
return $data;
}
/**
* Import deals into the database with pre-fetched associations.
*
* API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT
* caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()
* where Laravel retries the whole job with backoff. After all retries exhausted,
* failed() requeues all IDs to Redis.
*
* The per-deal loop catches exceptions individually. A deal can end up in three states:
* - success: imported/updated successfully
* - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)
* These are permanent issues — retrying won't fix them.
* - skipped (null): missing dependencies (no account, unknown pipeline/stage).
* This is acceptable — the deal cannot be imported until those exist.
*/
private function importOpportunityBatch(array $deals): array
{
$syncedOpportunities = [
'success' => [],
'failed_ids' => [],
];
$dealIds = array_column($deals, 'id');
// Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the
// queue job retries the whole batch and eventually requeues all deal IDs back to Redis.
try {
$companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');
$contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');
$associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);
$existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(
$this->config,
array_map('strval', $dealIds)
);
$existingCrmIdSet = array_flip($existingCrmIds);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [
'teamId' => $this->team->getId(),
'dealCount' => count($dealIds),
'error' => $e->getMessage(),
]);
throw $e;
}
foreach ($deals as $deal) {
try {
$deal['associations'] = $this->prepareAssociationsForOpportunity(
$deal['id'],
$companyAssociations,
$contactAssociations,
$associationsData
);
$syncedOpportunity = $this->importOrUpdateOpportunity(
$deal,
isset($existingCrmIdSet[(string) $deal['id']])
);
if ($syncedOpportunity) {
$syncedOpportunities['success'][] = $syncedOpportunity;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [
'teamId' => $this->team->getId(),
'crmId' => $deal['id'],
'error' => $e->getMessage(),
]);
$syncedOpportunities['failed_ids'][] = $deal['id'];
$syncedOpportunities['errors'][$deal['id']] = $e->getMessage();
}
}
return $syncedOpportunities;
}
/**
* Prepare associated entities for opportunities with optimized batch processing
* Returns structured data with CRM ID to DB ID mappings for each opportunity
*/
private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array
{
// Step 1: Collect all unique company and contact IDs from associations
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
// Step 2: Batch sync missing entities and get CRM ID to DB ID mappings
$companyIdMappings = [];
$contactIdMappings = [];
if (! empty($allCompanyIds)) {
$companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);
}
if (! empty($allContactIds)) {
$contactIdMappings = $this->prepareAssociatedContacts($allContactIds);
}
return [
'company_id_mappings' => $companyIdMappings,
'contact_id_mappings' => $contactIdMappings,
];
}
/**
* Flatten association data to get unique IDs
*/
private function flattenAssociationIds(array $associations): array
{
$ids = [];
foreach ($associations as $dealAssociations) {
if (is_array($dealAssociations)) {
foreach ($dealAssociations as $id) {
$ids[$id] = true;
}
}
}
return array_keys($ids);
}
/**
* Batch sync missing accounts
*/
private function prepareAssociatedAccounts(array $companyIds): array
{
// Find which accounts already exist
$existingAccounts = $this->crmEntityRepository
->findAccountsByExternalIds($this->config, $companyIds);
$existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();
$existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {
return [$account->getCrmProviderId() => $account->getId()];
})->toArray();
$missingCompanyIds = array_diff($companyIds, $existingCompanyIds);
if (empty($missingCompanyIds)) {
return $existingAccountsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [
'teamId' => $this->team->getUuid(),
'total_companies' => count($companyIds),
'existing_companies' => count($existingCompanyIds),
'missing_companies' => count($missingCompanyIds),
]);
// we already have limit on opportunity ids count
// Initialize variable before try block
$syncedAccountsData = [];
try {
$syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [
'size' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
$syncedAccountsData = [];
}
return $existingAccountsData + $syncedAccountsData;
}
/**
* Prepare associated contacts - find existing and sync missing ones
* Returns mapping of CRM ID to DB ID
*/
private function prepareAssociatedContacts(array $contactIds): array
{
// Find which contacts already exist
$existingContacts = $this->crmEntityRepository
->findContactsByExternalIds($this->config, $contactIds);
$existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();
// Create mapping for existing contacts
$existingContactsData = $existingContacts->mapWithKeys(function ($contact) {
return [$contact->getCrmProviderId() => $contact->getId()];
})->toArray();
$missingContactIds = array_diff($contactIds, $existingContactIds);
if (empty($missingContactIds)) {
return $existingContactsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [
'teamId' => $this->team->getUuid(),
'total_contacts' => count($contactIds),
'existing_contacts' => count($existingContactIds),
'missing_contacts' => count($missingContactIds),
]);
// Sync missing contacts using batch API
try {
$syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [
'size' => count($missingContactIds),
'error' => $e->getMessage(),
]);
$syncedContactsData = [];
}
return $existingContactsData + $syncedContactsData;
}
private function batchSyncCrmObjects(string $objectType, array $crmIds): array
{
$syncObjects = [];
$crmObjectIds = array_values($crmIds);
foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {
try {
$objects = $objectType === 'companies' ?
$this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :
$this->client->getContactsByIds($chunk, $this->getContactFields());
foreach ($objects as $objectId => $objectData) {
$this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [
'requested_count' => count($chunk),
'synced_count' => count($objects),
]);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [
'ids' => $chunk,
'error' => $e->getMessage(),
]);
}
}
return $syncObjects;
}
private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void
{
try {
$object = $objectType === 'companies' ?
$this->importAccount($objectData) :
$this->importContact($objectData);
if ($object) {
$syncObjects[$object->getCrmProviderId()] = $object->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [
'id' => $objectId,
'error' => $e->getMessage(),
]);
}
}
/**
* Prepare associations for a single opportunity
*
* The return value is an array with the following structure:
* [
* 'companies' => [
* $companyCrmId => $companyId,
* ...
* ],
* 'contacts' => [
* $contactCrmId => $contactId,
* ...
* ],
* 'account_id' => $accountId,
* ]
*/
private function prepareAssociationsForOpportunity(
string $oppCrmId,
array $companyAssociations,
array $contactAssociations,
array $associationsData
): array {
$associations = [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
$oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];
foreach ($oppCompanyIds as $companyCrmId) {
if (isset($associationsData['company_id_mappings'][$companyCrmId])) {
$associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];
// Set primary account (first company becomes primary account)
if ($associations['account_id'] === null) {
$associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];
}
}
}
$oppContactIds = $contactAssociations[$oppCrmId] ?? [];
foreach ($oppContactIds as $contactCrmId) {
if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {
$associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];
}
}
return $associations;
}
/**
* Update only associations for an opportunity
*/
private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void
{
// Update contact associations
$this->importOpportunityContacts($opportunity, $associations['contacts']);
// Update company (account) associations
$this->updateOpportunityAccount($opportunity, $associations['account_id']);
}
/**
* Remove all contact associations from an opportunity
*/
private function removeAllOpportunityContacts(Opportunity $opportunity): void
{
$currentCount = (int) $opportunity->contacts()->count();
if ($currentCount > 0) {
$opportunity->contacts()->detach();
$this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_count' => $currentCount,
]);
}
}
private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void
{
if ($accountId === null) {
// No account ID provided - keep current account
return;
}
$currentAccountId = $opportunity->getAccountId();
// Only update if account has changed
if ($currentAccountId !== $accountId) {
$opportunity->account_id = $accountId;
$opportunity->save();
$this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [
'opportunity_id' => $opportunity->getId(),
'old_account_id' => $currentAccountId,
'new_account_id' => $accountId,
]);
}
}
/**
* Find existing opportunities by external IDs (OPTIMIZED VERSION)
* Uses batch query for better performance
*/
private function findExistingOpportunities(array $crmIds): Collection
{
return $this->crmEntityRepository
->findOpportunitiesByExternalIds($this->config, $crmIds);
}
private function processOpportunityBatch(array $opportunities): int
{
$syncedOpportunities = $this->importOpportunityBatch($opportunities);
return count($syncedOpportunities['success'] ?? []);
}
/**
* Convert single deal associations from HubSpot format to internal format
* Handles both HubSpot SDK objects and array formats
*
* @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed
*
* @return array Processed associations with DB IDs
*/
private function convertDealAssociations(array $opportunityAssociations): array
{
$associations = $this->initializeAssociationsStructure();
if (empty($opportunityAssociations)) {
return $associations;
}
$associationIds = $this->extractAssociationIds($opportunityAssociations);
$this->processCompanyAssociations($associationIds, $associations);
$this->processContactAssociations($associationIds, $associations);
return $associations;
}
private function initializeAssociationsStructure(): array
{
return [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
}
private function extractAssociationIds(array $opportunityAssociations): array
{
$associationIds = [];
foreach ($opportunityAssociations as $type => $associationData) {
if (! empty($associationData)) {
$associationIds[$type] = $this->convertSingleDealAssociations($associationData);
}
}
return $associationIds;
}
private function processCompanyAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['companies'])) {
return;
}
$companyId = $associationIds['companies'][0];
$account = $this->findOrSyncAccount($companyId);
if ($account instanceof Account) {
$associations['companies'][$companyId] = $account->getId();
$associations['account_id'] = $account->getId();
}
}
private function processContactAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['contacts'])) {
return;
}
foreach ($associationIds['contacts'] as $contactId) {
$contact = $this->findOrSyncContact($contactId);
if ($contact instanceof Contact) {
$associations['contacts'][$contactId] = $contact->getId();
}
}
}
private function findOrSyncAccount(string $companyId): ?Account
{
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);
if (! $account instanceof Account) {
$account = $this->syncAccount($companyId);
}
return $account;
}
private function findOrSyncContact(string $contactId): ?Contact
{
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);
if (! $contact instanceof Contact) {
$contact = $this->syncContact($contactId);
}
return $contact;
}
private function convertSingleDealAssociations($opportunityAssociations = null): array
{
$associationData = [];
if ($opportunityAssociations === null) {
return $associationData;
}
// Handle array input (from extractAssociationIds)
if (is_array($opportunityAssociations)) {
return $opportunityAssociations;
}
// Handle CollectionResponseAssociatedId object
if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {
foreach ($opportunityAssociations->getResults() as $association) {
$associationData[] = $association->getId();
}
}
return $associationData;
}
private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity
{
if (empty($crmData['properties'])) {
return null;
}
$crmId = (string) $crmData['id'];
$properties = $crmData['properties'];
$associations = $crmData['associations'] ?? [];
$opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(
$this->config,
$crmId
);
if ($opportunityExists) {
return $this->updateOpportunity($crmId, $properties, $associations);
} else {
return $this->createOpportunity($crmId, $properties, $associations);
}
}
/**
* Create new opportunity
*/
private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity
{
$accountId = $this->resolveAccountId($associations);
if (! $accountId) {
return null;
}
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
if (! $businessProcess) {
return null;
}
$stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);
if (! $stage) {
return null;
}
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->importOpportunityContacts($opportunity, $associations['contacts']);
if ($opportunity->wasRecentlyCreated) {
MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());
}
return $opportunity;
}
/**
* Update existing opportunity
*/
private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity
{
$accountId = $this->resolveAccountId($associations);
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
$stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->updateOpportunityAssociations($opportunity, $associations);
return $opportunity;
}
private function resolveAccountId(array $associations): ?int
{
if (! empty($associations['accountId'])) {
return $associations['accountId'];
}
if (empty($associations)) {
return null;
}
// we can't resolve multiple account ids (currently SDK returns one company)
foreach ($associations['companies'] as $accountId) {
return $accountId;
}
return null;
}
private function buildOpportunityData(
array $properties,
?int $accountId,
?BusinessProcess $businessProcess,
?Stage $stage
): array {
$ownerId = null;
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$name = 'Unknown';
if (isset($properties['dealname'])) {
$name = mb_strimwidth($properties['dealname'], 0, 128);
}
$amount = $this->resolveAmount($properties);
$currency = $properties['deal_currency_code'] ?? null;
$closeDate = null;
if (! empty($properties['closedate'])) {
$closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');
}
$remotelyCreatedAt = null;
if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {
$date = $this->parseCleanDatetime($properties['createdate']);
$remotelyCreatedAt = $date?->format('Y-m-d H:i:s');
}
$closedStages = $this->getClosedDealStages();
$isWon = in_array($properties['dealstage'], $closedStages['won']);
$isLost = in_array($properties['dealstage'], $closedStages['lost']);
$data = [
'team_id' => $this->team->getId(),
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => $name,
'value' => ! empty($amount) ? $amount : null,
'currency_code' => CurrencyFormatter::formatCode($currency),
'close_date' => $closeDate,
'is_closed' => $isWon || $isLost,
'is_won' => $isWon,
'remotely_created_at' => $remotelyCreatedAt,
'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),
'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),
];
if ($accountId) {
$data['account_id'] = $accountId;
}
if ($stage) {
$data['stage_id'] = $stage->id;
}
if ($businessProcess) {
$recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);
if ($recordType) {
$data['record_type_id'] = $recordType->id;
}
}
return $data;
}
private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess
{
if ($pipelineId === null) {
return null;
}
if (isset($this->cachedBusinessProcesses[$pipelineId])) {
return $this->cachedBusinessProcesses[$pipelineId];
}
$businessProcess = $this->getBusinessProcess($pipelineId);
if (! $businessProcess instanceof BusinessProcess) {
$this->importStages();
$businessProcess = $this->getBusinessProcess($pipelineId);
}
if (! $businessProcess instanceof BusinessProcess) {
$this->logger->info(
'[HubSpot] Deal is not attached to a pipeline',
[
'pipeline' => $pipelineId]
);
}
$this->cachedBusinessProcesses[$pipelineId] = $businessProcess;
return $businessProcess;
}
private function getBusinessProcess(string $pipelineId): ?BusinessProcess
{
return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);
}
private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage
{
if (empty($stageId)) {
return null;
}
$cacheKey = $businessProcess->getId() . ':' . $stageId;
if (isset($this->cachedStages[$cacheKey])) {
return $this->cachedStages[$cacheKey];
}
$stage = $this->crmEntityRepository->getPipelineStageByConditions(
$businessProcess,
[
'crm_provider_id' => $stageId,
'type' => Stage::TYPE_OPPORTUNITY,
]
);
if ($stage === null) {
$this->importStages(null, $stageId);
}
if ($stage === null) {
$this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);
}
$this->cachedStages[$cacheKey] = $stage;
return $stage;
}
private function resolveAmount(array $properties): ?string
{
$amount = null;
if (! empty($properties['amount'])) {
$amount = str_replace(',', '', $properties['amount']);
}
if ($this->config->hasDefaultCurrencyFieldSet()) {
$valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();
$amount = $properties[$valueFieldName] ?? $amount;
}
return $amount;
}
private function parseCleanDatetime(string $datetime): ?Carbon
{
// Treat pre-1980 values as invalid
$minValidDate = Carbon::parse('1980-01-01 00:00:00');
try {
$date = Carbon::parse($datetime);
if ($minValidDate->gt($date)) {
return null;
}
return $date;
} catch (Exception) {
return null; // On parse error, treat as null
}
}
private function resolveDealProbability(?string $stageProbability): int
{
if ($stageProbability === null) {
return 0;
}
$probability = (float) $stageProbability;
return $probability > 1 ? 0 : (int) ($probability * 100);
}
private function resolveForecastCategory(?string $forecastCategory): string
{
if (! $forecastCategory) {
return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;
}
$forecastCategory = str_replace('_', ' ', $forecastCategory);
return ucwords(strtolower($forecastCategory));
}
private function importExternalFieldData(array $properties, int $opportunityId): void
{
$crmFields = $this->getOpportunitySyncableFields();
$this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);
}
private function importOpportunityContacts(Opportunity $opportunity, array $associations): void
{
// Handle empty or missing contact associations
if (empty($associations)) {
// Remove all existing contact associations if none provided
$this->removeAllOpportunityContacts($opportunity);
return;
}
// Use differential sync approach for better performance and accuracy
$this->syncOpportunityContactsDifferential($opportunity, $associations);
}
/**
* Sync opportunity contacts using differential approach
* This compares current vs new associations and only makes necessary changes
*/
private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void
{
$currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);
$contactAssociationIds = array_keys($contactAssociations);
$contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);
$contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);
if (empty($contactsToAdd) && empty($contactsToRemove)) {
return;
}
$this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);
$this->removeContactAssociations($opportunity, $contactsToRemove);
$this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);
}
private function getCurrentContactCrmIds(Opportunity $opportunity): array
{
return $opportunity->contacts()
->pluck('contacts.crm_provider_id')
->toArray();
}
private function logContactAssociationChanges(
Opportunity $opportunity,
array $currentContactCrmIds,
array $contactAssociations,
array $contactsToAdd,
array $contactsToRemove
): void {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [
'opportunity_id' => $opportunity->getId(),
'current_contacts' => $currentContactCrmIds,
'new_contacts' => $contactAssociations,
'contacts_to_add' => $contactsToAdd,
'contacts_to_remove' => $contactsToRemove,
]);
}
private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void
{
if (empty($contactsToRemove)) {
return;
}
$contactsToDetach = $opportunity->contacts()
->whereIn('contacts.crm_provider_id', $contactsToRemove)
->pluck('contacts.id')
->toArray();
if (! empty($contactsToDetach)) {
$opportunity->contacts()->detach($contactsToDetach);
$this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_contact_crm_ids' => $contactsToRemove,
'removed_contact_count' => count($contactsToDetach),
]);
}
}
private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void
{
if (empty($contactsToAdd)) {
return;
}
$contactsAdded = [];
foreach ($contactsToAdd as $crmId) {
$id = $contactAssociations[$crmId];
if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {
$contactsAdded[] = $crmId;
}
}
$this->logAddedContacts($opportunity, $contactsAdded);
}
private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool
{
try {
$contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);
if (! $contact) {
return false;
}
return $this->performContactAttachment($opportunity, $contact, $crmId);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [
'opportunity_id' => $opportunity->getId(),
'contact_crm_id' => $crmId,
'error' => $e->getMessage(),
]);
return false;
}
}
private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool
{
try {
$opportunity->contacts()->attach($contact->getId(), [
'crm_provider_id' => $crmId,
]);
return true;
} catch (\Illuminate\Database\QueryException $e) {
if (str_contains($e->getMessage(), 'Duplicate entry')) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [
'contact_id' => $contact->getId(),
'contact_crm_id' => $crmId,
'opportunity_id' => $opportunity->getId(),
]);
return false;
}
throw $e;
}
}
private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void
{
if (! empty($contactsAdded)) {
$this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [
'opportunity_id' => $opportunity->getId(),
'contacts_to_add_count' => count($contactsAdded),
'added_contact_crm_ids' => $contactsAdded,
'added_contacts_count' => count($contactsAdded),
]);
}
}
}
Execute
Explain Plan
Browse Query History...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.03046875,"top":0.017361112,"width":0.0453125,"height":0.022222223},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"bounds":{"left":0.07578125,"top":0.017361112,"width":0.14960937,"height":0.022222223},"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.78515625,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsCommandTest","depth":6,"bounds":{"left":0.803125,"top":0.017361112,"width":0.09765625,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsCommandTest'","depth":6,"bounds":{"left":0.9007813,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsCommandTest'","depth":6,"bounds":{"left":0.9140625,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9273437,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96015626,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9734375,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9867188,"top":0.017361112,"width":0.013281226,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show Replace Field","depth":4,"bounds":{"left":0.12382813,"top":0.19930555,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Search History","depth":3,"bounds":{"left":0.13867188,"top":0.19861111,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"\"podcast_audio_url\"","depth":4,"bounds":{"left":0.1515625,"top":0.19861111,"width":0.06367187,"height":0.013888889},"value":"\"podcast_audio_url\"","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"bounds":{"left":0.22578125,"top":0.19861111,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Match Case","depth":3,"bounds":{"left":0.2375,"top":0.19861111,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Words","depth":3,"bounds":{"left":0.24765626,"top":0.19861111,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Regex","depth":3,"bounds":{"left":0.2578125,"top":0.19861111,"width":0.00859375,"height":0.015277778},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Replace History","depth":3,"bounds":{"left":0.23320313,"top":1.0,"width":0.00859375,"height":0.0},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Replace","depth":4,"role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"bounds":{"left":0.23320313,"top":1.0,"width":0.00859375,"height":0.0},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Preserve case","depth":3,"bounds":{"left":0.23320313,"top":1.0,"width":0.00859375,"height":0.0},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"0 results","depth":4,"bounds":{"left":0.27382812,"top":0.19791667,"width":0.030078124,"height":0.015277778},"role_description":"text"},{"role":"AXButton","text":"Previous Occurrence","depth":4,"bounds":{"left":0.30390626,"top":0.19722222,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Occurrence","depth":4,"bounds":{"left":0.3140625,"top":0.19722222,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Filter Search Results","depth":4,"bounds":{"left":0.32421875,"top":0.19722222,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open in Window, Multiple Cursors","depth":4,"bounds":{"left":0.334375,"top":0.19722222,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Click to highlight","depth":4,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":4,"bounds":{"left":0.46640626,"top":0.19722222,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.049609374,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"32","depth":4,"bounds":{"left":0.42539063,"top":0.22430556,"width":0.012109375,"height":0.013194445},"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.43984374,"top":0.22430556,"width":0.009375,"height":0.013194445},"role_description":"text"},{"role":"AXStaticText","text":"19","depth":4,"bounds":{"left":0.4515625,"top":0.22430556,"width":0.011328125,"height":0.013194445},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.46484375,"top":0.22291666,"width":0.00859375,"height":0.015972223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4734375,"top":0.22291666,"width":0.008203125,"height":0.015972223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\CollectionResponseAssociatedId;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Models\\Account;\nuse Exception;\nuse Jiminny\\Component\\DealInsights\\Forecast\\Forecast;\nuse Jiminny\\Jobs\\Crm\\MatchActivitiesToNewOpportunity;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Crm\\BusinessProcess;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Models\\Opportunity;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DealFieldsService;\nuse Jiminny\\Services\\Crm\\Hubspot\\OpportunitySyncStrategy\\HubspotSingleSyncStrategy;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Services\\Crm\\OpportunitySyncStrategyResolver;\nuse Jiminny\\Utils\\CurrencyFormatter;\n\n/**\n * Optimized sync methods for better performance\n * These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains\n */\ntrait OpportunitySyncTrait\n{\n private const int BATCH_SIZE = 100;\n private const int BATCH_PROCESS_SIZE = 800;\n\n protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;\n protected CrmEntityRepository $crmEntityRepository;\n protected DealFieldsService $dealFieldsService;\n\n private ?array $cachedClosedDealStages = null;\n private array $cachedBusinessProcesses = [];\n private array $cachedStages = [];\n\n public function syncOpportunities(array $parameters, ?string $strategy = null): int\n {\n $strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);\n $parameters['config'] = $this->config;\n $syncCount = 0;\n $reportedTotal = 0;\n $lastSyncedId = [];\n\n try {\n foreach ($strategies as $strategyName => $syncStrategy) {\n $this->logger->info(\n '[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .\n $strategyName\n );\n\n $total = 0;\n $lastId = null;\n $buffer = [];\n\n // HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies\n foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {\n $buffer[] = $hsOpportunity;\n\n // process every 800 rows (fits < 1 000 association limit)\n if (\\count($buffer) >= self::BATCH_PROCESS_SIZE) {\n $syncCount += $this->processOpportunityBatch($buffer);\n $buffer = [];\n }\n }\n\n // leftovers\n if ($buffer) {\n $syncCount += $this->processOpportunityBatch($buffer);\n }\n\n $reportedTotal += $total;\n $lastSyncedId = $lastId;\n }\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException | CrmException $e) {\n $this->handleSyncException($e, $parameters);\n }\n\n $this->logger->info(\n '[HubSpot] Synced opportunities',\n [\n 'team' => $this->team->getId(),\n 'sync_count' => $syncCount,\n 'total' => $reportedTotal,\n 'last_synced_id' => $lastSyncedId,\n ]\n );\n\n return $reportedTotal;\n }\n\n private function handleSyncException(\\Throwable $e, array $parameters): void\n {\n if (($parameters['since'] ?? null) instanceof Carbon) {\n $parameters['since'] = $parameters['since']->toDateTimeString();\n }\n $parameters['config'] = $this->config->getId();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [\n 'teamId' => $this->team->getUuid(),\n 'parameters' => $parameters,\n 'reason' => $e->getMessage(),\n ]);\n }\n\n /**\n * @inheritdoc\n */\n public function syncOpportunity(string $crmId): ?Opportunity\n {\n $strategy = $this->opportunitySyncStrategyResolver->resolve(\n $this->config,\n OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,\n );\n\n $parameters = [\n 'config' => $this->config,\n 'crm_id' => $crmId,\n ];\n\n try {\n if (! $strategy instanceof HubspotSingleSyncStrategy) {\n throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');\n }\n\n $hsOpportunity = $strategy->fetchOpportunity($parameters);\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);\n\n return $this->importOrUpdateOpportunity($hsOpportunity);\n }\n\n /**\n * Process webhook-collected opportunity batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportOpportunityBatch jobs for batch processing.\n *\n * @return int Number of opportunity IDs dispatched to jobs\n */\n public function batchSyncOpportunities(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,\n $configId\n );\n }\n\n /**\n * Import a batch of opportunities by their CRM IDs.\n * Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().\n *\n * @param array<string> $crmIds HubSpot deal CRM IDs\n *\n * @return array{success: array, failed_ids: array, errors?: array<string, string>}\n */\n public function importOpportunityBatchByIds(array $crmIds): array\n {\n $fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);\n\n $allDeals = [];\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n $deals = $this->client->getOpportunitiesByIds($chunk, $fields);\n foreach ($deals as $deal) {\n $allDeals[] = $deal;\n }\n }\n\n // IDs not returned by HubSpot are likely deleted or inaccessible deals.\n // These are not failures — retrying won't bring them back.\n $fetchedIds = array_map('strval', array_column($allDeals, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allDeals),\n ]);\n }\n\n if (empty($allDeals)) {\n return ['success' => [], 'failed_ids' => []];\n }\n\n return $this->importOpportunityBatch($allDeals);\n }\n\n private function getClosedDealStages(): array\n {\n if ($this->cachedClosedDealStages !== null) {\n return $this->cachedClosedDealStages;\n }\n\n $stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);\n $data = [\n 'lost' => [],\n 'won' => [],\n ];\n\n foreach ($stages as $stage) {\n if ($stage->probability == 0.00) {\n $data['lost'][] = $stage->crm_provider_id;\n }\n if ($stage->probability == 100.00) {\n $data['won'][] = $stage->crm_provider_id;\n }\n }\n\n $this->cachedClosedDealStages = $data;\n\n return $data;\n }\n\n /**\n * Import deals into the database with pre-fetched associations.\n *\n * API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT\n * caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()\n * where Laravel retries the whole job with backoff. After all retries exhausted,\n * failed() requeues all IDs to Redis.\n *\n * The per-deal loop catches exceptions individually. A deal can end up in three states:\n * - success: imported/updated successfully\n * - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)\n * These are permanent issues — retrying won't fix them.\n * - skipped (null): missing dependencies (no account, unknown pipeline/stage).\n * This is acceptable — the deal cannot be imported until those exist.\n */\n private function importOpportunityBatch(array $deals): array\n {\n $syncedOpportunities = [\n 'success' => [],\n 'failed_ids' => [],\n ];\n $dealIds = array_column($deals, 'id');\n\n // Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the\n // queue job retries the whole batch and eventually requeues all deal IDs back to Redis.\n try {\n $companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');\n $contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');\n\n $associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);\n\n $existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(\n $this->config,\n array_map('strval', $dealIds)\n );\n $existingCrmIdSet = array_flip($existingCrmIds);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [\n 'teamId' => $this->team->getId(),\n 'dealCount' => count($dealIds),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n foreach ($deals as $deal) {\n try {\n $deal['associations'] = $this->prepareAssociationsForOpportunity(\n $deal['id'],\n $companyAssociations,\n $contactAssociations,\n $associationsData\n );\n\n $syncedOpportunity = $this->importOrUpdateOpportunity(\n $deal,\n isset($existingCrmIdSet[(string) $deal['id']])\n );\n if ($syncedOpportunity) {\n $syncedOpportunities['success'][] = $syncedOpportunity;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $deal['id'],\n 'error' => $e->getMessage(),\n ]);\n $syncedOpportunities['failed_ids'][] = $deal['id'];\n $syncedOpportunities['errors'][$deal['id']] = $e->getMessage();\n }\n }\n\n return $syncedOpportunities;\n }\n\n /**\n * Prepare associated entities for opportunities with optimized batch processing\n * Returns structured data with CRM ID to DB ID mappings for each opportunity\n */\n private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array\n {\n // Step 1: Collect all unique company and contact IDs from associations\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n\n // Step 2: Batch sync missing entities and get CRM ID to DB ID mappings\n $companyIdMappings = [];\n $contactIdMappings = [];\n\n if (! empty($allCompanyIds)) {\n $companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);\n }\n\n if (! empty($allContactIds)) {\n $contactIdMappings = $this->prepareAssociatedContacts($allContactIds);\n }\n\n return [\n 'company_id_mappings' => $companyIdMappings,\n 'contact_id_mappings' => $contactIdMappings,\n ];\n }\n\n /**\n * Flatten association data to get unique IDs\n */\n private function flattenAssociationIds(array $associations): array\n {\n $ids = [];\n foreach ($associations as $dealAssociations) {\n if (is_array($dealAssociations)) {\n foreach ($dealAssociations as $id) {\n $ids[$id] = true;\n }\n }\n }\n\n return array_keys($ids);\n }\n\n /**\n * Batch sync missing accounts\n */\n private function prepareAssociatedAccounts(array $companyIds): array\n {\n // Find which accounts already exist\n $existingAccounts = $this->crmEntityRepository\n ->findAccountsByExternalIds($this->config, $companyIds);\n\n $existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();\n\n $existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {\n return [$account->getCrmProviderId() => $account->getId()];\n })->toArray();\n\n $missingCompanyIds = array_diff($companyIds, $existingCompanyIds);\n\n if (empty($missingCompanyIds)) {\n return $existingAccountsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [\n 'teamId' => $this->team->getUuid(),\n 'total_companies' => count($companyIds),\n 'existing_companies' => count($existingCompanyIds),\n 'missing_companies' => count($missingCompanyIds),\n ]);\n\n // we already have limit on opportunity ids count\n // Initialize variable before try block\n $syncedAccountsData = [];\n\n try {\n $syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [\n 'size' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedAccountsData = [];\n }\n\n return $existingAccountsData + $syncedAccountsData;\n }\n\n /**\n * Prepare associated contacts - find existing and sync missing ones\n * Returns mapping of CRM ID to DB ID\n */\n private function prepareAssociatedContacts(array $contactIds): array\n {\n // Find which contacts already exist\n $existingContacts = $this->crmEntityRepository\n ->findContactsByExternalIds($this->config, $contactIds);\n\n $existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();\n\n // Create mapping for existing contacts\n $existingContactsData = $existingContacts->mapWithKeys(function ($contact) {\n return [$contact->getCrmProviderId() => $contact->getId()];\n })->toArray();\n\n $missingContactIds = array_diff($contactIds, $existingContactIds);\n\n if (empty($missingContactIds)) {\n return $existingContactsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [\n 'teamId' => $this->team->getUuid(),\n 'total_contacts' => count($contactIds),\n 'existing_contacts' => count($existingContactIds),\n 'missing_contacts' => count($missingContactIds),\n ]);\n\n // Sync missing contacts using batch API\n try {\n $syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [\n 'size' => count($missingContactIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedContactsData = [];\n }\n\n return $existingContactsData + $syncedContactsData;\n }\n\n private function batchSyncCrmObjects(string $objectType, array $crmIds): array\n {\n $syncObjects = [];\n $crmObjectIds = array_values($crmIds);\n\n foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {\n try {\n $objects = $objectType === 'companies' ?\n $this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :\n $this->client->getContactsByIds($chunk, $this->getContactFields());\n\n foreach ($objects as $objectId => $objectData) {\n $this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [\n 'requested_count' => count($chunk),\n 'synced_count' => count($objects),\n ]);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [\n 'ids' => $chunk,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncObjects;\n }\n\n private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void\n {\n try {\n $object = $objectType === 'companies' ?\n $this->importAccount($objectData) :\n $this->importContact($objectData);\n\n if ($object) {\n $syncObjects[$object->getCrmProviderId()] = $object->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [\n 'id' => $objectId,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n /**\n * Prepare associations for a single opportunity\n *\n * The return value is an array with the following structure:\n * [\n * 'companies' => [\n * $companyCrmId => $companyId,\n * ...\n * ],\n * 'contacts' => [\n * $contactCrmId => $contactId,\n * ...\n * ],\n * 'account_id' => $accountId,\n * ]\n */\n private function prepareAssociationsForOpportunity(\n string $oppCrmId,\n array $companyAssociations,\n array $contactAssociations,\n array $associationsData\n ): array {\n $associations = [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n\n $oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];\n foreach ($oppCompanyIds as $companyCrmId) {\n if (isset($associationsData['company_id_mappings'][$companyCrmId])) {\n $associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];\n\n // Set primary account (first company becomes primary account)\n if ($associations['account_id'] === null) {\n $associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];\n }\n }\n }\n\n $oppContactIds = $contactAssociations[$oppCrmId] ?? [];\n foreach ($oppContactIds as $contactCrmId) {\n if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {\n $associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];\n }\n }\n\n return $associations;\n }\n\n /**\n * Update only associations for an opportunity\n */\n private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void\n {\n // Update contact associations\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n // Update company (account) associations\n $this->updateOpportunityAccount($opportunity, $associations['account_id']);\n }\n\n /**\n * Remove all contact associations from an opportunity\n */\n private function removeAllOpportunityContacts(Opportunity $opportunity): void\n {\n $currentCount = (int) $opportunity->contacts()->count();\n\n if ($currentCount > 0) {\n $opportunity->contacts()->detach();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_count' => $currentCount,\n ]);\n }\n }\n\n private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void\n {\n if ($accountId === null) {\n // No account ID provided - keep current account\n return;\n }\n\n $currentAccountId = $opportunity->getAccountId();\n\n // Only update if account has changed\n if ($currentAccountId !== $accountId) {\n $opportunity->account_id = $accountId;\n $opportunity->save();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [\n 'opportunity_id' => $opportunity->getId(),\n 'old_account_id' => $currentAccountId,\n 'new_account_id' => $accountId,\n ]);\n }\n }\n\n /**\n * Find existing opportunities by external IDs (OPTIMIZED VERSION)\n * Uses batch query for better performance\n */\n private function findExistingOpportunities(array $crmIds): Collection\n {\n return $this->crmEntityRepository\n ->findOpportunitiesByExternalIds($this->config, $crmIds);\n }\n\n private function processOpportunityBatch(array $opportunities): int\n {\n $syncedOpportunities = $this->importOpportunityBatch($opportunities);\n\n return count($syncedOpportunities['success'] ?? []);\n }\n\n /**\n * Convert single deal associations from HubSpot format to internal format\n * Handles both HubSpot SDK objects and array formats\n *\n * @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed\n *\n * @return array Processed associations with DB IDs\n */\n private function convertDealAssociations(array $opportunityAssociations): array\n {\n $associations = $this->initializeAssociationsStructure();\n\n if (empty($opportunityAssociations)) {\n return $associations;\n }\n\n $associationIds = $this->extractAssociationIds($opportunityAssociations);\n\n $this->processCompanyAssociations($associationIds, $associations);\n $this->processContactAssociations($associationIds, $associations);\n\n return $associations;\n }\n\n private function initializeAssociationsStructure(): array\n {\n return [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n }\n\n private function extractAssociationIds(array $opportunityAssociations): array\n {\n $associationIds = [];\n\n foreach ($opportunityAssociations as $type => $associationData) {\n if (! empty($associationData)) {\n $associationIds[$type] = $this->convertSingleDealAssociations($associationData);\n }\n }\n\n return $associationIds;\n }\n\n private function processCompanyAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['companies'])) {\n return;\n }\n\n $companyId = $associationIds['companies'][0];\n $account = $this->findOrSyncAccount($companyId);\n\n if ($account instanceof Account) {\n $associations['companies'][$companyId] = $account->getId();\n $associations['account_id'] = $account->getId();\n }\n }\n\n private function processContactAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['contacts'])) {\n return;\n }\n\n foreach ($associationIds['contacts'] as $contactId) {\n $contact = $this->findOrSyncContact($contactId);\n\n if ($contact instanceof Contact) {\n $associations['contacts'][$contactId] = $contact->getId();\n }\n }\n }\n\n private function findOrSyncAccount(string $companyId): ?Account\n {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);\n\n if (! $account instanceof Account) {\n $account = $this->syncAccount($companyId);\n }\n\n return $account;\n }\n\n private function findOrSyncContact(string $contactId): ?Contact\n {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);\n\n if (! $contact instanceof Contact) {\n $contact = $this->syncContact($contactId);\n }\n\n return $contact;\n }\n\n private function convertSingleDealAssociations($opportunityAssociations = null): array\n {\n $associationData = [];\n\n if ($opportunityAssociations === null) {\n return $associationData;\n }\n\n // Handle array input (from extractAssociationIds)\n if (is_array($opportunityAssociations)) {\n return $opportunityAssociations;\n }\n\n // Handle CollectionResponseAssociatedId object\n if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {\n foreach ($opportunityAssociations->getResults() as $association) {\n $associationData[] = $association->getId();\n }\n }\n\n return $associationData;\n }\n\n private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity\n {\n if (empty($crmData['properties'])) {\n return null;\n }\n\n $crmId = (string) $crmData['id'];\n $properties = $crmData['properties'];\n $associations = $crmData['associations'] ?? [];\n\n $opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(\n $this->config,\n $crmId\n );\n\n if ($opportunityExists) {\n return $this->updateOpportunity($crmId, $properties, $associations);\n } else {\n return $this->createOpportunity($crmId, $properties, $associations);\n }\n }\n\n /**\n * Create new opportunity\n */\n private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n if (! $accountId) {\n return null;\n }\n\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n if (! $businessProcess) {\n return null;\n }\n\n $stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);\n if (! $stage) {\n return null;\n }\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n if ($opportunity->wasRecentlyCreated) {\n MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());\n }\n\n return $opportunity;\n }\n\n /**\n * Update existing opportunity\n */\n private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n $stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->updateOpportunityAssociations($opportunity, $associations);\n\n return $opportunity;\n }\n\n private function resolveAccountId(array $associations): ?int\n {\n if (! empty($associations['accountId'])) {\n return $associations['accountId'];\n }\n\n if (empty($associations)) {\n return null;\n }\n\n // we can't resolve multiple account ids (currently SDK returns one company)\n foreach ($associations['companies'] as $accountId) {\n return $accountId;\n }\n\n return null;\n }\n\n private function buildOpportunityData(\n array $properties,\n ?int $accountId,\n ?BusinessProcess $businessProcess,\n ?Stage $stage\n ): array {\n $ownerId = null;\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $name = 'Unknown';\n if (isset($properties['dealname'])) {\n $name = mb_strimwidth($properties['dealname'], 0, 128);\n }\n\n $amount = $this->resolveAmount($properties);\n $currency = $properties['deal_currency_code'] ?? null;\n\n $closeDate = null;\n if (! empty($properties['closedate'])) {\n $closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');\n }\n\n $remotelyCreatedAt = null;\n if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {\n $date = $this->parseCleanDatetime($properties['createdate']);\n $remotelyCreatedAt = $date?->format('Y-m-d H:i:s');\n }\n\n $closedStages = $this->getClosedDealStages();\n $isWon = in_array($properties['dealstage'], $closedStages['won']);\n $isLost = in_array($properties['dealstage'], $closedStages['lost']);\n\n $data = [\n 'team_id' => $this->team->getId(),\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'value' => ! empty($amount) ? $amount : null,\n 'currency_code' => CurrencyFormatter::formatCode($currency),\n 'close_date' => $closeDate,\n 'is_closed' => $isWon || $isLost,\n 'is_won' => $isWon,\n 'remotely_created_at' => $remotelyCreatedAt,\n 'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),\n 'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),\n ];\n\n if ($accountId) {\n $data['account_id'] = $accountId;\n }\n\n if ($stage) {\n $data['stage_id'] = $stage->id;\n }\n\n if ($businessProcess) {\n $recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);\n if ($recordType) {\n $data['record_type_id'] = $recordType->id;\n }\n }\n\n return $data;\n }\n\n private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess\n {\n if ($pipelineId === null) {\n return null;\n }\n\n if (isset($this->cachedBusinessProcesses[$pipelineId])) {\n return $this->cachedBusinessProcesses[$pipelineId];\n }\n\n $businessProcess = $this->getBusinessProcess($pipelineId);\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->importStages();\n $businessProcess = $this->getBusinessProcess($pipelineId);\n }\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->logger->info(\n '[HubSpot] Deal is not attached to a pipeline',\n [\n 'pipeline' => $pipelineId]\n );\n }\n\n $this->cachedBusinessProcesses[$pipelineId] = $businessProcess;\n\n return $businessProcess;\n }\n\n private function getBusinessProcess(string $pipelineId): ?BusinessProcess\n {\n return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);\n }\n\n private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage\n {\n if (empty($stageId)) {\n return null;\n }\n\n $cacheKey = $businessProcess->getId() . ':' . $stageId;\n if (isset($this->cachedStages[$cacheKey])) {\n return $this->cachedStages[$cacheKey];\n }\n\n $stage = $this->crmEntityRepository->getPipelineStageByConditions(\n $businessProcess,\n [\n 'crm_provider_id' => $stageId,\n 'type' => Stage::TYPE_OPPORTUNITY,\n ]\n );\n\n if ($stage === null) {\n $this->importStages(null, $stageId);\n }\n\n if ($stage === null) {\n $this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);\n }\n\n $this->cachedStages[$cacheKey] = $stage;\n\n return $stage;\n }\n\n private function resolveAmount(array $properties): ?string\n {\n $amount = null;\n if (! empty($properties['amount'])) {\n $amount = str_replace(',', '', $properties['amount']);\n }\n\n if ($this->config->hasDefaultCurrencyFieldSet()) {\n $valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();\n $amount = $properties[$valueFieldName] ?? $amount;\n }\n\n return $amount;\n }\n\n private function parseCleanDatetime(string $datetime): ?Carbon\n {\n // Treat pre-1980 values as invalid\n $minValidDate = Carbon::parse('1980-01-01 00:00:00');\n\n try {\n $date = Carbon::parse($datetime);\n\n if ($minValidDate->gt($date)) {\n return null;\n }\n\n return $date;\n } catch (Exception) {\n return null; // On parse error, treat as null\n }\n }\n\n private function resolveDealProbability(?string $stageProbability): int\n {\n if ($stageProbability === null) {\n return 0;\n }\n\n $probability = (float) $stageProbability;\n\n return $probability > 1 ? 0 : (int) ($probability * 100);\n }\n\n private function resolveForecastCategory(?string $forecastCategory): string\n {\n if (! $forecastCategory) {\n return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;\n }\n\n $forecastCategory = str_replace('_', ' ', $forecastCategory);\n\n return ucwords(strtolower($forecastCategory));\n }\n\n private function importExternalFieldData(array $properties, int $opportunityId): void\n {\n $crmFields = $this->getOpportunitySyncableFields();\n $this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);\n }\n\n private function importOpportunityContacts(Opportunity $opportunity, array $associations): void\n {\n // Handle empty or missing contact associations\n if (empty($associations)) {\n // Remove all existing contact associations if none provided\n $this->removeAllOpportunityContacts($opportunity);\n\n return;\n }\n\n // Use differential sync approach for better performance and accuracy\n $this->syncOpportunityContactsDifferential($opportunity, $associations);\n }\n\n /**\n * Sync opportunity contacts using differential approach\n * This compares current vs new associations and only makes necessary changes\n */\n private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void\n {\n $currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);\n $contactAssociationIds = array_keys($contactAssociations);\n\n $contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);\n $contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);\n\n if (empty($contactsToAdd) && empty($contactsToRemove)) {\n return;\n }\n\n $this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);\n\n $this->removeContactAssociations($opportunity, $contactsToRemove);\n $this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);\n }\n\n private function getCurrentContactCrmIds(Opportunity $opportunity): array\n {\n return $opportunity->contacts()\n ->pluck('contacts.crm_provider_id')\n ->toArray();\n }\n\n private function logContactAssociationChanges(\n Opportunity $opportunity,\n array $currentContactCrmIds,\n array $contactAssociations,\n array $contactsToAdd,\n array $contactsToRemove\n ): void {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [\n 'opportunity_id' => $opportunity->getId(),\n 'current_contacts' => $currentContactCrmIds,\n 'new_contacts' => $contactAssociations,\n 'contacts_to_add' => $contactsToAdd,\n 'contacts_to_remove' => $contactsToRemove,\n ]);\n }\n\n private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void\n {\n if (empty($contactsToRemove)) {\n return;\n }\n\n $contactsToDetach = $opportunity->contacts()\n ->whereIn('contacts.crm_provider_id', $contactsToRemove)\n ->pluck('contacts.id')\n ->toArray();\n\n if (! empty($contactsToDetach)) {\n $opportunity->contacts()->detach($contactsToDetach);\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_contact_crm_ids' => $contactsToRemove,\n 'removed_contact_count' => count($contactsToDetach),\n ]);\n }\n }\n\n private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void\n {\n if (empty($contactsToAdd)) {\n return;\n }\n\n $contactsAdded = [];\n foreach ($contactsToAdd as $crmId) {\n $id = $contactAssociations[$crmId];\n\n if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {\n $contactsAdded[] = $crmId;\n }\n }\n\n $this->logAddedContacts($opportunity, $contactsAdded);\n }\n\n private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);\n\n if (! $contact) {\n return false;\n }\n\n return $this->performContactAttachment($opportunity, $contact, $crmId);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [\n 'opportunity_id' => $opportunity->getId(),\n 'contact_crm_id' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool\n {\n try {\n $opportunity->contacts()->attach($contact->getId(), [\n 'crm_provider_id' => $crmId,\n ]);\n\n return true;\n } catch (\\Illuminate\\Database\\QueryException $e) {\n if (str_contains($e->getMessage(), 'Duplicate entry')) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [\n 'contact_id' => $contact->getId(),\n 'contact_crm_id' => $crmId,\n 'opportunity_id' => $opportunity->getId(),\n ]);\n\n return false;\n }\n\n throw $e;\n }\n }\n\n private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void\n {\n if (! empty($contactsAdded)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'contacts_to_add_count' => count($contactsAdded),\n 'added_contact_crm_ids' => $contactsAdded,\n 'added_contacts_count' => count($contactsAdded),\n ]);\n }\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\CollectionResponseAssociatedId;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Models\\Account;\nuse Exception;\nuse Jiminny\\Component\\DealInsights\\Forecast\\Forecast;\nuse Jiminny\\Jobs\\Crm\\MatchActivitiesToNewOpportunity;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Crm\\BusinessProcess;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Models\\Opportunity;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DealFieldsService;\nuse Jiminny\\Services\\Crm\\Hubspot\\OpportunitySyncStrategy\\HubspotSingleSyncStrategy;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Services\\Crm\\OpportunitySyncStrategyResolver;\nuse Jiminny\\Utils\\CurrencyFormatter;\n\n/**\n * Optimized sync methods for better performance\n * These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains\n */\ntrait OpportunitySyncTrait\n{\n private const int BATCH_SIZE = 100;\n private const int BATCH_PROCESS_SIZE = 800;\n\n protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;\n protected CrmEntityRepository $crmEntityRepository;\n protected DealFieldsService $dealFieldsService;\n\n private ?array $cachedClosedDealStages = null;\n private array $cachedBusinessProcesses = [];\n private array $cachedStages = [];\n\n public function syncOpportunities(array $parameters, ?string $strategy = null): int\n {\n $strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);\n $parameters['config'] = $this->config;\n $syncCount = 0;\n $reportedTotal = 0;\n $lastSyncedId = [];\n\n try {\n foreach ($strategies as $strategyName => $syncStrategy) {\n $this->logger->info(\n '[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .\n $strategyName\n );\n\n $total = 0;\n $lastId = null;\n $buffer = [];\n\n // HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies\n foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {\n $buffer[] = $hsOpportunity;\n\n // process every 800 rows (fits < 1 000 association limit)\n if (\\count($buffer) >= self::BATCH_PROCESS_SIZE) {\n $syncCount += $this->processOpportunityBatch($buffer);\n $buffer = [];\n }\n }\n\n // leftovers\n if ($buffer) {\n $syncCount += $this->processOpportunityBatch($buffer);\n }\n\n $reportedTotal += $total;\n $lastSyncedId = $lastId;\n }\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException | CrmException $e) {\n $this->handleSyncException($e, $parameters);\n }\n\n $this->logger->info(\n '[HubSpot] Synced opportunities',\n [\n 'team' => $this->team->getId(),\n 'sync_count' => $syncCount,\n 'total' => $reportedTotal,\n 'last_synced_id' => $lastSyncedId,\n ]\n );\n\n return $reportedTotal;\n }\n\n private function handleSyncException(\\Throwable $e, array $parameters): void\n {\n if (($parameters['since'] ?? null) instanceof Carbon) {\n $parameters['since'] = $parameters['since']->toDateTimeString();\n }\n $parameters['config'] = $this->config->getId();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [\n 'teamId' => $this->team->getUuid(),\n 'parameters' => $parameters,\n 'reason' => $e->getMessage(),\n ]);\n }\n\n /**\n * @inheritdoc\n */\n public function syncOpportunity(string $crmId): ?Opportunity\n {\n $strategy = $this->opportunitySyncStrategyResolver->resolve(\n $this->config,\n OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,\n );\n\n $parameters = [\n 'config' => $this->config,\n 'crm_id' => $crmId,\n ];\n\n try {\n if (! $strategy instanceof HubspotSingleSyncStrategy) {\n throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');\n }\n\n $hsOpportunity = $strategy->fetchOpportunity($parameters);\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);\n\n return $this->importOrUpdateOpportunity($hsOpportunity);\n }\n\n /**\n * Process webhook-collected opportunity batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportOpportunityBatch jobs for batch processing.\n *\n * @return int Number of opportunity IDs dispatched to jobs\n */\n public function batchSyncOpportunities(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,\n $configId\n );\n }\n\n /**\n * Import a batch of opportunities by their CRM IDs.\n * Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().\n *\n * @param array<string> $crmIds HubSpot deal CRM IDs\n *\n * @return array{success: array, failed_ids: array, errors?: array<string, string>}\n */\n public function importOpportunityBatchByIds(array $crmIds): array\n {\n $fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);\n\n $allDeals = [];\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n $deals = $this->client->getOpportunitiesByIds($chunk, $fields);\n foreach ($deals as $deal) {\n $allDeals[] = $deal;\n }\n }\n\n // IDs not returned by HubSpot are likely deleted or inaccessible deals.\n // These are not failures — retrying won't bring them back.\n $fetchedIds = array_map('strval', array_column($allDeals, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allDeals),\n ]);\n }\n\n if (empty($allDeals)) {\n return ['success' => [], 'failed_ids' => []];\n }\n\n return $this->importOpportunityBatch($allDeals);\n }\n\n private function getClosedDealStages(): array\n {\n if ($this->cachedClosedDealStages !== null) {\n return $this->cachedClosedDealStages;\n }\n\n $stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);\n $data = [\n 'lost' => [],\n 'won' => [],\n ];\n\n foreach ($stages as $stage) {\n if ($stage->probability == 0.00) {\n $data['lost'][] = $stage->crm_provider_id;\n }\n if ($stage->probability == 100.00) {\n $data['won'][] = $stage->crm_provider_id;\n }\n }\n\n $this->cachedClosedDealStages = $data;\n\n return $data;\n }\n\n /**\n * Import deals into the database with pre-fetched associations.\n *\n * API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT\n * caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()\n * where Laravel retries the whole job with backoff. After all retries exhausted,\n * failed() requeues all IDs to Redis.\n *\n * The per-deal loop catches exceptions individually. A deal can end up in three states:\n * - success: imported/updated successfully\n * - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)\n * These are permanent issues — retrying won't fix them.\n * - skipped (null): missing dependencies (no account, unknown pipeline/stage).\n * This is acceptable — the deal cannot be imported until those exist.\n */\n private function importOpportunityBatch(array $deals): array\n {\n $syncedOpportunities = [\n 'success' => [],\n 'failed_ids' => [],\n ];\n $dealIds = array_column($deals, 'id');\n\n // Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the\n // queue job retries the whole batch and eventually requeues all deal IDs back to Redis.\n try {\n $companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');\n $contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');\n\n $associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);\n\n $existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(\n $this->config,\n array_map('strval', $dealIds)\n );\n $existingCrmIdSet = array_flip($existingCrmIds);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [\n 'teamId' => $this->team->getId(),\n 'dealCount' => count($dealIds),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n foreach ($deals as $deal) {\n try {\n $deal['associations'] = $this->prepareAssociationsForOpportunity(\n $deal['id'],\n $companyAssociations,\n $contactAssociations,\n $associationsData\n );\n\n $syncedOpportunity = $this->importOrUpdateOpportunity(\n $deal,\n isset($existingCrmIdSet[(string) $deal['id']])\n );\n if ($syncedOpportunity) {\n $syncedOpportunities['success'][] = $syncedOpportunity;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $deal['id'],\n 'error' => $e->getMessage(),\n ]);\n $syncedOpportunities['failed_ids'][] = $deal['id'];\n $syncedOpportunities['errors'][$deal['id']] = $e->getMessage();\n }\n }\n\n return $syncedOpportunities;\n }\n\n /**\n * Prepare associated entities for opportunities with optimized batch processing\n * Returns structured data with CRM ID to DB ID mappings for each opportunity\n */\n private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array\n {\n // Step 1: Collect all unique company and contact IDs from associations\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n\n // Step 2: Batch sync missing entities and get CRM ID to DB ID mappings\n $companyIdMappings = [];\n $contactIdMappings = [];\n\n if (! empty($allCompanyIds)) {\n $companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);\n }\n\n if (! empty($allContactIds)) {\n $contactIdMappings = $this->prepareAssociatedContacts($allContactIds);\n }\n\n return [\n 'company_id_mappings' => $companyIdMappings,\n 'contact_id_mappings' => $contactIdMappings,\n ];\n }\n\n /**\n * Flatten association data to get unique IDs\n */\n private function flattenAssociationIds(array $associations): array\n {\n $ids = [];\n foreach ($associations as $dealAssociations) {\n if (is_array($dealAssociations)) {\n foreach ($dealAssociations as $id) {\n $ids[$id] = true;\n }\n }\n }\n\n return array_keys($ids);\n }\n\n /**\n * Batch sync missing accounts\n */\n private function prepareAssociatedAccounts(array $companyIds): array\n {\n // Find which accounts already exist\n $existingAccounts = $this->crmEntityRepository\n ->findAccountsByExternalIds($this->config, $companyIds);\n\n $existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();\n\n $existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {\n return [$account->getCrmProviderId() => $account->getId()];\n })->toArray();\n\n $missingCompanyIds = array_diff($companyIds, $existingCompanyIds);\n\n if (empty($missingCompanyIds)) {\n return $existingAccountsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [\n 'teamId' => $this->team->getUuid(),\n 'total_companies' => count($companyIds),\n 'existing_companies' => count($existingCompanyIds),\n 'missing_companies' => count($missingCompanyIds),\n ]);\n\n // we already have limit on opportunity ids count\n // Initialize variable before try block\n $syncedAccountsData = [];\n\n try {\n $syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [\n 'size' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedAccountsData = [];\n }\n\n return $existingAccountsData + $syncedAccountsData;\n }\n\n /**\n * Prepare associated contacts - find existing and sync missing ones\n * Returns mapping of CRM ID to DB ID\n */\n private function prepareAssociatedContacts(array $contactIds): array\n {\n // Find which contacts already exist\n $existingContacts = $this->crmEntityRepository\n ->findContactsByExternalIds($this->config, $contactIds);\n\n $existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();\n\n // Create mapping for existing contacts\n $existingContactsData = $existingContacts->mapWithKeys(function ($contact) {\n return [$contact->getCrmProviderId() => $contact->getId()];\n })->toArray();\n\n $missingContactIds = array_diff($contactIds, $existingContactIds);\n\n if (empty($missingContactIds)) {\n return $existingContactsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [\n 'teamId' => $this->team->getUuid(),\n 'total_contacts' => count($contactIds),\n 'existing_contacts' => count($existingContactIds),\n 'missing_contacts' => count($missingContactIds),\n ]);\n\n // Sync missing contacts using batch API\n try {\n $syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [\n 'size' => count($missingContactIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedContactsData = [];\n }\n\n return $existingContactsData + $syncedContactsData;\n }\n\n private function batchSyncCrmObjects(string $objectType, array $crmIds): array\n {\n $syncObjects = [];\n $crmObjectIds = array_values($crmIds);\n\n foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {\n try {\n $objects = $objectType === 'companies' ?\n $this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :\n $this->client->getContactsByIds($chunk, $this->getContactFields());\n\n foreach ($objects as $objectId => $objectData) {\n $this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [\n 'requested_count' => count($chunk),\n 'synced_count' => count($objects),\n ]);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [\n 'ids' => $chunk,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncObjects;\n }\n\n private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void\n {\n try {\n $object = $objectType === 'companies' ?\n $this->importAccount($objectData) :\n $this->importContact($objectData);\n\n if ($object) {\n $syncObjects[$object->getCrmProviderId()] = $object->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [\n 'id' => $objectId,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n /**\n * Prepare associations for a single opportunity\n *\n * The return value is an array with the following structure:\n * [\n * 'companies' => [\n * $companyCrmId => $companyId,\n * ...\n * ],\n * 'contacts' => [\n * $contactCrmId => $contactId,\n * ...\n * ],\n * 'account_id' => $accountId,\n * ]\n */\n private function prepareAssociationsForOpportunity(\n string $oppCrmId,\n array $companyAssociations,\n array $contactAssociations,\n array $associationsData\n ): array {\n $associations = [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n\n $oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];\n foreach ($oppCompanyIds as $companyCrmId) {\n if (isset($associationsData['company_id_mappings'][$companyCrmId])) {\n $associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];\n\n // Set primary account (first company becomes primary account)\n if ($associations['account_id'] === null) {\n $associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];\n }\n }\n }\n\n $oppContactIds = $contactAssociations[$oppCrmId] ?? [];\n foreach ($oppContactIds as $contactCrmId) {\n if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {\n $associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];\n }\n }\n\n return $associations;\n }\n\n /**\n * Update only associations for an opportunity\n */\n private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void\n {\n // Update contact associations\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n // Update company (account) associations\n $this->updateOpportunityAccount($opportunity, $associations['account_id']);\n }\n\n /**\n * Remove all contact associations from an opportunity\n */\n private function removeAllOpportunityContacts(Opportunity $opportunity): void\n {\n $currentCount = (int) $opportunity->contacts()->count();\n\n if ($currentCount > 0) {\n $opportunity->contacts()->detach();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_count' => $currentCount,\n ]);\n }\n }\n\n private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void\n {\n if ($accountId === null) {\n // No account ID provided - keep current account\n return;\n }\n\n $currentAccountId = $opportunity->getAccountId();\n\n // Only update if account has changed\n if ($currentAccountId !== $accountId) {\n $opportunity->account_id = $accountId;\n $opportunity->save();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [\n 'opportunity_id' => $opportunity->getId(),\n 'old_account_id' => $currentAccountId,\n 'new_account_id' => $accountId,\n ]);\n }\n }\n\n /**\n * Find existing opportunities by external IDs (OPTIMIZED VERSION)\n * Uses batch query for better performance\n */\n private function findExistingOpportunities(array $crmIds): Collection\n {\n return $this->crmEntityRepository\n ->findOpportunitiesByExternalIds($this->config, $crmIds);\n }\n\n private function processOpportunityBatch(array $opportunities): int\n {\n $syncedOpportunities = $this->importOpportunityBatch($opportunities);\n\n return count($syncedOpportunities['success'] ?? []);\n }\n\n /**\n * Convert single deal associations from HubSpot format to internal format\n * Handles both HubSpot SDK objects and array formats\n *\n * @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed\n *\n * @return array Processed associations with DB IDs\n */\n private function convertDealAssociations(array $opportunityAssociations): array\n {\n $associations = $this->initializeAssociationsStructure();\n\n if (empty($opportunityAssociations)) {\n return $associations;\n }\n\n $associationIds = $this->extractAssociationIds($opportunityAssociations);\n\n $this->processCompanyAssociations($associationIds, $associations);\n $this->processContactAssociations($associationIds, $associations);\n\n return $associations;\n }\n\n private function initializeAssociationsStructure(): array\n {\n return [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n }\n\n private function extractAssociationIds(array $opportunityAssociations): array\n {\n $associationIds = [];\n\n foreach ($opportunityAssociations as $type => $associationData) {\n if (! empty($associationData)) {\n $associationIds[$type] = $this->convertSingleDealAssociations($associationData);\n }\n }\n\n return $associationIds;\n }\n\n private function processCompanyAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['companies'])) {\n return;\n }\n\n $companyId = $associationIds['companies'][0];\n $account = $this->findOrSyncAccount($companyId);\n\n if ($account instanceof Account) {\n $associations['companies'][$companyId] = $account->getId();\n $associations['account_id'] = $account->getId();\n }\n }\n\n private function processContactAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['contacts'])) {\n return;\n }\n\n foreach ($associationIds['contacts'] as $contactId) {\n $contact = $this->findOrSyncContact($contactId);\n\n if ($contact instanceof Contact) {\n $associations['contacts'][$contactId] = $contact->getId();\n }\n }\n }\n\n private function findOrSyncAccount(string $companyId): ?Account\n {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);\n\n if (! $account instanceof Account) {\n $account = $this->syncAccount($companyId);\n }\n\n return $account;\n }\n\n private function findOrSyncContact(string $contactId): ?Contact\n {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);\n\n if (! $contact instanceof Contact) {\n $contact = $this->syncContact($contactId);\n }\n\n return $contact;\n }\n\n private function convertSingleDealAssociations($opportunityAssociations = null): array\n {\n $associationData = [];\n\n if ($opportunityAssociations === null) {\n return $associationData;\n }\n\n // Handle array input (from extractAssociationIds)\n if (is_array($opportunityAssociations)) {\n return $opportunityAssociations;\n }\n\n // Handle CollectionResponseAssociatedId object\n if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {\n foreach ($opportunityAssociations->getResults() as $association) {\n $associationData[] = $association->getId();\n }\n }\n\n return $associationData;\n }\n\n private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity\n {\n if (empty($crmData['properties'])) {\n return null;\n }\n\n $crmId = (string) $crmData['id'];\n $properties = $crmData['properties'];\n $associations = $crmData['associations'] ?? [];\n\n $opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(\n $this->config,\n $crmId\n );\n\n if ($opportunityExists) {\n return $this->updateOpportunity($crmId, $properties, $associations);\n } else {\n return $this->createOpportunity($crmId, $properties, $associations);\n }\n }\n\n /**\n * Create new opportunity\n */\n private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n if (! $accountId) {\n return null;\n }\n\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n if (! $businessProcess) {\n return null;\n }\n\n $stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);\n if (! $stage) {\n return null;\n }\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n if ($opportunity->wasRecentlyCreated) {\n MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());\n }\n\n return $opportunity;\n }\n\n /**\n * Update existing opportunity\n */\n private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n $stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->updateOpportunityAssociations($opportunity, $associations);\n\n return $opportunity;\n }\n\n private function resolveAccountId(array $associations): ?int\n {\n if (! empty($associations['accountId'])) {\n return $associations['accountId'];\n }\n\n if (empty($associations)) {\n return null;\n }\n\n // we can't resolve multiple account ids (currently SDK returns one company)\n foreach ($associations['companies'] as $accountId) {\n return $accountId;\n }\n\n return null;\n }\n\n private function buildOpportunityData(\n array $properties,\n ?int $accountId,\n ?BusinessProcess $businessProcess,\n ?Stage $stage\n ): array {\n $ownerId = null;\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $name = 'Unknown';\n if (isset($properties['dealname'])) {\n $name = mb_strimwidth($properties['dealname'], 0, 128);\n }\n\n $amount = $this->resolveAmount($properties);\n $currency = $properties['deal_currency_code'] ?? null;\n\n $closeDate = null;\n if (! empty($properties['closedate'])) {\n $closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');\n }\n\n $remotelyCreatedAt = null;\n if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {\n $date = $this->parseCleanDatetime($properties['createdate']);\n $remotelyCreatedAt = $date?->format('Y-m-d H:i:s');\n }\n\n $closedStages = $this->getClosedDealStages();\n $isWon = in_array($properties['dealstage'], $closedStages['won']);\n $isLost = in_array($properties['dealstage'], $closedStages['lost']);\n\n $data = [\n 'team_id' => $this->team->getId(),\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'value' => ! empty($amount) ? $amount : null,\n 'currency_code' => CurrencyFormatter::formatCode($currency),\n 'close_date' => $closeDate,\n 'is_closed' => $isWon || $isLost,\n 'is_won' => $isWon,\n 'remotely_created_at' => $remotelyCreatedAt,\n 'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),\n 'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),\n ];\n\n if ($accountId) {\n $data['account_id'] = $accountId;\n }\n\n if ($stage) {\n $data['stage_id'] = $stage->id;\n }\n\n if ($businessProcess) {\n $recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);\n if ($recordType) {\n $data['record_type_id'] = $recordType->id;\n }\n }\n\n return $data;\n }\n\n private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess\n {\n if ($pipelineId === null) {\n return null;\n }\n\n if (isset($this->cachedBusinessProcesses[$pipelineId])) {\n return $this->cachedBusinessProcesses[$pipelineId];\n }\n\n $businessProcess = $this->getBusinessProcess($pipelineId);\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->importStages();\n $businessProcess = $this->getBusinessProcess($pipelineId);\n }\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->logger->info(\n '[HubSpot] Deal is not attached to a pipeline',\n [\n 'pipeline' => $pipelineId]\n );\n }\n\n $this->cachedBusinessProcesses[$pipelineId] = $businessProcess;\n\n return $businessProcess;\n }\n\n private function getBusinessProcess(string $pipelineId): ?BusinessProcess\n {\n return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);\n }\n\n private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage\n {\n if (empty($stageId)) {\n return null;\n }\n\n $cacheKey = $businessProcess->getId() . ':' . $stageId;\n if (isset($this->cachedStages[$cacheKey])) {\n return $this->cachedStages[$cacheKey];\n }\n\n $stage = $this->crmEntityRepository->getPipelineStageByConditions(\n $businessProcess,\n [\n 'crm_provider_id' => $stageId,\n 'type' => Stage::TYPE_OPPORTUNITY,\n ]\n );\n\n if ($stage === null) {\n $this->importStages(null, $stageId);\n }\n\n if ($stage === null) {\n $this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);\n }\n\n $this->cachedStages[$cacheKey] = $stage;\n\n return $stage;\n }\n\n private function resolveAmount(array $properties): ?string\n {\n $amount = null;\n if (! empty($properties['amount'])) {\n $amount = str_replace(',', '', $properties['amount']);\n }\n\n if ($this->config->hasDefaultCurrencyFieldSet()) {\n $valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();\n $amount = $properties[$valueFieldName] ?? $amount;\n }\n\n return $amount;\n }\n\n private function parseCleanDatetime(string $datetime): ?Carbon\n {\n // Treat pre-1980 values as invalid\n $minValidDate = Carbon::parse('1980-01-01 00:00:00');\n\n try {\n $date = Carbon::parse($datetime);\n\n if ($minValidDate->gt($date)) {\n return null;\n }\n\n return $date;\n } catch (Exception) {\n return null; // On parse error, treat as null\n }\n }\n\n private function resolveDealProbability(?string $stageProbability): int\n {\n if ($stageProbability === null) {\n return 0;\n }\n\n $probability = (float) $stageProbability;\n\n return $probability > 1 ? 0 : (int) ($probability * 100);\n }\n\n private function resolveForecastCategory(?string $forecastCategory): string\n {\n if (! $forecastCategory) {\n return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;\n }\n\n $forecastCategory = str_replace('_', ' ', $forecastCategory);\n\n return ucwords(strtolower($forecastCategory));\n }\n\n private function importExternalFieldData(array $properties, int $opportunityId): void\n {\n $crmFields = $this->getOpportunitySyncableFields();\n $this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);\n }\n\n private function importOpportunityContacts(Opportunity $opportunity, array $associations): void\n {\n // Handle empty or missing contact associations\n if (empty($associations)) {\n // Remove all existing contact associations if none provided\n $this->removeAllOpportunityContacts($opportunity);\n\n return;\n }\n\n // Use differential sync approach for better performance and accuracy\n $this->syncOpportunityContactsDifferential($opportunity, $associations);\n }\n\n /**\n * Sync opportunity contacts using differential approach\n * This compares current vs new associations and only makes necessary changes\n */\n private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void\n {\n $currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);\n $contactAssociationIds = array_keys($contactAssociations);\n\n $contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);\n $contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);\n\n if (empty($contactsToAdd) && empty($contactsToRemove)) {\n return;\n }\n\n $this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);\n\n $this->removeContactAssociations($opportunity, $contactsToRemove);\n $this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);\n }\n\n private function getCurrentContactCrmIds(Opportunity $opportunity): array\n {\n return $opportunity->contacts()\n ->pluck('contacts.crm_provider_id')\n ->toArray();\n }\n\n private function logContactAssociationChanges(\n Opportunity $opportunity,\n array $currentContactCrmIds,\n array $contactAssociations,\n array $contactsToAdd,\n array $contactsToRemove\n ): void {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [\n 'opportunity_id' => $opportunity->getId(),\n 'current_contacts' => $currentContactCrmIds,\n 'new_contacts' => $contactAssociations,\n 'contacts_to_add' => $contactsToAdd,\n 'contacts_to_remove' => $contactsToRemove,\n ]);\n }\n\n private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void\n {\n if (empty($contactsToRemove)) {\n return;\n }\n\n $contactsToDetach = $opportunity->contacts()\n ->whereIn('contacts.crm_provider_id', $contactsToRemove)\n ->pluck('contacts.id')\n ->toArray();\n\n if (! empty($contactsToDetach)) {\n $opportunity->contacts()->detach($contactsToDetach);\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_contact_crm_ids' => $contactsToRemove,\n 'removed_contact_count' => count($contactsToDetach),\n ]);\n }\n }\n\n private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void\n {\n if (empty($contactsToAdd)) {\n return;\n }\n\n $contactsAdded = [];\n foreach ($contactsToAdd as $crmId) {\n $id = $contactAssociations[$crmId];\n\n if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {\n $contactsAdded[] = $crmId;\n }\n }\n\n $this->logAddedContacts($opportunity, $contactsAdded);\n }\n\n private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);\n\n if (! $contact) {\n return false;\n }\n\n return $this->performContactAttachment($opportunity, $contact, $crmId);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [\n 'opportunity_id' => $opportunity->getId(),\n 'contact_crm_id' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool\n {\n try {\n $opportunity->contacts()->attach($contact->getId(), [\n 'crm_provider_id' => $crmId,\n ]);\n\n return true;\n } catch (\\Illuminate\\Database\\QueryException $e) {\n if (str_contains($e->getMessage(), 'Duplicate entry')) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [\n 'contact_id' => $contact->getId(),\n 'contact_crm_id' => $crmId,\n 'opportunity_id' => $opportunity->getId(),\n ]);\n\n return false;\n }\n\n throw $e;\n }\n }\n\n private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void\n {\n if (! empty($contactsAdded)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'contacts_to_add_count' => count($contactsAdded),\n 'added_contact_crm_ids' => $contactsAdded,\n 'added_contacts_count' => count($contactsAdded),\n ]);\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.48359376,"top":0.08611111,"width":0.01015625,"height":0.016666668},"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.49375,"top":0.08611111,"width":0.01015625,"height":0.016666668},"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.5066406,"top":0.08611111,"width":0.01015625,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-347027978161856519
|
-8178087410993426138
|
app_switch
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
"podcast_audio_url"
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
0 results
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Code changed:
Hide
Sync Changes
Hide This Notification
32
2
19
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use HubSpot\Client\Crm\Deals\Model\CollectionResponseAssociatedId;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Models\Account;
use Exception;
use Jiminny\Component\DealInsights\Forecast\Forecast;
use Jiminny\Jobs\Crm\MatchActivitiesToNewOpportunity;
use Jiminny\Models\Contact;
use Jiminny\Models\Crm\BusinessProcess;
use Jiminny\Exceptions\CrmException;
use Jiminny\Models\Opportunity;
use Illuminate\Support\Collection;
use Jiminny\Models\Stage;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Crm\Hubspot\DealFieldsService;
use Jiminny\Services\Crm\Hubspot\OpportunitySyncStrategy\HubspotSingleSyncStrategy;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Services\Crm\OpportunitySyncStrategyResolver;
use Jiminny\Utils\CurrencyFormatter;
/**
* Optimized sync methods for better performance
* These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains
*/
trait OpportunitySyncTrait
{
private const int BATCH_SIZE = 100;
private const int BATCH_PROCESS_SIZE = 800;
protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;
protected CrmEntityRepository $crmEntityRepository;
protected DealFieldsService $dealFieldsService;
private ?array $cachedClosedDealStages = null;
private array $cachedBusinessProcesses = [];
private array $cachedStages = [];
public function syncOpportunities(array $parameters, ?string $strategy = null): int
{
$strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);
$parameters['config'] = $this->config;
$syncCount = 0;
$reportedTotal = 0;
$lastSyncedId = [];
try {
foreach ($strategies as $strategyName => $syncStrategy) {
$this->logger->info(
'[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .
$strategyName
);
$total = 0;
$lastId = null;
$buffer = [];
// HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies
foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {
$buffer[] = $hsOpportunity;
// process every 800 rows (fits < 1 000 association limit)
if (\count($buffer) >= self::BATCH_PROCESS_SIZE) {
$syncCount += $this->processOpportunityBatch($buffer);
$buffer = [];
}
}
// leftovers
if ($buffer) {
$syncCount += $this->processOpportunityBatch($buffer);
}
$reportedTotal += $total;
$lastSyncedId = $lastId;
}
} catch (\HubSpot\Client\Crm\Deals\ApiException | CrmException $e) {
$this->handleSyncException($e, $parameters);
}
$this->logger->info(
'[HubSpot] Synced opportunities',
[
'team' => $this->team->getId(),
'sync_count' => $syncCount,
'total' => $reportedTotal,
'last_synced_id' => $lastSyncedId,
]
);
return $reportedTotal;
}
private function handleSyncException(\Throwable $e, array $parameters): void
{
if (($parameters['since'] ?? null) instanceof Carbon) {
$parameters['since'] = $parameters['since']->toDateTimeString();
}
$parameters['config'] = $this->config->getId();
$this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [
'teamId' => $this->team->getUuid(),
'parameters' => $parameters,
'reason' => $e->getMessage(),
]);
}
/**
* @inheritdoc
*/
public function syncOpportunity(string $crmId): ?Opportunity
{
$strategy = $this->opportunitySyncStrategyResolver->resolve(
$this->config,
OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,
);
$parameters = [
'config' => $this->config,
'crm_id' => $crmId,
];
try {
if (! $strategy instanceof HubspotSingleSyncStrategy) {
throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');
}
$hsOpportunity = $strategy->fetchOpportunity($parameters);
} catch (\HubSpot\Client\Crm\Deals\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
$hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);
return $this->importOrUpdateOpportunity($hsOpportunity);
}
/**
* Process webhook-collected opportunity batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportOpportunityBatch jobs for batch processing.
*
* @return int Number of opportunity IDs dispatched to jobs
*/
public function batchSyncOpportunities(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,
$configId
);
}
/**
* Import a batch of opportunities by their CRM IDs.
* Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().
*
* @param array<string> $crmIds HubSpot deal CRM IDs
*
* @return array{success: array, failed_ids: array, errors?: array<string, string>}
*/
public function importOpportunityBatchByIds(array $crmIds): array
{
$fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);
$allDeals = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
$deals = $this->client->getOpportunitiesByIds($chunk, $fields);
foreach ($deals as $deal) {
$allDeals[] = $deal;
}
}
// IDs not returned by HubSpot are likely deleted or inaccessible deals.
// These are not failures — retrying won't bring them back.
$fetchedIds = array_map('strval', array_column($allDeals, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allDeals),
]);
}
if (empty($allDeals)) {
return ['success' => [], 'failed_ids' => []];
}
return $this->importOpportunityBatch($allDeals);
}
private function getClosedDealStages(): array
{
if ($this->cachedClosedDealStages !== null) {
return $this->cachedClosedDealStages;
}
$stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);
$data = [
'lost' => [],
'won' => [],
];
foreach ($stages as $stage) {
if ($stage->probability == 0.00) {
$data['lost'][] = $stage->crm_provider_id;
}
if ($stage->probability == 100.00) {
$data['won'][] = $stage->crm_provider_id;
}
}
$this->cachedClosedDealStages = $data;
return $data;
}
/**
* Import deals into the database with pre-fetched associations.
*
* API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT
* caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()
* where Laravel retries the whole job with backoff. After all retries exhausted,
* failed() requeues all IDs to Redis.
*
* The per-deal loop catches exceptions individually. A deal can end up in three states:
* - success: imported/updated successfully
* - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)
* These are permanent issues — retrying won't fix them.
* - skipped (null): missing dependencies (no account, unknown pipeline/stage).
* This is acceptable — the deal cannot be imported until those exist.
*/
private function importOpportunityBatch(array $deals): array
{
$syncedOpportunities = [
'success' => [],
'failed_ids' => [],
];
$dealIds = array_column($deals, 'id');
// Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the
// queue job retries the whole batch and eventually requeues all deal IDs back to Redis.
try {
$companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');
$contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');
$associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);
$existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(
$this->config,
array_map('strval', $dealIds)
);
$existingCrmIdSet = array_flip($existingCrmIds);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [
'teamId' => $this->team->getId(),
'dealCount' => count($dealIds),
'error' => $e->getMessage(),
]);
throw $e;
}
foreach ($deals as $deal) {
try {
$deal['associations'] = $this->prepareAssociationsForOpportunity(
$deal['id'],
$companyAssociations,
$contactAssociations,
$associationsData
);
$syncedOpportunity = $this->importOrUpdateOpportunity(
$deal,
isset($existingCrmIdSet[(string) $deal['id']])
);
if ($syncedOpportunity) {
$syncedOpportunities['success'][] = $syncedOpportunity;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [
'teamId' => $this->team->getId(),
'crmId' => $deal['id'],
'error' => $e->getMessage(),
]);
$syncedOpportunities['failed_ids'][] = $deal['id'];
$syncedOpportunities['errors'][$deal['id']] = $e->getMessage();
}
}
return $syncedOpportunities;
}
/**
* Prepare associated entities for opportunities with optimized batch processing
* Returns structured data with CRM ID to DB ID mappings for each opportunity
*/
private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array
{
// Step 1: Collect all unique company and contact IDs from associations
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
// Step 2: Batch sync missing entities and get CRM ID to DB ID mappings
$companyIdMappings = [];
$contactIdMappings = [];
if (! empty($allCompanyIds)) {
$companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);
}
if (! empty($allContactIds)) {
$contactIdMappings = $this->prepareAssociatedContacts($allContactIds);
}
return [
'company_id_mappings' => $companyIdMappings,
'contact_id_mappings' => $contactIdMappings,
];
}
/**
* Flatten association data to get unique IDs
*/
private function flattenAssociationIds(array $associations): array
{
$ids = [];
foreach ($associations as $dealAssociations) {
if (is_array($dealAssociations)) {
foreach ($dealAssociations as $id) {
$ids[$id] = true;
}
}
}
return array_keys($ids);
}
/**
* Batch sync missing accounts
*/
private function prepareAssociatedAccounts(array $companyIds): array
{
// Find which accounts already exist
$existingAccounts = $this->crmEntityRepository
->findAccountsByExternalIds($this->config, $companyIds);
$existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();
$existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {
return [$account->getCrmProviderId() => $account->getId()];
})->toArray();
$missingCompanyIds = array_diff($companyIds, $existingCompanyIds);
if (empty($missingCompanyIds)) {
return $existingAccountsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [
'teamId' => $this->team->getUuid(),
'total_companies' => count($companyIds),
'existing_companies' => count($existingCompanyIds),
'missing_companies' => count($missingCompanyIds),
]);
// we already have limit on opportunity ids count
// Initialize variable before try block
$syncedAccountsData = [];
try {
$syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [
'size' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
$syncedAccountsData = [];
}
return $existingAccountsData + $syncedAccountsData;
}
/**
* Prepare associated contacts - find existing and sync missing ones
* Returns mapping of CRM ID to DB ID
*/
private function prepareAssociatedContacts(array $contactIds): array
{
// Find which contacts already exist
$existingContacts = $this->crmEntityRepository
->findContactsByExternalIds($this->config, $contactIds);
$existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();
// Create mapping for existing contacts
$existingContactsData = $existingContacts->mapWithKeys(function ($contact) {
return [$contact->getCrmProviderId() => $contact->getId()];
})->toArray();
$missingContactIds = array_diff($contactIds, $existingContactIds);
if (empty($missingContactIds)) {
return $existingContactsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [
'teamId' => $this->team->getUuid(),
'total_contacts' => count($contactIds),
'existing_contacts' => count($existingContactIds),
'missing_contacts' => count($missingContactIds),
]);
// Sync missing contacts using batch API
try {
$syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [
'size' => count($missingContactIds),
'error' => $e->getMessage(),
]);
$syncedContactsData = [];
}
return $existingContactsData + $syncedContactsData;
}
private function batchSyncCrmObjects(string $objectType, array $crmIds): array
{
$syncObjects = [];
$crmObjectIds = array_values($crmIds);
foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {
try {
$objects = $objectType === 'companies' ?
$this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :
$this->client->getContactsByIds($chunk, $this->getContactFields());
foreach ($objects as $objectId => $objectData) {
$this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [
'requested_count' => count($chunk),
'synced_count' => count($objects),
]);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [
'ids' => $chunk,
'error' => $e->getMessage(),
]);
}
}
return $syncObjects;
}
private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void
{
try {
$object = $objectType === 'companies' ?
$this->importAccount($objectData) :
$this->importContact($objectData);
if ($object) {
$syncObjects[$object->getCrmProviderId()] = $object->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [
'id' => $objectId,
'error' => $e->getMessage(),
]);
}
}
/**
* Prepare associations for a single opportunity
*
* The return value is an array with the following structure:
* [
* 'companies' => [
* $companyCrmId => $companyId,
* ...
* ],
* 'contacts' => [
* $contactCrmId => $contactId,
* ...
* ],
* 'account_id' => $accountId,
* ]
*/
private function prepareAssociationsForOpportunity(
string $oppCrmId,
array $companyAssociations,
array $contactAssociations,
array $associationsData
): array {
$associations = [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
$oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];
foreach ($oppCompanyIds as $companyCrmId) {
if (isset($associationsData['company_id_mappings'][$companyCrmId])) {
$associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];
// Set primary account (first company becomes primary account)
if ($associations['account_id'] === null) {
$associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];
}
}
}
$oppContactIds = $contactAssociations[$oppCrmId] ?? [];
foreach ($oppContactIds as $contactCrmId) {
if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {
$associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];
}
}
return $associations;
}
/**
* Update only associations for an opportunity
*/
private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void
{
// Update contact associations
$this->importOpportunityContacts($opportunity, $associations['contacts']);
// Update company (account) associations
$this->updateOpportunityAccount($opportunity, $associations['account_id']);
}
/**
* Remove all contact associations from an opportunity
*/
private function removeAllOpportunityContacts(Opportunity $opportunity): void
{
$currentCount = (int) $opportunity->contacts()->count();
if ($currentCount > 0) {
$opportunity->contacts()->detach();
$this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_count' => $currentCount,
]);
}
}
private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void
{
if ($accountId === null) {
// No account ID provided - keep current account
return;
}
$currentAccountId = $opportunity->getAccountId();
// Only update if account has changed
if ($currentAccountId !== $accountId) {
$opportunity->account_id = $accountId;
$opportunity->save();
$this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [
'opportunity_id' => $opportunity->getId(),
'old_account_id' => $currentAccountId,
'new_account_id' => $accountId,
]);
}
}
/**
* Find existing opportunities by external IDs (OPTIMIZED VERSION)
* Uses batch query for better performance
*/
private function findExistingOpportunities(array $crmIds): Collection
{
return $this->crmEntityRepository
->findOpportunitiesByExternalIds($this->config, $crmIds);
}
private function processOpportunityBatch(array $opportunities): int
{
$syncedOpportunities = $this->importOpportunityBatch($opportunities);
return count($syncedOpportunities['success'] ?? []);
}
/**
* Convert single deal associations from HubSpot format to internal format
* Handles both HubSpot SDK objects and array formats
*
* @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed
*
* @return array Processed associations with DB IDs
*/
private function convertDealAssociations(array $opportunityAssociations): array
{
$associations = $this->initializeAssociationsStructure();
if (empty($opportunityAssociations)) {
return $associations;
}
$associationIds = $this->extractAssociationIds($opportunityAssociations);
$this->processCompanyAssociations($associationIds, $associations);
$this->processContactAssociations($associationIds, $associations);
return $associations;
}
private function initializeAssociationsStructure(): array
{
return [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
}
private function extractAssociationIds(array $opportunityAssociations): array
{
$associationIds = [];
foreach ($opportunityAssociations as $type => $associationData) {
if (! empty($associationData)) {
$associationIds[$type] = $this->convertSingleDealAssociations($associationData);
}
}
return $associationIds;
}
private function processCompanyAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['companies'])) {
return;
}
$companyId = $associationIds['companies'][0];
$account = $this->findOrSyncAccount($companyId);
if ($account instanceof Account) {
$associations['companies'][$companyId] = $account->getId();
$associations['account_id'] = $account->getId();
}
}
private function processContactAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['contacts'])) {
return;
}
foreach ($associationIds['contacts'] as $contactId) {
$contact = $this->findOrSyncContact($contactId);
if ($contact instanceof Contact) {
$associations['contacts'][$contactId] = $contact->getId();
}
}
}
private function findOrSyncAccount(string $companyId): ?Account
{
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);
if (! $account instanceof Account) {
$account = $this->syncAccount($companyId);
}
return $account;
}
private function findOrSyncContact(string $contactId): ?Contact
{
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);
if (! $contact instanceof Contact) {
$contact = $this->syncContact($contactId);
}
return $contact;
}
private function convertSingleDealAssociations($opportunityAssociations = null): array
{
$associationData = [];
if ($opportunityAssociations === null) {
return $associationData;
}
// Handle array input (from extractAssociationIds)
if (is_array($opportunityAssociations)) {
return $opportunityAssociations;
}
// Handle CollectionResponseAssociatedId object
if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {
foreach ($opportunityAssociations->getResults() as $association) {
$associationData[] = $association->getId();
}
}
return $associationData;
}
private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity
{
if (empty($crmData['properties'])) {
return null;
}
$crmId = (string) $crmData['id'];
$properties = $crmData['properties'];
$associations = $crmData['associations'] ?? [];
$opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(
$this->config,
$crmId
);
if ($opportunityExists) {
return $this->updateOpportunity($crmId, $properties, $associations);
} else {
return $this->createOpportunity($crmId, $properties, $associations);
}
}
/**
* Create new opportunity
*/
private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity
{
$accountId = $this->resolveAccountId($associations);
if (! $accountId) {
return null;
}
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
if (! $businessProcess) {
return null;
}
$stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);
if (! $stage) {
return null;
}
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->importOpportunityContacts($opportunity, $associations['contacts']);
if ($opportunity->wasRecentlyCreated) {
MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());
}
return $opportunity;
}
/**
* Update existing opportunity
*/
private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity
{
$accountId = $this->resolveAccountId($associations);
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
$stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->updateOpportunityAssociations($opportunity, $associations);
return $opportunity;
}
private function resolveAccountId(array $associations): ?int
{
if (! empty($associations['accountId'])) {
return $associations['accountId'];
}
if (empty($associations)) {
return null;
}
// we can't resolve multiple account ids (currently SDK returns one company)
foreach ($associations['companies'] as $accountId) {
return $accountId;
}
return null;
}
private function buildOpportunityData(
array $properties,
?int $accountId,
?BusinessProcess $businessProcess,
?Stage $stage
): array {
$ownerId = null;
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$name = 'Unknown';
if (isset($properties['dealname'])) {
$name = mb_strimwidth($properties['dealname'], 0, 128);
}
$amount = $this->resolveAmount($properties);
$currency = $properties['deal_currency_code'] ?? null;
$closeDate = null;
if (! empty($properties['closedate'])) {
$closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');
}
$remotelyCreatedAt = null;
if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {
$date = $this->parseCleanDatetime($properties['createdate']);
$remotelyCreatedAt = $date?->format('Y-m-d H:i:s');
}
$closedStages = $this->getClosedDealStages();
$isWon = in_array($properties['dealstage'], $closedStages['won']);
$isLost = in_array($properties['dealstage'], $closedStages['lost']);
$data = [
'team_id' => $this->team->getId(),
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => $name,
'value' => ! empty($amount) ? $amount : null,
'currency_code' => CurrencyFormatter::formatCode($currency),
'close_date' => $closeDate,
'is_closed' => $isWon || $isLost,
'is_won' => $isWon,
'remotely_created_at' => $remotelyCreatedAt,
'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),
'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),
];
if ($accountId) {
$data['account_id'] = $accountId;
}
if ($stage) {
$data['stage_id'] = $stage->id;
}
if ($businessProcess) {
$recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);
if ($recordType) {
$data['record_type_id'] = $recordType->id;
}
}
return $data;
}
private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess
{
if ($pipelineId === null) {
return null;
}
if (isset($this->cachedBusinessProcesses[$pipelineId])) {
return $this->cachedBusinessProcesses[$pipelineId];
}
$businessProcess = $this->getBusinessProcess($pipelineId);
if (! $businessProcess instanceof BusinessProcess) {
$this->importStages();
$businessProcess = $this->getBusinessProcess($pipelineId);
}
if (! $businessProcess instanceof BusinessProcess) {
$this->logger->info(
'[HubSpot] Deal is not attached to a pipeline',
[
'pipeline' => $pipelineId]
);
}
$this->cachedBusinessProcesses[$pipelineId] = $businessProcess;
return $businessProcess;
}
private function getBusinessProcess(string $pipelineId): ?BusinessProcess
{
return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);
}
private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage
{
if (empty($stageId)) {
return null;
}
$cacheKey = $businessProcess->getId() . ':' . $stageId;
if (isset($this->cachedStages[$cacheKey])) {
return $this->cachedStages[$cacheKey];
}
$stage = $this->crmEntityRepository->getPipelineStageByConditions(
$businessProcess,
[
'crm_provider_id' => $stageId,
'type' => Stage::TYPE_OPPORTUNITY,
]
);
if ($stage === null) {
$this->importStages(null, $stageId);
}
if ($stage === null) {
$this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);
}
$this->cachedStages[$cacheKey] = $stage;
return $stage;
}
private function resolveAmount(array $properties): ?string
{
$amount = null;
if (! empty($properties['amount'])) {
$amount = str_replace(',', '', $properties['amount']);
}
if ($this->config->hasDefaultCurrencyFieldSet()) {
$valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();
$amount = $properties[$valueFieldName] ?? $amount;
}
return $amount;
}
private function parseCleanDatetime(string $datetime): ?Carbon
{
// Treat pre-1980 values as invalid
$minValidDate = Carbon::parse('1980-01-01 00:00:00');
try {
$date = Carbon::parse($datetime);
if ($minValidDate->gt($date)) {
return null;
}
return $date;
} catch (Exception) {
return null; // On parse error, treat as null
}
}
private function resolveDealProbability(?string $stageProbability): int
{
if ($stageProbability === null) {
return 0;
}
$probability = (float) $stageProbability;
return $probability > 1 ? 0 : (int) ($probability * 100);
}
private function resolveForecastCategory(?string $forecastCategory): string
{
if (! $forecastCategory) {
return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;
}
$forecastCategory = str_replace('_', ' ', $forecastCategory);
return ucwords(strtolower($forecastCategory));
}
private function importExternalFieldData(array $properties, int $opportunityId): void
{
$crmFields = $this->getOpportunitySyncableFields();
$this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);
}
private function importOpportunityContacts(Opportunity $opportunity, array $associations): void
{
// Handle empty or missing contact associations
if (empty($associations)) {
// Remove all existing contact associations if none provided
$this->removeAllOpportunityContacts($opportunity);
return;
}
// Use differential sync approach for better performance and accuracy
$this->syncOpportunityContactsDifferential($opportunity, $associations);
}
/**
* Sync opportunity contacts using differential approach
* This compares current vs new associations and only makes necessary changes
*/
private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void
{
$currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);
$contactAssociationIds = array_keys($contactAssociations);
$contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);
$contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);
if (empty($contactsToAdd) && empty($contactsToRemove)) {
return;
}
$this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);
$this->removeContactAssociations($opportunity, $contactsToRemove);
$this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);
}
private function getCurrentContactCrmIds(Opportunity $opportunity): array
{
return $opportunity->contacts()
->pluck('contacts.crm_provider_id')
->toArray();
}
private function logContactAssociationChanges(
Opportunity $opportunity,
array $currentContactCrmIds,
array $contactAssociations,
array $contactsToAdd,
array $contactsToRemove
): void {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [
'opportunity_id' => $opportunity->getId(),
'current_contacts' => $currentContactCrmIds,
'new_contacts' => $contactAssociations,
'contacts_to_add' => $contactsToAdd,
'contacts_to_remove' => $contactsToRemove,
]);
}
private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void
{
if (empty($contactsToRemove)) {
return;
}
$contactsToDetach = $opportunity->contacts()
->whereIn('contacts.crm_provider_id', $contactsToRemove)
->pluck('contacts.id')
->toArray();
if (! empty($contactsToDetach)) {
$opportunity->contacts()->detach($contactsToDetach);
$this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_contact_crm_ids' => $contactsToRemove,
'removed_contact_count' => count($contactsToDetach),
]);
}
}
private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void
{
if (empty($contactsToAdd)) {
return;
}
$contactsAdded = [];
foreach ($contactsToAdd as $crmId) {
$id = $contactAssociations[$crmId];
if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {
$contactsAdded[] = $crmId;
}
}
$this->logAddedContacts($opportunity, $contactsAdded);
}
private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool
{
try {
$contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);
if (! $contact) {
return false;
}
return $this->performContactAttachment($opportunity, $contact, $crmId);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [
'opportunity_id' => $opportunity->getId(),
'contact_crm_id' => $crmId,
'error' => $e->getMessage(),
]);
return false;
}
}
private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool
{
try {
$opportunity->contacts()->attach($contact->getId(), [
'crm_provider_id' => $crmId,
]);
return true;
} catch (\Illuminate\Database\QueryException $e) {
if (str_contains($e->getMessage(), 'Duplicate entry')) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [
'contact_id' => $contact->getId(),
'contact_crm_id' => $crmId,
'opportunity_id' => $opportunity->getId(),
]);
return false;
}
throw $e;
}
}
private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void
{
if (! empty($contactsAdded)) {
$this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [
'opportunity_id' => $opportunity->getId(),
'contacts_to_add_count' => count($contactsAdded),
'added_contact_crm_ids' => $contactsAdded,
'added_contacts_count' => count($contactsAdded),
]);
}
}
}
Execute
Explain Plan
Browse Query History...
|
NULL
|
|
43327
|
922
|
14
|
2026-04-17T08:03:20.099697+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776413000099_m1.jpg...
|
Firefox
|
Meet - Backend Chapter — Work
|
1
|
meet.google.com/gjc-ikxu-wxu?authuser=lukas.kovali meet.google.com/gjc-ikxu-wxu?authuser=lukas.kovalik%40jiminny.com...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Close tab
[SRD-6793] Les Mills activity types not pulling in - Jira
Close tab
Problem loading page
Close tab
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Close tab
CloudWatch | us-east-2
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Console Home | Console Home | eu-west-1
Close tab
New Tab
Close tab
Meet - Backend Chapter
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
Nikolay Nikolov (Presenting, annotating)
Nikolay Nikolov (Presenting, annotating)
People
3
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Unpin Nikolay Nikolov's presentation from your main screen
You can't unmute someone else's presentation
More options for Nikolay Nikolov
Zoom in
Open in new window
Enter Full Screen
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Pin Nikolay Nikolov to your main screen
Mute Nikolay Nikolov's microphone
More options for Nikolay Nikolov
Nikolay Nikolov
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
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.
11:03
AM
Backend Chapter
Backend Chapter
Audio settings
Turn off microphone
Video settings
Turn off camera
Nikolay Nikolov is presenting
Send a reaction
Turn on captions
Raise hand (ctrl + ⌘ + h)
More options
Leave call
Meeting details
Chat with everyone
Meeting tools
The presentation by Nikolay Nikolov was added to the main screen. The presentation by Nikolay Nikolov is on the main screen....
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"bounds":{"left":0.0,"top":0.072222225,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.13222222,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.13222222,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":4,"bounds":{"left":0.0,"top":0.17777778,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.17777778,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Problem loading page","depth":4,"bounds":{"left":0.0,"top":0.22333333,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.22333333,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Symfony\\Component\\Debug\\Exception\\FatalThrowableError: 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","depth":4,"bounds":{"left":0.0,"top":0.2688889,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.2688889,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0,"top":0.31444445,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.31444445,"width":0.010416667,"height":0.016666668},"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.36,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.36,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Console Home | Console Home | eu-west-1","depth":4,"bounds":{"left":0.0,"top":0.40555555,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.40555555,"width":0.010416667,"height":0.016666668},"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.4511111,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.4511111,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Meet - Backend Chapter","depth":4,"bounds":{"left":0.0,"top":0.49666667,"width":0.033680554,"height":0.045555554},"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.49666667,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.005902778,"top":0.54444444,"width":0.022222223,"height":0.035555556},"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},"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},"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},"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},"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},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Nikolay Nikolov (Presenting, annotating)","depth":12,"bounds":{"left":0.07534722,"top":0.101111114,"width":0.17916666,"height":0.022222223},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Nikolov (Presenting, annotating)","depth":13,"bounds":{"left":0.07534722,"top":0.10222222,"width":0.17916666,"height":0.020555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"People","depth":15,"bounds":{"left":0.88680553,"top":0.08944444,"width":0.04097222,"height":0.04},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"3","depth":22,"bounds":{"left":0.9145833,"top":0.101111114,"width":0.0048611113,"height":0.017222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Take notes with Gemini","depth":14,"bounds":{"left":0.93333334,"top":0.08944444,"width":0.025,"height":0.04},"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":17,"bounds":{"left":0.9361111,"top":0.101111114,"width":0.06388891,"height":0.017222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini","depth":22,"bounds":{"left":0.96666664,"top":0.101111114,"width":0.028125,"height":0.017222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Gemini","depth":21,"bounds":{"left":0.96458334,"top":0.090555556,"width":0.023611112,"height":0.037777778},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"bounds":{"left":0.5798611,"top":0.61,"width":0.14652778,"height":0.08888889},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"bounds":{"left":0.72430557,"top":0.625,"width":0.08090278,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"bounds":{"left":0.7017361,"top":0.6205556,"width":0.11076389,"height":0.05666667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Unpin Nikolay Nikolov's presentation from your main screen","depth":13,"bounds":{"left":0.34618056,"top":0.5088889,"width":0.027777778,"height":0.044444446},"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":13,"bounds":{"left":0.37395832,"top":0.50666666,"width":0.030555556,"height":0.04888889},"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":13,"bounds":{"left":0.4045139,"top":0.5088889,"width":0.027777778,"height":0.044444446},"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":13,"bounds":{"left":0.63090277,"top":0.78333336,"width":0.027777778,"height":0.044444446},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open in new window","depth":13,"bounds":{"left":0.6642361,"top":0.78333336,"width":0.027777778,"height":0.044444446},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Enter Full Screen","depth":13,"bounds":{"left":0.69756943,"top":0.78333336,"width":0.027777778,"height":0.044444446},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"bounds":{"left":0.9201389,"top":0.36666667,"width":0.079861104,"height":0.07722222},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"bounds":{"left":1.0,"top":0.38166666,"width":-0.064236164,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"bounds":{"left":1.0,"top":0.3772222,"width":-0.042013884,"height":0.045},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Pin Nikolay Nikolov to your main screen","depth":13,"bounds":{"left":0.82395834,"top":0.31555554,"width":0.027777778,"height":0.044444446},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Mute Nikolay Nikolov's microphone","depth":13,"bounds":{"left":0.8517361,"top":0.31333333,"width":0.030555556,"height":0.04888889},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"More options for Nikolay Nikolov","depth":13,"bounds":{"left":0.8822917,"top":0.31555554,"width":0.027777778,"height":0.044444446},"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":17,"bounds":{"left":0.75590277,"top":0.485,"width":0.07847222,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"bounds":{"left":1.0,"top":0.75333333,"width":-0.0006943941,"height":0.07722222},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"bounds":{"left":0.92743057,"top":0.7683333,"width":0.07256943,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"bounds":{"left":0.9079861,"top":0.7638889,"width":0.092013896,"height":0.045},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"You’re continuously framed","depth":13,"bounds":{"left":0.82256943,"top":0.7,"width":0.030555556,"height":0.04888889},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Backgrounds and effects","depth":13,"bounds":{"left":0.853125,"top":0.7,"width":0.030555556,"height":0.04888889},"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":13,"bounds":{"left":0.8836806,"top":0.7022222,"width":0.027777778,"height":0.044444446},"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":17,"bounds":{"left":0.75590277,"top":0.87166667,"width":0.06875,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Others might see more of your background. Click to view your full video.","depth":14,"bounds":{"left":0.9614583,"top":0.86722225,"width":0.019444445,"height":0.031111112},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"11:03","depth":12,"bounds":{"left":0.050347224,"top":0.9444444,"width":0.02673611,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AM","depth":12,"bounds":{"left":0.08055556,"top":0.9444444,"width":0.017708333,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Backend Chapter","depth":12,"bounds":{"left":0.115625,"top":0.9111111,"width":0.08993056,"height":0.08888888},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Backend Chapter","depth":15,"bounds":{"left":0.115625,"top":0.9444444,"width":0.08993056,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Audio settings","depth":13,"bounds":{"left":0.32118055,"top":0.9288889,"width":0.06111111,"height":0.053333335},"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":13,"bounds":{"left":0.34895834,"top":0.9288889,"width":0.033333335,"height":0.053333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Video settings","depth":13,"bounds":{"left":0.38784721,"top":0.9288889,"width":0.06111111,"height":0.053333335},"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":13,"bounds":{"left":0.415625,"top":0.9288889,"width":0.033333335,"height":0.053333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Nikolay Nikolov is presenting","depth":12,"bounds":{"left":0.45451388,"top":0.9288889,"width":0.03888889,"height":0.053333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Send a reaction","depth":12,"bounds":{"left":0.49895832,"top":0.9288889,"width":0.03888889,"height":0.053333335},"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":13,"bounds":{"left":0.5434028,"top":0.9288889,"width":0.03888889,"height":0.053333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Raise hand (ctrl + ⌘ + h)","depth":12,"bounds":{"left":0.58784723,"top":0.9288889,"width":0.03888889,"height":0.053333335},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"More options","depth":12,"bounds":{"left":0.6322917,"top":0.9288889,"width":0.025,"height":0.053333335},"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":12,"bounds":{"left":0.6628472,"top":0.9288889,"width":0.05,"height":0.053333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Meeting details","depth":12,"bounds":{"left":0.89166665,"top":0.9288889,"width":0.033333335,"height":0.053333335},"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":12,"bounds":{"left":0.925,"top":0.9288889,"width":0.033333335,"height":0.053333335},"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":12,"bounds":{"left":0.9583333,"top":0.9288889,"width":0.033333335,"height":0.053333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"The presentation by Nikolay Nikolov was added to the main screen. The presentation by Nikolay Nikolov is on the main screen.","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
5445154472650180529
|
-6453401609438004432
|
app_switch
|
hybrid
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Close tab
[SRD-6793] Les Mills activity types not pulling in - Jira
Close tab
Problem loading page
Close tab
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Close tab
CloudWatch | us-east-2
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Console Home | Console Home | eu-west-1
Close tab
New Tab
Close tab
Meet - Backend Chapter
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
Nikolay Nikolov (Presenting, annotating)
Nikolay Nikolov (Presenting, annotating)
People
3
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Unpin Nikolay Nikolov's presentation from your main screen
You can't unmute someone else's presentation
More options for Nikolay Nikolov
Zoom in
Open in new window
Enter Full Screen
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Pin Nikolay Nikolov to your main screen
Mute Nikolay Nikolov's microphone
More options for Nikolay Nikolov
Nikolay Nikolov
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
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.
11:03
AM
Backend Chapter
Backend Chapter
Audio settings
Turn off microphone
Video settings
Turn off camera
Nikolay Nikolov is presenting
Send a reaction
Turn on captions
Raise hand (ctrl + ⌘ + h)
More options
Leave call
Meeting details
Chat with everyone
Meeting tools
The presentation by Nikolay Nikolov was added to the main screen. The presentation by Nikolay Nikolov is on the main screen.
FirefoxFileEditViewHistory→Nikolay Nikolov (Presenting, annotating)BookmarksProfilesToolsWindowHelp(allBackend Chapter • 27 m leftmeet.google.com/gjc-ikxu-wxu?authuser=lukas.kovalik%40jiminny.com100% C47 8• Fri 17 Apr 11:03:193SQL EditorDatabase*<PROD US> jiminny_nki_Jocal.sol XFri 17 Ape 11:03B3 Projec# LOCAL devAlesanоstPRoDtUlocalhost:74.32ASTAGE localhost7732# Staging_root localhost7732+select * fron failed_jobs fj where fj-queue like 'tanalytics_low' order by id desc;|-- REPORTS |select * fron opportunities where id = 7594349}select « fron opportunity_stages os where os.opportunity_id = 7594349 and os.created_at > '2026-04-17 04:20:00' order by os.created_at desc;select count(») fron opportunity_stages os where os.opportunity_id = 7594349 and os.created_at > '2026-84-17 04:20:00' order by os.created_at asc;select distinct os.stage_id from opportunity_stages os where os.opportunity_id = 7594349 and os.created_at > '2026-04-17 04:20:00' order by os.created_at aSELECT stage_id, COUNT(*) as count, MIN(created_at), MAX(created_at)FROM opportunity_stagesWHEREopportunity_id = 7594349ukour br stage. tosNikolay Nikolov116112026.803Paneis 8 ® E # Eюбку ?meet.google.com is sharing your screen.Stop sharing201s (0.001s fetch), on 2026-04-17 a: 10:52:181095 :1: 79365Sel: 010Lukas Kovalik11:03 AM | Backend Chapter...
|
NULL
|
|
43333
|
922
|
17
|
2026-04-17T08:03:31.620411+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776413011620_m1.jpg...
|
Firefox
|
Meet - Backend Chapter — Work
|
1
|
meet.google.com/gjc-ikxu-wxu?authuser=lukas.kovali meet.google.com/gjc-ikxu-wxu?authuser=lukas.kovalik%40jiminny.com...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Close tab
[SRD-6793] Les Mills activity types not pulling in - Jira
Close tab
Problem loading page
Close tab
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Close tab
CloudWatch | us-east-2
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Console Home | Console Home | eu-west-1
Close tab
New Tab
Close tab
Meet - Backend Chapter
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
Nikolay Nikolov (Presenting, annotating)
Nikolay Nikolov (Presenting, annotating)
People
3
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Unpin Nikolay Nikolov's presentation from your main screen
You can't unmute someone else's presentation
More options for Nikolay Nikolov
Zoom in
Open in new window
Enter Full Screen
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Pin Nikolay Nikolov to your main screen
Mute Nikolay Nikolov's microphone
More options for Nikolay Nikolov
Nikolay Nikolov
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
You’re continuously framed
Backgrounds and effects
More options for Lukas Kovalik...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"bounds":{"left":0.0,"top":0.072222225,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.13222222,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.13222222,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":4,"bounds":{"left":0.0,"top":0.17777778,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.17777778,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Problem loading page","depth":4,"bounds":{"left":0.0,"top":0.22333333,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.22333333,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Symfony\\Component\\Debug\\Exception\\FatalThrowableError: 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","depth":4,"bounds":{"left":0.0,"top":0.2688889,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.2688889,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0,"top":0.31444445,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.31444445,"width":0.010416667,"height":0.016666668},"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.36,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.36,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Console Home | Console Home | eu-west-1","depth":4,"bounds":{"left":0.0,"top":0.40555555,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.40555555,"width":0.010416667,"height":0.016666668},"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.4511111,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.4511111,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Meet - Backend Chapter","depth":4,"bounds":{"left":0.0,"top":0.49666667,"width":0.033680554,"height":0.045555554},"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.49666667,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.005902778,"top":0.54444444,"width":0.022222223,"height":0.035555556},"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},"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},"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},"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},"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},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Nikolay Nikolov (Presenting, annotating)","depth":12,"bounds":{"left":0.07534722,"top":0.101111114,"width":0.17916666,"height":0.022222223},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Nikolov (Presenting, annotating)","depth":13,"bounds":{"left":0.07534722,"top":0.10222222,"width":0.17916666,"height":0.020555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"People","depth":15,"bounds":{"left":0.88680553,"top":0.08944444,"width":0.04097222,"height":0.04},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"3","depth":22,"bounds":{"left":0.9145833,"top":0.101111114,"width":0.0048611113,"height":0.017222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Take notes with Gemini","depth":14,"bounds":{"left":0.93333334,"top":0.08944444,"width":0.025,"height":0.04},"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":17,"bounds":{"left":0.9361111,"top":0.101111114,"width":0.06388891,"height":0.017222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini","depth":22,"bounds":{"left":0.96666664,"top":0.101111114,"width":0.028125,"height":0.017222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Gemini","depth":21,"bounds":{"left":0.96458334,"top":0.090555556,"width":0.023611112,"height":0.037777778},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"bounds":{"left":0.5798611,"top":0.61,"width":0.14652778,"height":0.08888889},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"bounds":{"left":0.72430557,"top":0.625,"width":0.08090278,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"bounds":{"left":0.7017361,"top":0.6205556,"width":0.11076389,"height":0.05666667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Unpin Nikolay Nikolov's presentation from your main screen","depth":13,"bounds":{"left":0.34618056,"top":0.5088889,"width":0.027777778,"height":0.044444446},"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":13,"bounds":{"left":0.37395832,"top":0.50666666,"width":0.030555556,"height":0.04888889},"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":13,"bounds":{"left":0.4045139,"top":0.5088889,"width":0.027777778,"height":0.044444446},"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":13,"bounds":{"left":0.63090277,"top":0.78333336,"width":0.027777778,"height":0.044444446},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open in new window","depth":13,"bounds":{"left":0.6642361,"top":0.78333336,"width":0.027777778,"height":0.044444446},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Enter Full Screen","depth":13,"bounds":{"left":0.69756943,"top":0.78333336,"width":0.027777778,"height":0.044444446},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"bounds":{"left":0.9201389,"top":0.36666667,"width":0.079861104,"height":0.07722222},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"bounds":{"left":1.0,"top":0.38166666,"width":-0.064236164,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"bounds":{"left":1.0,"top":0.3772222,"width":-0.042013884,"height":0.045},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Pin Nikolay Nikolov to your main screen","depth":13,"bounds":{"left":0.82395834,"top":0.31555554,"width":0.027777778,"height":0.044444446},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Mute Nikolay Nikolov's microphone","depth":13,"bounds":{"left":0.8517361,"top":0.31333333,"width":0.030555556,"height":0.04888889},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"More options for Nikolay Nikolov","depth":13,"bounds":{"left":0.8822917,"top":0.31555554,"width":0.027777778,"height":0.044444446},"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":17,"bounds":{"left":0.75590277,"top":0.485,"width":0.07847222,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"bounds":{"left":1.0,"top":0.75333333,"width":-0.0006943941,"height":0.07722222},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"bounds":{"left":0.92743057,"top":0.7683333,"width":0.07256943,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"bounds":{"left":0.9079861,"top":0.7638889,"width":0.092013896,"height":0.045},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"You’re continuously framed","depth":13,"bounds":{"left":0.82256943,"top":0.7,"width":0.030555556,"height":0.04888889},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Backgrounds and effects","depth":13,"bounds":{"left":0.853125,"top":0.7,"width":0.030555556,"height":0.04888889},"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":13,"bounds":{"left":0.8836806,"top":0.7022222,"width":0.027777778,"height":0.044444446},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
2656420774226604358
|
-6471425078918415568
|
app_switch
|
hybrid
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Close tab
[SRD-6793] Les Mills activity types not pulling in - Jira
Close tab
Problem loading page
Close tab
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Close tab
CloudWatch | us-east-2
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Console Home | Console Home | eu-west-1
Close tab
New Tab
Close tab
Meet - Backend Chapter
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
Nikolay Nikolov (Presenting, annotating)
Nikolay Nikolov (Presenting, annotating)
People
3
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Unpin Nikolay Nikolov's presentation from your main screen
You can't unmute someone else's presentation
More options for Nikolay Nikolov
Zoom in
Open in new window
Enter Full Screen
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Pin Nikolay Nikolov to your main screen
Mute Nikolay Nikolov's microphone
More options for Nikolay Nikolov
Nikolay Nikolov
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
You’re continuously framed
Backgrounds and effects
More options for Lukas Kovalik
FirefoxFileEditView→Nikolay Nikolov (Presenting, annotating)HistoryBookmarksProfilesToolsWindowHelp(allBackend Chapter • 27 m leftmeet.google.com/gjc-ikxu-wxu?authuser=lukas.kovalik%40jiminny.com100% C47 8• Fri 17 Apr 11:03:313SOL Editor Database*<PROD US> jiminny_nki_Jocal.sol XFri 17 Apr 11:03CntoUtecYkOU Uouminnyahinieoca,• 0• 8•9-3 ProjecA LOCAL devPlosanostPRoD tUcarloscrose/QA locahost7432•STAGE localhost7732A Staging_root localhost7732+select * fron failed_jobs fj where f)-queue like 'sanalytics_low' order by id desc;-- REPORTS |select * fron opportunities where id = 7594349;select « from opportunity_stages os where os.opportunity_id = 7594349 and Os.created_at > '2026-04-17 04:20:00' order by os.created_at desc;select count(») from opportunity_stages os where os.opportunity_id = 7594349 and os.created_at > '2026-84-17 04:20:00' order by os.created_at asc:select distinct os.stage_id from opportunity_stages os where os.opportunity_id = 7594349 and os.created_at > '2026-04-17 84:20:00' order by os-created_at aSELECT stage_id, COUNT(*) as count, MIN(created_at), MAX(created_at)FROM opportunity_stagesWHEREopportunity_id = 7594349ukuur br stage.3osNikolay Nikolov• close_date2026-07-03@deleted_atWereatecaonowor-tetet7026-03-10 16:1024302070-00000Panels 8 ® l 1 limeet.google.com is sharing your screen.Stop sharing1087 : 48 : 78913Sel: 010Lukas Kovalik11:03 AM | Backend Chapter...
|
NULL
|
|
43334
|
923
|
9
|
2026-04-17T08:03:31.620442+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776413011620_m2.jpg...
|
Firefox
|
Meet - Backend Chapter — Work
|
1
|
meet.google.com/gjc-ikxu-wxu?authuser=lukas.kovali meet.google.com/gjc-ikxu-wxu?authuser=lukas.kovalik%40jiminny.com...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Close tab
[SRD-6793] Les Mills activity types not pulling in - Jira
Close tab
Problem loading page
Close tab
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Close tab
CloudWatch | us-east-2
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Console Home | Console Home | eu-west-1
Close tab
New Tab
Close tab
Meet - Backend Chapter
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
Nikolay Nikolov (Presenting, annotating)
Nikolay Nikolov (Presenting, annotating)
People
3
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Unpin Nikolay Nikolov's presentation from your main screen
You can't unmute someone else's presentation
More options for Nikolay Nikolov
Zoom in
Open in new window
Enter Full Screen
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Pin Nikolay Nikolov to your main screen
Mute Nikolay Nikolov's microphone
More options for Nikolay Nikolov
Nikolay Nikolov
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
You’re continuously framed
Backgrounds and effects...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.018945312,"height":-0.045138836},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.018945312,"height":-0.08263886},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.23398438,"top":1.0,"width":0.005859375,"height":-0.08263886},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Problem loading page","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Symfony\\Component\\Debug\\Exception\\FatalThrowableError: 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","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Console Home | Console Home | eu-west-1","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"New Tab","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Meet - Backend Chapter","depth":4,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXButton","text":"Close tab","depth":5,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Nikolay Nikolov (Presenting, annotating)","depth":12,"bounds":{"left":0.27558595,"top":1.0,"width":0.10078125,"height":-0.063194394},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Nikolov (Presenting, annotating)","depth":13,"bounds":{"left":0.27558595,"top":1.0,"width":0.10078125,"height":-0.06388891},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"People","depth":15,"bounds":{"left":0.7320312,"top":1.0,"width":0.023046875,"height":-0.05590272},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"3","depth":22,"bounds":{"left":0.7476562,"top":1.0,"width":0.002734375,"height":-0.063194394},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Take notes with Gemini","depth":14,"bounds":{"left":0.75820315,"top":1.0,"width":0.0140625,"height":-0.05590272},"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":17,"bounds":{"left":0.7597656,"top":1.0,"width":0.051171876,"height":-0.063194394},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini","depth":22,"bounds":{"left":0.7769531,"top":1.0,"width":0.015820313,"height":-0.063194394},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Gemini","depth":21,"bounds":{"left":0.7757813,"top":1.0,"width":0.01328125,"height":-0.056597233},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Unpin Nikolay Nikolov's presentation from your main screen","depth":13,"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":13,"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":13,"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":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open in new window","depth":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Enter Full Screen","depth":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Pin Nikolay Nikolov to your main screen","depth":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Mute Nikolay Nikolov's microphone","depth":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"More options for Nikolay Nikolov","depth":13,"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":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"You’re continuously framed","depth":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Backgrounds and effects","depth":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
2796550329874451212
|
-6471425078918415568
|
app_switch
|
hybrid
|
NULL
|
Workers | Datadog
Platform Sprint 2 Q2 - Platform Workers | Datadog
Platform Sprint 2 Q2 - Platform Team - Scrum Board - Jira
Close tab
[SRD-6793] Les Mills activity types not pulling in - Jira
Close tab
Problem loading page
Close tab
Symfony\Component\Debug\Exception\FatalThrowableError: 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
Close tab
CloudWatch | us-east-2
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Console Home | Console Home | eu-west-1
Close tab
New Tab
Close tab
Meet - Backend Chapter
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
Nikolay Nikolov (Presenting, annotating)
Nikolay Nikolov (Presenting, annotating)
People
3
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Unpin Nikolay Nikolov's presentation from your main screen
You can't unmute someone else's presentation
More options for Nikolay Nikolov
Zoom in
Open in new window
Enter Full Screen
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Pin Nikolay Nikolov to your main screen
Mute Nikolay Nikolov's microphone
More options for Nikolay Nikolov
Nikolay Nikolov
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
You’re continuously framed
Backgrounds and effects
PhpStormFV faVsco.jsProjectv© Nudae.php© NudgeRun.phpOpportunity.php© Participant.php© Partner.php© Permission.php© PhoneNumber.php© PlaybackTheme.php© Playbook.php© PlaybookCategory.php© Playlist.php© RateLimit.phpC Region.php©Role.php©RoleChangeEvent.php© ScopeGroup.php© Session.php© SlackBot.php© SocialAccount.php© Stage.php© Task.php© Team.php© TeamAiContext.php© TeamDomain.php© TeamFeature.php© TeamSettings.php© TextRelay.php© Track.php© TranscriptionModel.php© TranscriptionModelLoc:© TranscriptionProvider.p© User.php©UserSettings.php© Vocabulary.php© VocabularyPronunciatio© VoiceAccess.php©VoiceConsentPrefix.php› D NotificationsD ObserversC PoliciesO ProvidersD QueueRepositoriesD AIAutoScoringCalendar>DCrm> D Geography© ActiveStreamsRepositol© ActivityCommentRepos© ActivityLogRepository.p© ActivityMessageReposii© ActivityMomentRepositr© ActivityProviderReposit© ActivityRepository.php© Activity SearchFilterRep® ActivityShareRepository© ActivityUploadSettingRi© AiPromptRepository.phyViewNavigateCodeLaravelRefacton( #11894 on JY-18909-automated-reports-ask-jiminny k v© AutomatedReportsService.php© SendReportJob.php© SendReportMailJob.php© ReportController.php© TokenBuilder.php© TeamSetupController.phppnp apl.onp• Filesystem.phpAutomatedReportsCommand.phpAskJiminnykeporscontroller.ono© AutomatedReportsCommandTest.php© AutomatedReportsSendCommand.php© Team.php© AutomatedReportsRepository.php© CreateHeldActivityEvent.php© TrackProviderInstalledEvent.php© CreateActivityLoggedEvent.php© UserPilotActivityListener.phpC ActivityLogged.phpAulomaleakeporscallbackservice.onoC RequestGenerateAskJiminnyReportJob.php© RequestGenerateReportJob.php© SyncOpportunity.php© OpportunitySyncTrait.phpService.php© AutomatedReportResult.php(c) AutomatedReport.onpclass Opportunity extends Model implements158159160'forecast_category',];161162163164protected $appends = [la_scring]:165 G166167168169protected $hidden = ['uvid','id',170 6t >1174protected static function newFactory(): Factoryf...}175 đt >protected static function bootOf...}170/** @deprecated */L77public function deleteExistingAjPrompt(): voidf...}209 6t >220protected function casts(): arrayf...}/** @deprecated */public function getOpportunitySuggestAttribute(): stringf...3226LLIpublic function getFormattedValueAttribute(): stringf...7232244public function getCurrencyCode(): stringf...}24514%public function getName(): stringf..public function isClosed(): boolf...}200public function isWon(): boolf...}260public function activities(): hasMany{...}Helper Code will help IDE to understand your Laravel app code. // Generate // Don't Show Anymore (today 8:59)Backend Chaoter . 27 m lefi100% C•8 • Fri 17 Apr 11:03:31AutomatedReportsCommandTestvA HS_local [iminny@localhost]Al console [EU] X= custom.log= laravel.logfii crm_configurations (EU]V connect.vueV Onboard.vue1550155115521553B15MTAY19501950105/[CREDIT_CARD]—1562-1563156515661567190815691570157115721573157415751080158115871588115821570157110Y41593159415951596159715981599A SF [jiminny@localhost]C" scratch_1.jsonconsole PRODL console [STAGING]Ix. Aulo vFlaycroundvselect * from contacts cwhere c.crm_configuration_id = 370 order by c.updated_at desc;SELECT * FROM participants where activity_id = 38833541;SELECT * FROM participants where activity_id = 39216301;SELECT * FROM activity_summary_logs where activity_id = 39216301;SELECT * FROM activities WHERE uuid_to_bin('c7d99fbe-1fb1-41f2-8f4d-52e2bf70e1e9') = uuid; # 38833541,|Cril 470.11090410SELECT * FROM activitiesYnckr vure to onnceontuon-y1ad-44a-doceyacoe4doonae = Uulo # 07410001.CMII 400.1000900select * from crm_profiles where crm_configuration_id = 319 and crm_provider_id = 525785080;select * from opportunities where crm_configuration_id = 319 and crm_provider_id = 410150124747;select * from accounts where crm_configuration_id = 319 and crm_provider_id = 47150650569;select * from contacts where crm_configuration_id = 319 and crm_provider_id IN ('665587441856','742723347700');# owner 13236 525785080# contact 116779180 665587441856 - activity - Alex Howes [EMAIL] created 2026-01-26# contact 219247563 742723347700 - [EMAIL] 2026-03-24# company 4176133 47150650569# deal 7100953 410150124747SELECTCONCAT(u.id, CASE WHEN U.id = t.owner_id THEN ' (owner)' ELSE "' END) AS user_id,U.ellan tosa.*,t.owner_id FROM social_accounts saJOIN users u on u.id = sa.sociable_idJOIN teams t 1.n<->1: on t.id = u.team_idWHERE U.team_id = 400 and sa.provider = 'hubspot':select * from features;select * from team_features where feature_id = 40;select * from teams where id = 556: # owner: 18101. crm: 477select * from crm_configurations where id = 477;SELECT * FROM users WHERE id = 18101;SELECTCONCAT(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 saJOIN users u on u.id = sa.sociable_idNureanstI.n<->I: on t.1d = U.team_1dWHERE U.team_id = 556 and sa.provider = 'integration-app';select * from opportunities where id = 7594349;select * from stages where team_id = 459;select * from teams where id = 459;SELECTCONCAT(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 saJOIN users u on u.id = sa.sociable_idJOIN teams tI.n<→>I: on t.1d = U.ceam_1dWHERE U.team_1d = 45% and sa.provider = 'nupspor :Eajiminny026 49 422 Х 3 X103 ^d M1111W Windsurf Teams 1:1 UTF-8 f 4 spaces...
|
NULL
|
|
43613
|
926
|
21
|
2026-04-17T08:13:41.432893+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776413621432_m1.jpg...
|
PhpStorm
|
faVsco.js – RunOpportunityAiAnalysis.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsCommandTest","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsCommandTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsCommandTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-482216603274606444
|
1760908322497348928
|
app_switch
|
hybrid
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelpallBackend Chapter • 17 m left100% C8 • Fri 17 Apr 11:13:41meet.google.com/xpx-omah-rkn=+llian Kyuchukov (Presenting, annotating)FileProfies...= 408 • Fri 17 Apr 11:13CloudWatch |eu-west-1CloodWatch | eu-west-1eu-west-1.console.aws.amazon.com/cloudwatch/home?region=eu-west-1alogsV2:logs-insightsS3FqueryDetailS3D-(end-0-start--172800-timeType-'RELATIVE-1z~'UTC-unit-/seconds-editor Strin..Finish update :aws[Option+S) ©urope (irelneRU.View_Only_©765720199711MOUGWALCELogs Insights > _generalQuery definition infoQueriesja unified observabllity platform for a smoother experience, now in preview mode. Click here to try it out!Custom (2d)UTC timezone2 Start tailingSaved queriesQ Filter by query nomeLearn more tQuery scopeLog group nameTUUYCILY SEICLAUSelect up to 50 log groupsBrowse: Log GroupsFacetsLookup tablesCreate query765720193711Show more chosen log groups (-29)fields @tinestanp, @nessape, @logstrean, elog| filter gnessage Like""portal_id": 3364459'Linit 10000Z Query generatorQ Fields• Saved and sample queries© Query commandsRun queryUooeycooucPetcning resuatsLogs (-)Patterns (-)VisualizationLogs (-)|• Summarize results)• Investigate YIShare resultsExport resultsAdd to dashboardFetching data88898'@ GoudShell• Calendar (1)• CleandDB (20)• CRM (12)• Dialers (19)|• Emo (Activity Logs) (1)• Maits (1) |• Notetaker (31) |• Nudge (1)• Ogi (2)• Pipedrive (4)• Processing (4)• Prophet (1)• Recall (1) |• Summary (15)• Tomov (4)_general - CWLI • +|_Social Account Status logs - CWU +|OB Errors - CWL +|Ouplicated track event - CWL +O 2026, Amazon Web Services, Inc. or its affliates.Cooldie preferences2024-11.6.56.pngVNVasil Vasilevllian KyuchukovMihail MihaylovNikolay NikolovLukas Kovalik11:13 AM | Daily - ProcessingSộ3...
|
NULL
|
|
43614
|
927
|
12
|
2026-04-17T08:13:41.433243+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776413621433_m2.jpg...
|
PhpStorm
|
faVsco.js – RunOpportunityAiAnalysis.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.03046875,"top":0.017361112,"width":0.0453125,"height":0.022222223},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"bounds":{"left":0.07578125,"top":0.017361112,"width":0.14960937,"height":0.022222223},"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.78515625,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
305365489813454361
|
-4018125326210054687
|
app_switch
|
hybrid
|
NULL
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
PhpStormFileEditFV faVsco.js vViewNavigateCodeLaravelRefactor#11894 on JY-18909-automated-reports-ask-iminnyToolsWindowHelpProject vDevPostmanCommand.php© DiarizeViaAiParticipantidentif© EncryptTokensCommand.php© EngagementStatsRegenerate© FeatureFlagsHelper.phprixcrosslenantissues.pnpc rusnro esrermissonscachec cenerarelnternalwebnookl ol® GroupSetDefaultLanguageCo© HelperTruncateCoachingTabl© HubspotJournalPollingComm© HubspotWebhookServiceCon© ImportRecording.php© ImportUsersFromCsvFile.php© IterateUsersCommand.php© JiminnyCacheClearCommanc© JiminnyDebugCommand.phpC J minnvser-ncvore. okenmc minny okenntoconmane.© MakeSlackLiveCoachingChat© ManageScimForTeam.php© MarkBranchForEnvironmentP© MuteOrganizerChannel.php© PhpApm.php© PropagateCoachingFeedbach© PurgeConferences.php© PurgeSoftDeletedOpportunitic) Purcesvncbatchescommanoc reca cu arevearskscommallc removevelerevakescommc kemovecxolreanuacescomn© RemoveUnusedParticipantSp© ResetElasticSearch.php© RestoreActivityCrmProviderkc© RestoreActivityTypeComman© SeedActivities.php© SyncActivity.php© Tracklmported.php© UpdateActivitiesAverageScorg whichworkerlsWorkingOnWn> [ Schedulingc kerner.ono› ContractsD DomainODTOD EmailsEnumsD Eventsv MActivitiesD ActivityProviderv M AiAutomationC ACMIvrosceCTAcceo.onC) AiAutomationAnalvsisRea‹C) ConferenceActivityimporti© ManualTriggerForAnalysis© RunActivityAiAnalysis.php© RunOpportunityAiAnalysis© TemplateFieldsUpdated.pl> D Audio© AutomatedReportsService.php© ReportController.php• Filesystem.phpC Team.php© SendReportMailJob.phpphp api.phpAutomatedReportsSendCommand.phprackrrovlderlnstalleacventongC UserPilotActivityListener.php© RequestGenerateAskJiminnyReportJob.php© SyncOpportunity.php(C) OpportunityUpdated.php© RequestGenerateReportJob.php© Opportunity.phpc Eventservicerrovider.onp© OpportunityPendingAiAnalysisAfterStageChanged.php€ HasAttributes.php© Service.php© RunOpportunityAiAnalysis.php(©) AutomatedReportResult.php© AutomatedReport.php› use ...class RunQpportunityAiAnalysis* @param int $opportunityIdx Oodrallno neriemo corertelaslos* oparam int surloderda* Oparam string $triggerType* Oparam string $crm0bjectType* Oparam int[] $crm0bjectIdspublic function __construct(private readonly int $opportunityId,private readonly array $crmTemplateFieldsIds,private readonly int scriggeridprivate readonly string $triggerType = CrmTempLateRun: : TRIGGER_OPPORprivate readonly string $crm0bjectType = Field::0BJECT_OPPORTUNITY,private readonly array $crmobjectIds = [1,) 4..3public function getOpportunityId(): intreturn $this->opportunityId;* Oreturn int[]*/public function getCrmTemplateFieldsIds(): arrayf…..;public function getTriggerId(): intf...public function getTriggerType(): stringf...}public function getCrm0bjectType(): ?stringf...}Helper Code will help IDE to understand your Laravel app code. // Generate // Don't Show Anymore (today 8:59)= custom.log= laravel.log< Hs local liminnyalocalnostiV connect.vueA console [PROD]V Onboard.vueA console [STAGING]15521556100/1o58155915611562156315641569157015711572157315741575157615/215801001158215831584158515861OY1159215931594159515961597159816004 SF [jiminny@localhost]L console [EU] XC* scratch_1.json(iii crm configurations [EUlIx. AutovHaycroundvselect * from contacts cwhere c.crm_configuration_id = 370 order by c.updated_at desc;Ma lminny v026 49 A22 X3 X 103 ^SELECT * FROM participants where activity_ id = 38833541;SELECT * FROM participants where activity_id = 39216301;SELECI * FRUM aculVity_sUmmary_Logs where activity_1d = 572165011SELECT * FROM activities WHERE uvid_to_bin('c7d99fbe-1fb1-41f2-8f4d-52e2bf70e1e9') = vuid; # 38833541, tiSELECT * FROM activities WHERE uvid_to_bin('2e6ff4d3-9faa-447a-a8c1-9acde4d885ae') = vuid;#072105011Cselect * fromcrm_profiles where crm_configuration_id = 319 and crm_provider_id = 525785080;select * fromopportunities where crm_configuration_id = 319 and crm_provider_id = 410150124747;select * from accounts where crm_configuration_id = 319 and crm_provider_id = 47150650569;select * from contacts where crm_configuration_id = 319 and crm_provider_id IN ('665587441856',# owner 13236 525785080# contact 116779180 665587441856 - activity - Alex Howes [EMAIL] created 2026-01-26# contact 219247563 742723347700 - [EMAIL] 2026-03-24# company 4176155 47150650564# deal 7100953 410150124747ISELELCONCAT(u.id, CASE WHEN U.id = t.owner_id THEN ' (owner)' ELSE "' END) AS user_id,Urela tasa.*,t.owner_id FROM social_accounts saJOIN users u on u.id = sa.sociable_idJOIN teams t 1..n<->1: on t.id = u.team_idWHERE u.team_id = 400 and sa.provider = 'hukspot':select * from features;select * from team_features where feature_id = 40;Iselect * from teams where id = 556: # owner: 18101. crm: 477Iselect * from crm confiqurations where id = 477:SELECT * FROM users WHERE id = 18101;SELECTCONCAT(u.id, LASE WHEN U.id = t.owner_id THEN ' (owner)' ELSE "' END) AS User_id,u.email,sa.*,t.owner_id FROM social_accounts saJOIN users u on u.id = sa.sociable_idJUreanstns on td s u.rean lolWHERE u.team_id = 556 and sa.provider = 'integration-app';select * from opportunitieswhere id = 7594349;select * from stages where team_id = 459;select * from teams where id = 459;SELECTCONCAT(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 saJOIN users u on u.id = sa.sociable_idJUIN teams t n<→>l on t.1d = U.ceam_1dWHERE U.Ceam_1d = 45% and sa.provider = 'nupspor :, 0half Backend Chapter • 17 m leftL AutomatedReportsCommandTestv100% C•8 • Fri 17 Apr 11:13:41CascadeAutomated Report RetrInvestigating HubSpotComponent|AiAutomation\Listeners|PendingAnalysis|OpportunityPendingAiAnalysisAfterStageChanged:1, is executed. Can it update the opportunitywsta rast context opportunityrendingAiAnalysisatlerstagecnanged listener class In 4.20560 Fast Context FillCrmFieldValuesService execute - does it update the opportunity mod...lllLiorsHere's the full execution chain:What OpportunityPendingAiAnalysisAfterStageChanged doesIt does NOT directly update the opportunity. It's a trigger/orchestration step. Here's the full chain:=xecutonowUpportunityscaceupuared event Tirea- UpportunacyPenalngA1AnalysisArterstagechanged: : hand le•Loac opportuniry trom Db2. Check team has AI_CRM_FILLING feature enabled3. Find matching CRM templates for the opportunity4. Collect applicable template field IDs5. Dispatch RunOpportunityAiAnalysis event (queued)- Kunupportun1tyA1Analys1sL1scener::handle- FillCrmFieldValuesService: : execute()- U0portunaVermrlelohandler..execure1. Calls Prophet AI to extract field values from deal2. Saves CrmTemplateRun records3. Dispatches AiAutomationAnalysisReadyJob (queued)- FrocessAlAucomaclonana lysiskesulcs→ calls service->syncOpportunityo to refresh local data- caLls UpdatecrmFleldAction:: executet)→ writes AI-generated values back to CRM (HubSpot)Does "Uocare the ooponuniwrYes, indirectly and asynchronously, but not stage_id - it writes Al-generated CRM field values (notstace, speclllcally• ProcessAiAutomationAnalysisResults::qetTarqet0biect() calls service->syncOpportunity —ProcessAlAutomat1onAna Lysiskesults.php: 150 - wnich re-syncs the opportunity trom Hubspot,potentially updating stage_id and other fields as a side effectUpdateCrmFieldAction:: syncRemoteRecord also calls syncOpportunity after writing — UpdateCrmFie dAction.nhn:1.66Key gates — it returns early (no-op) if:1. Opportunity not found2. Team not found3. Team doesn't have AI CRM FILLING feature flag4. No CRM templates match the opportunity5. Templates exist but have no active fieldsRe lation to vour importStages IssueThe syncOpportunity() calls inside ProcessAiAutomationAnalysisResults go through the normalopportunity syne patn, whien calls resouvestade → polentally inportstades. oo this Al auromationchain is a possible additional trigger for the repeated importStages calls you're seeing, separate fromthe scheduled sync jobs.C1l -Ask anything (24L)+ ‹› CodeClaude Sonnet 4.6winasur leams42:6uir-o( 4 spaces...
|
NULL
|
|
43691
|
928
|
11
|
2026-04-17T08:19:16.568152+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776413956568_m1.jpg...
|
Firefox
|
Meet - Daily - Processing — Work
|
1
|
meet.google.com/xpx-omah-rkn
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Meet - Daily - Processing
Close Workers | Datadog
Meet - Daily - Processing
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
Ilian Kyuchukov (Presenting, annotating)
Ilian Kyuchukov (Presenting, annotating)
People
6
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Zoom in
Open in new window
Enter Full Screen
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Vasil Vasilev
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Ilian Kyuchukov
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Mihail Mihaylov
Nikolay Nikolov
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Lukas Kovalik
Others might see more of your background. Click to view your full video.
11:19
AM
Daily - Processing
Daily - Processing
Audio settings
Turn on microphone
Video settings
Turn off camera
Ilian Kyuchukov is presenting
Send a reaction
Turn on captions
Raise hand (ctrl + ⌘ + h)
More options
Leave call...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"bounds":{"left":0.0,"top":0.072222225,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Meet - Daily - Processing","depth":4,"bounds":{"left":0.0,"top":0.13222222,"width":0.033680554,"height":0.045555554},"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.13222222,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.005902778,"top":0.18,"width":0.022222223,"height":0.035555556},"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},"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},"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},"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},"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},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Ilian Kyuchukov (Presenting, annotating)","depth":12,"bounds":{"left":0.07534722,"top":0.101111114,"width":0.18055555,"height":0.022222223},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Ilian Kyuchukov (Presenting, annotating)","depth":13,"bounds":{"left":0.07534722,"top":0.10222222,"width":0.18055555,"height":0.020555556},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"People","depth":15,"bounds":{"left":0.88680553,"top":0.08944444,"width":0.04097222,"height":0.04},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":22,"bounds":{"left":0.9145833,"top":0.101111114,"width":0.0048611113,"height":0.017222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Take notes with Gemini","depth":14,"bounds":{"left":0.93333334,"top":0.08944444,"width":0.025,"height":0.04},"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":17,"bounds":{"left":0.9361111,"top":0.101111114,"width":0.06388891,"height":0.017222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini","depth":22,"bounds":{"left":0.96666664,"top":0.101111114,"width":0.028125,"height":0.017222222},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Gemini","depth":21,"bounds":{"left":0.96458334,"top":0.090555556,"width":0.023611112,"height":0.037777778},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"bounds":{"left":0.5798611,"top":0.62833333,"width":0.14652778,"height":0.08888889},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"bounds":{"left":0.7239583,"top":0.6427778,"width":0.08090278,"height":0.018888889},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"bounds":{"left":0.7017361,"top":0.6388889,"width":0.11076389,"height":0.05666667},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Zoom in","depth":13,"bounds":{"left":0.6315972,"top":0.83111113,"width":0.027777778,"height":0.044444446},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open in new window","depth":13,"bounds":{"left":0.6649306,"top":0.83111113,"width":0.027777778,"height":0.044444446},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Enter Full Screen","depth":13,"bounds":{"left":0.6982639,"top":0.83111113,"width":0.027777778,"height":0.044444446},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"bounds":{"left":0.7861111,"top":0.27611113,"width":0.14652778,"height":0.07722222},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"bounds":{"left":0.9302083,"top":0.2911111,"width":0.069791675,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"bounds":{"left":0.9079861,"top":0.28666666,"width":0.092013896,"height":0.045},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":17,"bounds":{"left":0.75451386,"top":0.36277777,"width":0.061805554,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"bounds":{"left":0.91180557,"top":0.27611113,"width":0.08819443,"height":0.07722222},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"bounds":{"left":1.0,"top":0.2911111,"width":-0.05590272,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"bounds":{"left":1.0,"top":0.28666666,"width":-0.03368056,"height":0.045},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Ilian Kyuchukov","depth":17,"bounds":{"left":0.8802083,"top":0.36277777,"width":0.07986111,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"bounds":{"left":0.7861111,"top":0.5338889,"width":0.14652778,"height":0.07722222},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"bounds":{"left":0.9302083,"top":0.54888886,"width":0.069791675,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"bounds":{"left":0.9079861,"top":0.54444444,"width":0.092013896,"height":0.045},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mihail Mihaylov","depth":17,"bounds":{"left":0.75451386,"top":0.6205556,"width":0.07847222,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Nikolov","depth":17,"bounds":{"left":0.8802083,"top":0.6205556,"width":0.07847222,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"bounds":{"left":0.73888886,"top":0.7916667,"width":0.14652778,"height":0.07722222},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"bounds":{"left":0.665625,"top":0.8066667,"width":0.07569444,"height":0.017777778},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"bounds":{"left":0.64618057,"top":0.80222225,"width":0.11736111,"height":0.045},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Lukas Kovalik","depth":17,"bounds":{"left":0.75381947,"top":0.87833333,"width":0.06875,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Others might see more of your background. Click to view your full video.","depth":14,"bounds":{"left":0.96631944,"top":0.875,"width":0.018055556,"height":0.028888889},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"11:19","depth":12,"bounds":{"left":0.050347224,"top":0.9444444,"width":0.024305556,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AM","depth":12,"bounds":{"left":0.078125,"top":0.9444444,"width":0.017708333,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Daily - Processing","depth":12,"bounds":{"left":0.11319444,"top":0.9111111,"width":0.093055554,"height":0.08888888},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Daily - Processing","depth":15,"bounds":{"left":0.11319444,"top":0.9444444,"width":0.093055554,"height":0.022777777},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Audio settings","depth":13,"bounds":{"left":0.32118055,"top":0.9288889,"width":0.06111111,"height":0.053333335},"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":13,"bounds":{"left":0.34895834,"top":0.9288889,"width":0.033333335,"height":0.053333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false},{"role":"AXButton","text":"Video settings","depth":13,"bounds":{"left":0.38784721,"top":0.9288889,"width":0.06111111,"height":0.053333335},"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":13,"bounds":{"left":0.415625,"top":0.9288889,"width":0.033333335,"height":0.053333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Ilian Kyuchukov is presenting","depth":12,"bounds":{"left":0.45451388,"top":0.9288889,"width":0.03888889,"height":0.053333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Send a reaction","depth":12,"bounds":{"left":0.49895832,"top":0.9288889,"width":0.03888889,"height":0.053333335},"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":13,"bounds":{"left":0.5434028,"top":0.9288889,"width":0.03888889,"height":0.053333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Raise hand (ctrl + ⌘ + h)","depth":12,"bounds":{"left":0.58784723,"top":0.9288889,"width":0.03888889,"height":0.053333335},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"More options","depth":12,"bounds":{"left":0.6322917,"top":0.9288889,"width":0.025,"height":0.053333335},"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":12,"bounds":{"left":0.6628472,"top":0.9288889,"width":0.05,"height":0.053333335},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
2954367257208007506
|
-6461566788809589956
|
app_switch
|
accessibility
|
NULL
|
Workers | Datadog
Meet - Daily - Processing
Close Workers | Datadog
Meet - Daily - Processing
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
Ilian Kyuchukov (Presenting, annotating)
Ilian Kyuchukov (Presenting, annotating)
People
6
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Zoom in
Open in new window
Enter Full Screen
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Vasil Vasilev
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Ilian Kyuchukov
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Mihail Mihaylov
Nikolay Nikolov
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Lukas Kovalik
Others might see more of your background. Click to view your full video.
11:19
AM
Daily - Processing
Daily - Processing
Audio settings
Turn on microphone
Video settings
Turn off camera
Ilian Kyuchukov is presenting
Send a reaction
Turn on captions
Raise hand (ctrl + ⌘ + h)
More options
Leave call...
|
NULL
|
|
43692
|
930
|
4
|
2026-04-17T08:19:16.568121+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776413956568_m2.jpg...
|
Firefox
|
Meet - Daily - Processing — Work
|
1
|
meet.google.com/xpx-omah-rkn
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Meet - Daily - Processing
Close Workers | Datadog
Meet - Daily - Processing
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
Ilian Kyuchukov (Presenting, annotating)
Ilian Kyuchukov (Presenting, annotating)
People
6
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Zoom in
Open in new window
Enter Full Screen
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Vasil Vasilev
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Ilian Kyuchukov
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Mihail Mihaylov
Nikolay Nikolov
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Lukas Kovalik
Others might see more of your background. Click to view your full video.
11:19
AM
Daily - Processing
Daily - Processing
Audio settings
Turn on microphone
Video settings
Turn off camera
Ilian Kyuchukov is presenting
Send a reaction
Turn on captions
Raise hand (ctrl + ⌘ + h)
More options
Leave call
Meeting details
Chat with everyone
Meeting tools
Nikolay Nikolov says in chat: stage_id|count|MIN(created_at) |MAX(created_at) | --------+-----+-------------------+-------------------+ 5036| 1|2026-03-26 16:48:19|2026-03-26 16:48:19| 6293| 1|2026-04-16 19:16:54|2026-04-16 19:16:54| 7344| 1161|2026-03-27 10:47:18|2026-04-17 07:50:43| 11607| 1|2026-04-09 14:15:59|2026-04-09 14:15:59| 12098| 1|2026-03-16 16:53:14|2026-03-16 16:53:14| 14535| 1|2026-03-18 12:51:43|2026-03-18 12:51:43| 15223| 1|2026-03-18 14:52:15|2026-03-18 14:52:15| 16309| 2|2026-03-24 15:20:18|2026-03-30 14:47:38| 16352| 4738|2026-03-17 09:21:10|2026-04-17 07:50:59| 16378| 240|2026-04-16 12:42:37|2026-04-17 05:19:20| 18281| 1911|2026-04-17 04:30:48|2026-04-17 07:50:59| 20612| 2425|2026-04-17 04:31:13|2026-04-17 07:50:56|...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.018945312,"height":-0.045138836},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Meet - Daily - Processing","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.018945312,"height":-0.08263886},"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.23398438,"top":1.0,"width":0.005859375,"height":-0.08263886},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Ilian Kyuchukov (Presenting, annotating)","depth":12,"bounds":{"left":0.27558595,"top":1.0,"width":0.1015625,"height":-0.063194394},"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Ilian Kyuchukov (Presenting, annotating)","depth":13,"bounds":{"left":0.27558595,"top":1.0,"width":0.1015625,"height":-0.06388891},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"People","depth":15,"bounds":{"left":0.7320312,"top":1.0,"width":0.023046875,"height":-0.05590272},"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":22,"bounds":{"left":0.7476562,"top":1.0,"width":0.002734375,"height":-0.063194394},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Take notes with Gemini","depth":14,"bounds":{"left":0.75820315,"top":1.0,"width":0.0140625,"height":-0.05590272},"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":17,"bounds":{"left":0.7597656,"top":1.0,"width":0.051171876,"height":-0.063194394},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini","depth":22,"bounds":{"left":0.7769531,"top":1.0,"width":0.015820313,"height":-0.063194394},"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Gemini","depth":21,"bounds":{"left":0.7757813,"top":1.0,"width":0.01328125,"height":-0.056597233},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Zoom in","depth":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open in new window","depth":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Enter Full Screen","depth":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Ilian Kyuchukov","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mihail Mihaylov","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Nikolov","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Pop out this video More screens are more fun. Play this video while you do other things.","depth":15,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pop out this video","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"More screens are more fun. Play this video while you do other things.","depth":16,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Lukas Kovalik","depth":17,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Others might see more of your background. Click to view your full video.","depth":14,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"11:19","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AM","depth":12,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Daily - Processing","depth":12,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Daily - Processing","depth":15,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Audio settings","depth":13,"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":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false},{"role":"AXButton","text":"Video settings","depth":13,"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":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Ilian Kyuchukov is presenting","depth":12,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Send a reaction","depth":12,"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":13,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Raise hand (ctrl + ⌘ + h)","depth":12,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"More options","depth":12,"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":12,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Meeting details","depth":12,"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":12,"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":12,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Nikolay Nikolov says in chat: stage_id|count|MIN(created_at) |MAX(created_at) | --------+-----+-------------------+-------------------+ 5036| 1|2026-03-26 16:48:19|2026-03-26 16:48:19| 6293| 1|2026-04-16 19:16:54|2026-04-16 19:16:54| 7344| 1161|2026-03-27 10:47:18|2026-04-17 07:50:43| 11607| 1|2026-04-09 14:15:59|2026-04-09 14:15:59| 12098| 1|2026-03-16 16:53:14|2026-03-16 16:53:14| 14535| 1|2026-03-18 12:51:43|2026-03-18 12:51:43| 15223| 1|2026-03-18 14:52:15|2026-03-18 14:52:15| 16309| 2|2026-03-24 15:20:18|2026-03-30 14:47:38| 16352| 4738|2026-03-17 09:21:10|2026-04-17 07:50:59| 16378| 240|2026-04-16 12:42:37|2026-04-17 05:19:20| 18281| 1911|2026-04-17 04:30:48|2026-04-17 07:50:59| 20612| 2425|2026-04-17 04:31:13|2026-04-17 07:50:56|","depth":8,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-4390783709707525835
|
-6426171310220248260
|
app_switch
|
accessibility
|
NULL
|
Workers | Datadog
Meet - Daily - Processing
Close Workers | Datadog
Meet - Daily - Processing
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
Ilian Kyuchukov (Presenting, annotating)
Ilian Kyuchukov (Presenting, annotating)
People
6
Take notes with Gemini
Take notes with Gemini
Gemini
Gemini
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Zoom in
Open in new window
Enter Full Screen
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Vasil Vasilev
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Ilian Kyuchukov
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Mihail Mihaylov
Nikolay Nikolov
Pop out this video More screens are more fun. Play this video while you do other things.
Pop out this video
More screens are more fun. Play this video while you do other things.
Lukas Kovalik
Others might see more of your background. Click to view your full video.
11:19
AM
Daily - Processing
Daily - Processing
Audio settings
Turn on microphone
Video settings
Turn off camera
Ilian Kyuchukov is presenting
Send a reaction
Turn on captions
Raise hand (ctrl + ⌘ + h)
More options
Leave call
Meeting details
Chat with everyone
Meeting tools
Nikolay Nikolov says in chat: stage_id|count|MIN(created_at) |MAX(created_at) | --------+-----+-------------------+-------------------+ 5036| 1|2026-03-26 16:48:19|2026-03-26 16:48:19| 6293| 1|2026-04-16 19:16:54|2026-04-16 19:16:54| 7344| 1161|2026-03-27 10:47:18|2026-04-17 07:50:43| 11607| 1|2026-04-09 14:15:59|2026-04-09 14:15:59| 12098| 1|2026-03-16 16:53:14|2026-03-16 16:53:14| 14535| 1|2026-03-18 12:51:43|2026-03-18 12:51:43| 15223| 1|2026-03-18 14:52:15|2026-03-18 14:52:15| 16309| 2|2026-03-24 15:20:18|2026-03-30 14:47:38| 16352| 4738|2026-03-17 09:21:10|2026-04-17 07:50:59| 16378| 240|2026-04-16 12:42:37|2026-04-17 05:19:20| 18281| 1911|2026-04-17 04:30:48|2026-04-17 07:50:59| 20612| 2425|2026-04-17 04:31:13|2026-04-17 07:50:56|...
|
NULL
|
|
43693
|
928
|
12
|
2026-04-17T08:19:17.854524+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776413957854_m1.jpg...
|
PhpStorm
|
NULL
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
FirefoxFileEditViewHistoryBookmarksProfilesToolsWi FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelpmeet.google.com/xpx-omah-rkn> 0lal)• Backend Chapter • 11 m left100% <478 • Fri 17 Apr 11:19:17=6+llian Kyuchukov (Presenting, annotating)File= 40• • Q 8• Fri17Apr 11:19.CowowhtowesteCloudWatch | eu-west-1eu-west-1.console.aws.amazon.com/cloudwatch/home?region=eu-west-1#logsV2:logs-insightsS3FqueryldS3D1dcf83a9-8b6c-4a17-9483-14cc025cbc96S26queryDetailS3D-(end~Q-start~-8.• WorkFinish update :Q Seard([Option+S) ©0/12it ID: 7657-2019-9711EU.View_Only@765720199711CloudWatchLogs Insights _generalQuery definition infoa unified observability platform for a smoother experience, now in preview mode. Click here to try it out! |CuotmeCompare (Off)|WetimetoneL Start tailingQuery scopeLog group nameProperty selectorSelect up to 50 log groupsBrowse: Log Groups| Facets Lookup tablesE Show more chosen log groups (+29) |fields etinestanp, (nessage, @logStrean, elog1 filter @nessage like '[HubSpot Webhook]'I filter @nessage Like "59029758241'1 Linit 10000Logs Insights QL½ Query generatorQ FieldsSaved and sample queries© Query commandsRun queryFetching resultsCancelwweis sercu uueryEstimated remalning time: 11 secondsLogs (-)|Patterns (-)|VisualizationLogs (-)Summarize results)• Investigate• Share resultsExport resultsAdd to dashboardShowing 0 of 0 records matched - Scanning O13,143,147 records (3.1 GB) scanned in 7.28 @ 1,823,917 records/s (435.8 MB/s)E CloudShellPtivacyTermsNetmenthot2024-116.56.png1711:19 AM | Daily - ProcessingJINVasil Vasilevlian KyuchukovMihail MihaylovNikolay NikolovLukas Kovalik...
|
NULL
|
-1994515211918549574
|
NULL
|
app_switch
|
ocr
|
NULL
|
FirefoxFileEditViewHistoryBookmarksProfilesToolsWi FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelpmeet.google.com/xpx-omah-rkn> 0lal)• Backend Chapter • 11 m left100% <478 • Fri 17 Apr 11:19:17=6+llian Kyuchukov (Presenting, annotating)File= 40• • Q 8• Fri17Apr 11:19.CowowhtowesteCloudWatch | eu-west-1eu-west-1.console.aws.amazon.com/cloudwatch/home?region=eu-west-1#logsV2:logs-insightsS3FqueryldS3D1dcf83a9-8b6c-4a17-9483-14cc025cbc96S26queryDetailS3D-(end~Q-start~-8.• WorkFinish update :Q Seard([Option+S) ©0/12it ID: 7657-2019-9711EU.View_Only@765720199711CloudWatchLogs Insights _generalQuery definition infoa unified observability platform for a smoother experience, now in preview mode. Click here to try it out! |CuotmeCompare (Off)|WetimetoneL Start tailingQuery scopeLog group nameProperty selectorSelect up to 50 log groupsBrowse: Log Groups| Facets Lookup tablesE Show more chosen log groups (+29) |fields etinestanp, (nessage, @logStrean, elog1 filter @nessage like '[HubSpot Webhook]'I filter @nessage Like "59029758241'1 Linit 10000Logs Insights QL½ Query generatorQ FieldsSaved and sample queries© Query commandsRun queryFetching resultsCancelwweis sercu uueryEstimated remalning time: 11 secondsLogs (-)|Patterns (-)|VisualizationLogs (-)Summarize results)• Investigate• Share resultsExport resultsAdd to dashboardShowing 0 of 0 records matched - Scanning O13,143,147 records (3.1 GB) scanned in 7.28 @ 1,823,917 records/s (435.8 MB/s)E CloudShellPtivacyTermsNetmenthot2024-116.56.png1711:19 AM | Daily - ProcessingJINVasil Vasilevlian KyuchukovMihail MihaylovNikolay NikolovLukas Kovalik...
|
NULL
|
|
43694
|
930
|
5
|
2026-04-17T08:19:17.854510+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776413957854_m2.jpg...
|
PhpStorm
|
NULL
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorToo PhpStormFileEditViewNavigateCodeLaravelRefactorToolsWindowHelpProject v© OpportunityPendingAiA© HandleCrmFieldValidation© ProcessAiAutomationAnal;© RunActivityAiAnalysisListe© RunOpportunityAiAnalysis> D ProphetCommunication> [ ProphetServiceHandlers_ Repositories•servicesralTSM UiComponents(c) ContextObjectResolver.php© FieldDefinitionBuilder.php© FieldDefinitionToFieldReques© FillCrmFieldResponseParser.f© FillCrmFieldValuesService.ph© SaveCrmTemplateRunsServic© SyncCrmFieldService.php© TemplateFieldDefinitionServir© TemplateObjectType.phplestermalPromptservice.png> D AiCallScoringv D AskAnything> DDtosD Events© AskAnythingPromptService.p© HistoryService.php> D AskJiminnyAi> DAWS> D BillingManagement> D Cache> D CoachingFeedback> D Country> M CustomerApi› _ Database> D Datadog> D DateTime› D Deallnsights© DealRisks> D ElasticSearch> D Eloquent> D Encoding> D Encryption> DES> D Faker> D FeatureFlags› D FFMpeg> D FileSystem› D Gecko> D Gong› D GuzzleHttp> D KeyPoints> D Kiosk> D LanguageDetection> D LiveFeed> DJ Locks> D Math> D MediaPipeline> D MeetingBot© AutomatedReportsService.php© SendReportJob.php© SendReportMailJob.php© ReportController.phplokenbullaer.ono• TeamSetupController.phppnp apl.onoFilesystem.php© AskJiminnyReportsController.php© CreateHeldActivityEvent.php© TrackProviderInstalledEvent.php© UserPilotActivityListener.php© ActivityLogged.php© AutomatedReportsCallbackService.php© RequestGenerateAskJiminnyReportJob.php© RequestGenerateReportJob.phpC SyncOpportunity.phpT OpportunitySyncTrait.phpC Opportunity.phpC OpportunityUpdated.php© OpportunityStageUpdated.phpe FventService?rovider.onn© OpportunityPendingAiAnalysisAfterStageChanged.php© RunOpportunityAiAnalysis.php© ProcessAiAutomationAnalysisResults.php x€ HasAttributes.php© Service.phpC AutomatedReportResult.php(C) AutomatedReport.phpE custom.log= laravel.logA SF [jiminny@localhost]C scratch_1jsonC Team.phpconnect.vueV Onboard.vue< Hs local liminnyalocalnostÁ console [EU] xfi crm_configurations [EU]A console [PROD]A console [STAGING]X:Auto vPlaygroundMa liminnv v1547select x tol contacrs c1550where c.cmm.configuration_id = 370 order Dy .updatea.27 23 6;,026 49 A22 X3 X103 A V15511552SELECT * FROM participants where activity_id = 38833541;1553SELECT * FROM participants where activity_id = 39216301;1554SELECT * FROM activity_summary_logs where activity_id = 39216301;class ProcessalAuronaronanauyst skesuuusA1 X1 A1555SELECT * FROM activities WHERE uvid_to_bin( 'c7d99fbe-1fb1-41f2-8f4d-52eprivate tunccion processoingleremp latekoncermlemplatekon eermlemplatekon). vola1556SELECT * FROM activities WHERE uvid_to_bin('2e6ff4d3-9faa-447a-a8c1-9ac1557select * from crm_profiles where crm_configuration_id = 319 and crm_pro1558if ($updateTargetDto === null) {select * fromopportunities where crm_configuration_id = 319 and crm_pr1559select x ToIaccounts where crm_configuration_id = 319 and crm_provideSthis->logger→›info(' [ProcessAIAutomationAnalysis] UpdateTargetDTO is not currently updateable' , 1560select x ToIcontacts where crm_configuration_id = 319 and crm_provide'crm_template_run_id' => $crmTemplateRun->getId(),1501# owner 13236 525785080'crm_object_type' => $crmTemplateRun->getCrm0bjectType(),1562# contact 1'crm_object_id' => $crmTemplateRun->getCrm0bjectId(.16779180 665587441856 - activity - ALex Heves alex@support1563# contact 219247563 742723347700 - [EMAIL] 2026-03-24I);1564# company 4176133 47150650569reLurln1565# deal 7100953 4101501247471566SELECT1567CONCAT(u.id, CASE WHEN U.id = t.owner_id THEN ' (owner)' ELSE '' EN1568u.email,/** Before doing any changes make sure the target object and all its fields are up to date */1569sa.*,$targetobject = $this->getTarget0bject($updateTargetDto);1570t.owner id FROM social accounts saif (! $targetObject) {$this->Logger->info(' [ProcessAiAutomationAnalysis] Crm target object does not exist', [1571JOIN users u on u.id = sa.sociable idJOIN teams t 1..n<->1: on t.id = u.team id'crm_template_run_id' => $crmTemplateRun->getId(),1573'crm_object_type' => $crmTemplateRun->getCrm0bjectType(),1574WHERE U. team_id = 400 and sa.provider = 'hubspot';'crm_object_id' => $crmTemplateRun->getCrm0bjectId(),1575I);1576select * from features;select * from team_features where feature_id = 40;reLurnr1577157815791580/** Select what will be the next operation to execute, add the selection directly to updateTargetDto1581$this->determineUpdate0perationAction->execute(1582crmobject: $targetobject,1585crmTemplateRun: $crmTemplateRun,1584updacelargetuco: supaatelargecdto,realllr nunseoreallln15851586);1587xx nuu, uver'waler1588select * from teams where id = 556; # owner: 18101, crm: 477select * from crm_configurations where id = 477;SELECT * FROM users WHERE id = 18101;SELELTCONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE "' ENU.ellan tesa.x,t.owner_id FROM social_accounts saJOIN users u on u.id = sa.sociable_idJOIN teams t1..n<->1: on t.id = u.team_idWHERE u.team_id = 556 and sa.provider = 'integration-app':Append to CRM record, or skip. Execute the decision taken */1589$this->executeWriteDecision($crmTemplateRun, $updateTargetDto);1590159115921593private function executeWriteDecision(1594CrmTemplateRun $crmTemplateRun,UpdateCrmTargetDto $updateCrmTargetDto,11073): void {...}1107015971598144148private function getTarget0bject(UpdateCrmTargetDto $updateCrmTargetDto): CrmFillingTarget0bjectContract|select * from opportunities where id = 7594349;select * from stages where team_id = 459;select * from teams where id = 459:SELELTCONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE "'U.emall,Sa.x,t.owner_id FROM social_accounts saJOIN users u on u.id = sa.sociable_idJOIN teams t 1..n<->1: on t.id = u.team_idWHERE u.team_id = 459 and sa.provider = 'hukspat':return match ($updateCrmTargetDto->remote0bjectType) {1602Field: :0BJECT_LEAD => $this->service->syncLead($updateCrmTargetDto->remote0bjectProviderId).Field: :0BJECT_ACCOUNT => $this->service->syncAccount($updateCrmTargetDto->remote0bjectProviderId)ite // Don't Show Anymore (today 8:59)select * from opportunities where id = 7594349;Backend Chapter • 11 m left100% C•8 • Fri 17 Apr 11:19:17CascadeAutomated Report RetrInvestigating HubSpotComponent|AiAutomation\Listeners|PendingAnalysis|OpportunityPendingAiAnalysisAfterStageChanged:1, is executed. Can it update the opportunitysta rast context opportunityrendingAiAnalysisatlerstagecnanged listener class In 4.20560 Fast Context FillCrmFieldValuesService execute - does it update the opportunity mod...lllLiorsHere's the full execution chain:What OpportunityPendingAiAnalysisAfterStageChanged doesIt does NOT directly update the opportunity. It's a trigger/orchestration step. Here's the full chain:=xecutonowUpportunityscadeupuatea event Tirea- Upportun1cyPenalngA1AnalysisArterstaqechanged::handle•Loac opportuniry trom Db2. Check team has AI_CRM_FILLING feature enabled3. Find matching CRM templates for the opportunity4. Collect applicable template field IDs5. Dispatch RunOpportunityAiAnalysis event (queued)- Kunupportun1tyA1Analys1sL1scener::handle- FillCrmFieldValuesService: : execute()- U0portunaVermrle ohand ler..execuie1. Calls Prophet AI to extract field values from deal2. Saves CrmTemplateRun records3. Dispatches AiAutomationAnalysisReadyJob (queued)- FrocessAlAucomaclonana lysiskesulcs- cauls service→>syncupportunacy to rerresh local data- caLls UpdatecrmFleldAction:: executet)→ writes AI-generated values back to CRM (HubSpot)Does "Uocare the ooponuniwrYes, indirectly and asynchronously, but not stage_id - it writes Al-generated CRM field values (notstace, speclllcallyProcessAiAutomationAnalysisResults::getTarqet0biect() calls service->syncOpportunity —ProcessAlAutomat1onAna Lysiskesults.php: 150 - wnich re-syncs the opportunity trom Hubspot,potentially updating stage_id and other fields as a side effectUpdateCrmFieldAction:: syncRemoteRecord also calls syncOpportunity after writing - UpdateCrmFie dAction.nhn:1.66Key gates — it returns early (no-op) if:1. Opportunity not found2. Team not found3. Team doesn't have AI CRM FILLING feature flag4. No CRM templates match the opportunity5. Templates exist but have no active fieldsRe lation to vour importStages IssueThe syncOpportunity() calls inside ProcessAiAutomationAnalysisResults go through the normalopportunity sync path, which calls resolveStage( → potentially importStages() . So this Al automationchain is a possible additional trigger for the repeated importStages calls you're seeing, separate fromthe scheduled sync jobs.C1l -Ask anything (24L)+ <> CodeClaude Sonnet 4.6( 4 spaces...
|
NULL
|
-6097457467171214024
|
NULL
|
app_switch
|
ocr
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorToo PhpStormFileEditViewNavigateCodeLaravelRefactorToolsWindowHelpProject v© OpportunityPendingAiA© HandleCrmFieldValidation© ProcessAiAutomationAnal;© RunActivityAiAnalysisListe© RunOpportunityAiAnalysis> D ProphetCommunication> [ ProphetServiceHandlers_ Repositories•servicesralTSM UiComponents(c) ContextObjectResolver.php© FieldDefinitionBuilder.php© FieldDefinitionToFieldReques© FillCrmFieldResponseParser.f© FillCrmFieldValuesService.ph© SaveCrmTemplateRunsServic© SyncCrmFieldService.php© TemplateFieldDefinitionServir© TemplateObjectType.phplestermalPromptservice.png> D AiCallScoringv D AskAnything> DDtosD Events© AskAnythingPromptService.p© HistoryService.php> D AskJiminnyAi> DAWS> D BillingManagement> D Cache> D CoachingFeedback> D Country> M CustomerApi› _ Database> D Datadog> D DateTime› D Deallnsights© DealRisks> D ElasticSearch> D Eloquent> D Encoding> D Encryption> DES> D Faker> D FeatureFlags› D FFMpeg> D FileSystem› D Gecko> D Gong› D GuzzleHttp> D KeyPoints> D Kiosk> D LanguageDetection> D LiveFeed> DJ Locks> D Math> D MediaPipeline> D MeetingBot© AutomatedReportsService.php© SendReportJob.php© SendReportMailJob.php© ReportController.phplokenbullaer.ono• TeamSetupController.phppnp apl.onoFilesystem.php© AskJiminnyReportsController.php© CreateHeldActivityEvent.php© TrackProviderInstalledEvent.php© UserPilotActivityListener.php© ActivityLogged.php© AutomatedReportsCallbackService.php© RequestGenerateAskJiminnyReportJob.php© RequestGenerateReportJob.phpC SyncOpportunity.phpT OpportunitySyncTrait.phpC Opportunity.phpC OpportunityUpdated.php© OpportunityStageUpdated.phpe FventService?rovider.onn© OpportunityPendingAiAnalysisAfterStageChanged.php© RunOpportunityAiAnalysis.php© ProcessAiAutomationAnalysisResults.php x€ HasAttributes.php© Service.phpC AutomatedReportResult.php(C) AutomatedReport.phpE custom.log= laravel.logA SF [jiminny@localhost]C scratch_1jsonC Team.phpconnect.vueV Onboard.vue< Hs local liminnyalocalnostÁ console [EU] xfi crm_configurations [EU]A console [PROD]A console [STAGING]X:Auto vPlaygroundMa liminnv v1547select x tol contacrs c1550where c.cmm.configuration_id = 370 order Dy .updatea.27 23 6;,026 49 A22 X3 X103 A V15511552SELECT * FROM participants where activity_id = 38833541;1553SELECT * FROM participants where activity_id = 39216301;1554SELECT * FROM activity_summary_logs where activity_id = 39216301;class ProcessalAuronaronanauyst skesuuusA1 X1 A1555SELECT * FROM activities WHERE uvid_to_bin( 'c7d99fbe-1fb1-41f2-8f4d-52eprivate tunccion processoingleremp latekoncermlemplatekon eermlemplatekon). vola1556SELECT * FROM activities WHERE uvid_to_bin('2e6ff4d3-9faa-447a-a8c1-9ac1557select * from crm_profiles where crm_configuration_id = 319 and crm_pro1558if ($updateTargetDto === null) {select * fromopportunities where crm_configuration_id = 319 and crm_pr1559select x ToIaccounts where crm_configuration_id = 319 and crm_provideSthis->logger→›info(' [ProcessAIAutomationAnalysis] UpdateTargetDTO is not currently updateable' , 1560select x ToIcontacts where crm_configuration_id = 319 and crm_provide'crm_template_run_id' => $crmTemplateRun->getId(),1501# owner 13236 525785080'crm_object_type' => $crmTemplateRun->getCrm0bjectType(),1562# contact 1'crm_object_id' => $crmTemplateRun->getCrm0bjectId(.16779180 665587441856 - activity - ALex Heves alex@support1563# contact 219247563 742723347700 - [EMAIL] 2026-03-24I);1564# company 4176133 47150650569reLurln1565# deal 7100953 4101501247471566SELECT1567CONCAT(u.id, CASE WHEN U.id = t.owner_id THEN ' (owner)' ELSE '' EN1568u.email,/** Before doing any changes make sure the target object and all its fields are up to date */1569sa.*,$targetobject = $this->getTarget0bject($updateTargetDto);1570t.owner id FROM social accounts saif (! $targetObject) {$this->Logger->info(' [ProcessAiAutomationAnalysis] Crm target object does not exist', [1571JOIN users u on u.id = sa.sociable idJOIN teams t 1..n<->1: on t.id = u.team id'crm_template_run_id' => $crmTemplateRun->getId(),1573'crm_object_type' => $crmTemplateRun->getCrm0bjectType(),1574WHERE U. team_id = 400 and sa.provider = 'hubspot';'crm_object_id' => $crmTemplateRun->getCrm0bjectId(),1575I);1576select * from features;select * from team_features where feature_id = 40;reLurnr1577157815791580/** Select what will be the next operation to execute, add the selection directly to updateTargetDto1581$this->determineUpdate0perationAction->execute(1582crmobject: $targetobject,1585crmTemplateRun: $crmTemplateRun,1584updacelargetuco: supaatelargecdto,realllr nunseoreallln15851586);1587xx nuu, uver'waler1588select * from teams where id = 556; # owner: 18101, crm: 477select * from crm_configurations where id = 477;SELECT * FROM users WHERE id = 18101;SELELTCONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE "' ENU.ellan tesa.x,t.owner_id FROM social_accounts saJOIN users u on u.id = sa.sociable_idJOIN teams t1..n<->1: on t.id = u.team_idWHERE u.team_id = 556 and sa.provider = 'integration-app':Append to CRM record, or skip. Execute the decision taken */1589$this->executeWriteDecision($crmTemplateRun, $updateTargetDto);1590159115921593private function executeWriteDecision(1594CrmTemplateRun $crmTemplateRun,UpdateCrmTargetDto $updateCrmTargetDto,11073): void {...}1107015971598144148private function getTarget0bject(UpdateCrmTargetDto $updateCrmTargetDto): CrmFillingTarget0bjectContract|select * from opportunities where id = 7594349;select * from stages where team_id = 459;select * from teams where id = 459:SELELTCONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE "'U.emall,Sa.x,t.owner_id FROM social_accounts saJOIN users u on u.id = sa.sociable_idJOIN teams t 1..n<->1: on t.id = u.team_idWHERE u.team_id = 459 and sa.provider = 'hukspat':return match ($updateCrmTargetDto->remote0bjectType) {1602Field: :0BJECT_LEAD => $this->service->syncLead($updateCrmTargetDto->remote0bjectProviderId).Field: :0BJECT_ACCOUNT => $this->service->syncAccount($updateCrmTargetDto->remote0bjectProviderId)ite // Don't Show Anymore (today 8:59)select * from opportunities where id = 7594349;Backend Chapter • 11 m left100% C•8 • Fri 17 Apr 11:19:17CascadeAutomated Report RetrInvestigating HubSpotComponent|AiAutomation\Listeners|PendingAnalysis|OpportunityPendingAiAnalysisAfterStageChanged:1, is executed. Can it update the opportunitysta rast context opportunityrendingAiAnalysisatlerstagecnanged listener class In 4.20560 Fast Context FillCrmFieldValuesService execute - does it update the opportunity mod...lllLiorsHere's the full execution chain:What OpportunityPendingAiAnalysisAfterStageChanged doesIt does NOT directly update the opportunity. It's a trigger/orchestration step. Here's the full chain:=xecutonowUpportunityscadeupuatea event Tirea- Upportun1cyPenalngA1AnalysisArterstaqechanged::handle•Loac opportuniry trom Db2. Check team has AI_CRM_FILLING feature enabled3. Find matching CRM templates for the opportunity4. Collect applicable template field IDs5. Dispatch RunOpportunityAiAnalysis event (queued)- Kunupportun1tyA1Analys1sL1scener::handle- FillCrmFieldValuesService: : execute()- U0portunaVermrle ohand ler..execuie1. Calls Prophet AI to extract field values from deal2. Saves CrmTemplateRun records3. Dispatches AiAutomationAnalysisReadyJob (queued)- FrocessAlAucomaclonana lysiskesulcs- cauls service→>syncupportunacy to rerresh local data- caLls UpdatecrmFleldAction:: executet)→ writes AI-generated values back to CRM (HubSpot)Does "Uocare the ooponuniwrYes, indirectly and asynchronously, but not stage_id - it writes Al-generated CRM field values (notstace, speclllcallyProcessAiAutomationAnalysisResults::getTarqet0biect() calls service->syncOpportunity —ProcessAlAutomat1onAna Lysiskesults.php: 150 - wnich re-syncs the opportunity trom Hubspot,potentially updating stage_id and other fields as a side effectUpdateCrmFieldAction:: syncRemoteRecord also calls syncOpportunity after writing - UpdateCrmFie dAction.nhn:1.66Key gates — it returns early (no-op) if:1. Opportunity not found2. Team not found3. Team doesn't have AI CRM FILLING feature flag4. No CRM templates match the opportunity5. Templates exist but have no active fieldsRe lation to vour importStages IssueThe syncOpportunity() calls inside ProcessAiAutomationAnalysisResults go through the normalopportunity sync path, which calls resolveStage( → potentially importStages() . So this Al automationchain is a possible additional trigger for the repeated importStages calls you're seeing, separate fromthe scheduled sync jobs.C1l -Ask anything (24L)+ <> CodeClaude Sonnet 4.6( 4 spaces...
|
NULL
|
|
43696
|
928
|
14
|
2026-04-17T08:19:24.448033+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776413964448_m1.jpg...
|
Firefox
|
Meet - Daily - Processing — Work
|
1
|
meet.google.com/xpx-omah-rkn
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Workers | Datadog
Meet - Daily - Processing
Close Workers | Datadog
Meet - Daily - Processing
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Workers | Datadog","depth":4,"bounds":{"left":0.0,"top":0.072222225,"width":0.033680554,"height":0.045555554},"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Meet - Daily - Processing","depth":4,"bounds":{"left":0.0,"top":0.13222222,"width":0.033680554,"height":0.045555554},"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.13222222,"width":0.010416667,"height":0.016666668},"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.005902778,"top":0.18,"width":0.022222223,"height":0.035555556},"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},"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},"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},"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},"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
673836544651055637
|
1008872683201119874
|
app_switch
|
hybrid
|
NULL
|
Workers | Datadog
Meet - Daily - Processing
Close Workers | Datadog
Meet - Daily - Processing
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelpmeet.google.com/xpx-omah-rkn• Backend Chapter • 11 m left100% <478 • Fri 17 Apr 11:19:24+llian Kyuchukov (Presenting, annotating)FileCowownee"WesteCloudWatch | eu-west-1eu-west-1.console.aws.amazon.com/cloudwatch/home?region=eu-west-1#logsV2:logs-insightsS3FqueryldS3D1dcf83a9-8b6c-4a17-9483-14cc025cbc96S26queryDetailS3D-(end~Q-start~-8.Q Seard([Option+S) ©0/12CloudWatchLogs Insights > _generalQuery definition infoa unified observability platform for a smoother experience, now in preview mode. Click here to try it out! |CusotmeCompare (Off)|UTC timezoneL Start tailing)Query scopeLog group nameProperty selectorSelect up to 50 log groupsE Show more chosen log groups (+29) |fields etinestanp, (nessage, @logStrean, elog1 fitter @nessage like '[HubSpot Webhook]'I filter @nessage Like '59829758241'1 Linit 10000Logs Insights QL +½ Query generatorQ FieldsSaved and sample queries© Query commandsRun queryFetching resultsCancelwweus sercuuuetyомаеии готн м аумимLogs (2)|Patterns (-)VisualizationLogs (2)• Summarize results)• Investigate@ Share resultsShowing 2 of 2 records matched - Scanning O31,738,088 records (8.0 GB) scanned in 14.25 @ 2,240,282 records/s (575.0 MB/$)E CoudShell1711:19 AM | Daily - Processing...• • Q 8• Fri17Apr 11:19.• WorkFinish update :nt ID: 7657-2019-9711EU.View_Only@765720199711JVasil Vasilevllian KyuchukovBrowse: Log Groups FacetsLookup tablesMihail MihaylovNikolay NikolovAdd to dashboardPtivacyTermsHide. histescamCookde preferencesSctmenthot2024-116.56.pngLukas Kovalik...
|
NULL
|
|
43731
|
928
|
35
|
2026-04-17T08:20:35.678172+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776414035678_m1.jpg...
|
System Settings
|
Screen & System Audio Recording
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Search
Lukas Kovalik, Apple ID
1
1
Wi‑Fi
Bluetooth Search
Lukas Kovalik, Apple ID
1
1
Wi‑Fi
Bluetooth
Network
VPN
Notifications
Sound
Focus
Screen Time
General
Appearance
Accessibility
Control Centre
Siri & Spotlight
Privacy & Security
Desktop & Dock
Displays
Wallpaper
Screen Saver
Battery
Lock Screen
Touch ID & Password
Users & Groups
Passwords
Internet Accounts
Game Center
Wallet & Apple Pay
Keyboard
Mouse
Trackpad
Printers & Scanners
Screen & System Audio Recording
Allow the applications below to record the content of your screen and audio, even while using other applications.
1Password
BetterTouchTool
Claude
CleanShot X
Dia...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Search","depth":5,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"Lukas Kovalik, Apple ID","depth":8,"automation_id":"com.apple.systempreferences.AppleIDSettings*AppleIDSettings","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"1","depth":8,"automation_id":"com.apple.FollowUpSettings.FollowUpSettingsExtension*FOLLOWUP_SINGLE_ACTION_changePhoneNumber","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"1","depth":8,"automation_id":"com.apple.FollowUpSettings.FollowUpSettingsExtension*FOLLOWUP_SINGLE_ACTION_com.apple.SoftwareUpdateServices.followup:com.apple.Software-Update-Settings.extension","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"Wi‑Fi","depth":8,"automation_id":"com.apple.wifi-settings-extension","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"Bluetooth","depth":8,"automation_id":"com.apple.BluetoothSettings","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"Network","depth":8,"automation_id":"com.apple.Network-Settings.extension","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"VPN","depth":8,"automation_id":"com.apple.NetworkExtensionSettingsUI.NESettingsUIExtension*vpn","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"Notifications","depth":8,"automation_id":"com.apple.Notifications-Settings.extension","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"Sound","depth":8,"automation_id":"com.apple.Sound-Settings.extension","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"Focus","depth":8,"automation_id":"com.apple.Focus-Settings.extension","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"Screen Time","depth":8,"automation_id":"com.apple.Screen-Time-Settings.extension","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"General","depth":8,"automation_id":"com.apple.systempreferences.GeneralSettings","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"Appearance","depth":8,"automation_id":"com.apple.Appearance-Settings.extension","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"Accessibility","depth":8,"automation_id":"com.apple.Accessibility-Settings.extension","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"Control Centre","depth":8,"automation_id":"com.apple.ControlCenter-Settings.extension","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"Siri & Spotlight","depth":8,"automation_id":"com.apple.Siri-Settings.extension","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"Privacy & Security","depth":8,"automation_id":"com.apple.settings.PrivacySecurity.extension","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"Desktop & Dock","depth":8,"automation_id":"com.apple.Desktop-Settings.extension","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"Displays","depth":8,"automation_id":"com.apple.Displays-Settings.extension","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"Wallpaper","depth":8,"automation_id":"com.apple.Wallpaper-Settings.extension","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"Screen Saver","depth":8,"automation_id":"com.apple.ScreenSaver-Settings.extension","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"Battery","depth":8,"automation_id":"com.apple.Battery-Settings.extension*BatteryPreferences","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"Lock Screen","depth":8,"automation_id":"com.apple.Lock-Screen-Settings.extension","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"Touch ID & Password","depth":8,"automation_id":"com.apple.Touch-ID-Settings.extension*TouchIDPasswordPrefs","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"Users & Groups","depth":8,"automation_id":"com.apple.Users-Groups-Settings.extension","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"Passwords","depth":8,"automation_id":"com.apple.Passwords-Settings.extension","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"Internet Accounts","depth":8,"automation_id":"com.apple.Internet-Accounts-Settings.extension","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"Game Center","depth":8,"automation_id":"com.apple.Game-Center-Settings.extension"},{"role":"AXStaticText","text":"Wallet & Apple Pay","depth":8,"automation_id":"com.apple.WalletSettingsExtension","help_text":"","role_description":"text"},{"role":"AXStaticText","text":"Keyboard","depth":8,"automation_id":"com.apple.Keyboard-Settings.extension"},{"role":"AXStaticText","text":"Mouse","depth":8,"automation_id":"com.apple.Mouse-Settings.extension"},{"role":"AXStaticText","text":"Trackpad","depth":8,"automation_id":"com.apple.Trackpad-Settings.extension","role_description":"text"},{"role":"AXStaticText","text":"Printers & Scanners","depth":8,"automation_id":"com.apple.Print-Scan-Settings.extension","help_text":"","role_description":"text"},{"role":"AXHeading","text":"Screen & System Audio Recording","depth":6,"role_description":"heading"},{"role":"AXHeading","text":"Allow the applications below to record the content of your screen and audio, even while using other applications.","depth":6,"role_description":"heading"},{"role":"AXStaticText","text":"1Password","depth":11,"automation_id":"1Password_Title","role_description":"text"},{"role":"AXStaticText","text":"BetterTouchTool","depth":11,"automation_id":"BetterTouchTool_Title","role_description":"text"},{"role":"AXStaticText","text":"Claude","depth":11,"automation_id":"Claude_Title","role_description":"text"},{"role":"AXStaticText","text":"CleanShot X","depth":11,"automation_id":"CleanShot X_Title","role_description":"text"},{"role":"AXStaticText","text":"Dia","depth":11,"automation_id":"Dia_Title","role_description":"text"}]...
|
-2991470212286910548
|
-416433595993227856
|
app_switch
|
accessibility
|
NULL
|
Search
Lukas Kovalik, Apple ID
1
1
Wi‑Fi
Bluetooth Search
Lukas Kovalik, Apple ID
1
1
Wi‑Fi
Bluetooth
Network
VPN
Notifications
Sound
Focus
Screen Time
General
Appearance
Accessibility
Control Centre
Siri & Spotlight
Privacy & Security
Desktop & Dock
Displays
Wallpaper
Screen Saver
Battery
Lock Screen
Touch ID & Password
Users & Groups
Passwords
Internet Accounts
Game Center
Wallet & Apple Pay
Keyboard
Mouse
Trackpad
Printers & Scanners
Screen & System Audio Recording
Allow the applications below to record the content of your screen and audio, even while using other applications.
1Password
BetterTouchTool
Claude
CleanShot X
Dia...
|
NULL
|
|
50861
|
1093
|
37
|
2026-04-17T15:33:25.701769+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776440005701_m1.jpg...
|
Claude
|
Claude
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Skip to content
Skip to content
Click to collapse
Skip to content
Skip to content
Click to collapse
⌘B
Drag to resize
Collapse sidebar
Search
Chat
Cowork
Code
New chat ⌘N
New chat
⌘N
Projects
Customize
Artifacts
Pinned
Bulgarian citizenship application process for EU residents
More options for Bulgarian citizenship application process for EU residents
Dawarich location tracking project
More options for Dawarich location tracking project
Recents
View all
Boosteroid still recording despite ignored windows setting
More options for Boosteroid still recording despite ignored windows setting
Missing JavaScript promise in authorization response
More options for Missing JavaScript promise in authorization response
Linux SQLite UI for NAS
More options for Linux SQLite UI for NAS
Claude API 500 internal server error
More options for Claude API 500 internal server error
Screenpipe query capabilities and usage
More options for Screenpipe query capabilities and usage
eGPU compatibility with Mac mini and Studio
More options for eGPU compatibility with Mac mini and Studio
Understanding OpenRouter API gateway
More options for Understanding OpenRouter API gateway
Screenpipe 14-day retention explained
More options for Screenpipe 14-day retention explained
Reddit homepage feed overview
More options for Reddit homepage feed overview
Docker container not visible in console
More options for Docker container not visible in console
DIA browser RAM configuration
More options for DIA browser RAM configuration
Screenpipe company background
More options for Screenpipe company background
DSK Bank Bulgaria secure investing explained
More options for DSK Bank Bulgaria secure investing explained
Building a comprehensive personal homelab system
More options for Building a comprehensive personal homelab system
Importing Google Timeline data to Owntracks
More options for Importing Google Timeline data to Owntracks
Chromecast remote volume buttons not working
More options for Chromecast remote volume buttons not working
Screenpipe free version guide
More options for Screenpipe free version guide
Self-hosted LLM alternatives to Claude Sonnet
More options for Self-hosted LLM alternatives to Claude Sonnet
Screenpipe database query setup
More options for Screenpipe database query setup
Running screenpipe for free
More options for Running screenpipe for free
Updated to 1.3109.0
Relaunch to apply
Relaunch
Lukas Kovalik
Lukas Kovalik
Get apps and extensions
Boosteroid still recording despite ignored windows setting
Boosteroid still recording despite ignored windows setting
More options for Boosteroid still recording despite ignored windows setting
Close
Share chat
Screenpipe I have set Boosteroid to ignored windows but it is still being recorded. lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ cat config.json
{
"disable_audio": true,
"monitor_ids": ["Display 1_1440x900_0,0", "Display 2_3008x1253_-813,-1253"],
"ignored_windows": [
"1Password",
"Keychain Access",
"Bitwarden",
"System Preferences",
"System Settings",
"zoom.us",
"HBO Max",
"Screenpipe Dashboard",
"Boosteroid"
]
}
Show more
15:40
Retry
Edit
Copy
The
ignored_windows
filter in Screenpipe matches against window
titles
, not app names. The issue is likely that Boosteroid runs in a browser (Safari), so the window title is something like
"Boosteroid - Play PC Games on any device"
or the game title — not just "Boosteroid".
To find the exact window title being captured, query the DB:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;"
Or if you use the
spq
alias:
Copy to clipboard
bash
spq
"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE window_name LIKE '%Boosteroid%' OR window_name LIKE '%boosteroid%' ORDER BY created_at DESC LIMIT 20;"
Once you see the exact
window_name
value, add that string to
ignored_windows
. For example it might need to be:
Copy to clipboard
json
"Boosteroid - Play PC Games on any device"
A few other things worth knowing:
The match is
substring-based
, so adding
"Boosteroid"
should
work if the window name contains that string — but only if Screenpipe is checking
window_name
and not
app_name
. Safari's
app_name
is just
"Safari"
, so the filter on
"Boosteroid"
may not be hitting anything.
You could also try adding
"Safari"
to
ignored_windows
entirely, though that's a blunt instrument if you use Safari for other things.
After editing
config.json
, make sure to
restart Screenpipe
fully for the change to take effect.
The query above will tell you exactly what string to add.
Copy
Give positive feedback
Give negative feedback
Retry
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;"
Error: in prepare, no such column: created_at
ari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;"
Error: in prepare, no such column: created_at
ari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $
Show more
15:50
Retry
Edit
Copy
The
ocr_text
table uses a different timestamp column. Try:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;"
If you're unsure of the column names, check the schema first:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
".schema ocr_text"
Copy
Give positive feedback
Give negative feedback
Retry
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;"
Error: in prepare, no such column: timestamp
ari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite ".schema ocr_text"
CREATE TABLE IF NOT EXISTS "ocr_text" (
frame_id INTEGER NOT NULL,
text TEXT NOT NULL,
text_json TEXT,
app_name TEXT NOT NULL DEFAULT '',
ocr_engine TEXT NOT NULL DEFAULT 'unknown'
, window_name TEXT, focused BOOLEAN DEFAULT FALSE, text_length INTEGER, sync_id TEXT, synced_at DATETIME);
CREATE INDEX idx_ocr_text_frame_id ON ocr_text(frame_id);
CREATE INDEX idx_ocr_text_frame_app_window ON ocr_text(frame_id, app_name, window_name);
CREATE INDEX idx_ocr_text_length ON ocr_text (text_length);
CREATE INDEX idx_ocr_text_sync_id ON ocr_text(sync_id) WHERE sync_id IS NOT NULL;
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $
Show more
16:10
Retry
Edit
Copy
No timestamp on
ocr_text
directly — it joins via
frame_id
to the
frames
table. Try:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT DISTINCT o.app_name, o.window_name FROM ocr_text o JOIN frames f ON o.frame_id = f.id WHERE o.app_name LIKE '%Safari%' OR o.window_name LIKE '%Boosteroid%' ORDER BY f.id DESC LIMIT 20;"
Or just get distinct values without ordering by time:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' LIMIT 20;"
Copy
Give positive feedback
Give negative feedback
Retry
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;"
Error: in prepare, no such column: timestamp
ari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite ".schema ocr_text"
CREATE TABLE IF NOT EXISTS "ocr_text" (
frame_id INTEGER NOT NULL,
text TEXT NOT NULL,
text_json TEXT,
app_name TEXT NOT NULL DEFAULT '',
ocr_engine TEXT NOT NULL DEFAULT 'unknown'
, window_name TEXT, focused BOOLEAN DEFAULT FALSE, text_length INTEGER, sync_id TEXT, synced_at DATETIME);
CREATE INDEX idx_ocr_text_frame_id ON ocr_text(frame_id);
CREATE INDEX idx_ocr_text_frame_app_window ON ocr_text(frame_id, app_name, window_name);
CREATE INDEX idx_ocr_text_length ON ocr_text (text_length);
CREATE INDEX idx_ocr_text_sync_id ON ocr_text(sync_id) WHERE sync_id IS NOT NULL;
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $
Show more
16:27
Retry
Edit
Copy
Run the second query (no ORDER BY):
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' LIMIT 20;"
Copy
Give positive feedback
Give negative feedback
Retry
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT DISTINCT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' LIMIT 20;"
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $
16:36
Retry
Edit
Copy
Empty result — Boosteroid isn't being captured via Safari's window name. It might be running in a different browser or as a PWA. Let's cast a wider net:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE window_name LIKE '%Boosteroid%' OR window_name LIKE '%boosteroid%' OR app_name LIKE '%Boosteroid%' OR app_name LIKE '%Chrome%' OR app_name LIKE '%Firefox%' OR app_name LIKE '%Arc%' OR app_name LIKE '%Brave%' OR app_name LIKE '%Vivaldi%' LIMIT 50;"
And separately, check what apps are being recorded at all:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT DISTINCT app_name FROM ocr_text LIMIT 50;"
That second query will show you exactly what Screenpipe is capturing, and you can spot which app Boosteroid is running under.
Copy
Give positive feedback
Give negative feedback
Retry
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT DISTINCT app_name, window_name FROM ocr_text WHERE window_name LIKE '%Boosteroid%' OR window_name LIKE '%boosteroid%' OR app_name LIKE '%Boosteroid%' OR app_name LIKE '%Chrome%' OR app_name LIKE '%Firefox%' OR app_name LIKE '%Arc%' OR app_name LIKE '%Brave%' OR app_name LIKE '%Vivaldi%' LIMIT 50;"
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT DISTINCT app_name FROM ocr_text LIMIT 50;"
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $
16:36
Retry
Edit
Copy
The DB is completely empty — no OCR data at all. This means Screenpipe isn't recording anything currently, or the DB you're querying is not the active one.
Check a few things:
1. Is Screenpipe actually running?
Copy to clipboard
bash
pgrep -la screenpipe
2. Is this the right DB?
Copy to clipboard
bash
ls
-lh ~/.screenpipe/db.sqlite
# Check if there are other DB files
find
~ -name
"db.sqlite"
2
>
/dev/null
3. Check DB size — if it's tiny, it's empty:
Copy to clipboard
bash
du
-sh ~/.screenpipe/db.sqlite
4. Check frames table too:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT COUNT(*) FROM frames;"...
|
[{"role":"AXLink","text":& [{"role":"AXLink","text":"Skip to content","depth":14,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Skip to content","depth":15,"role_description":"text"},{"role":"AXStaticText","text":"Click to collapse","depth":16,"role_description":"text"},{"role":"AXStaticText","text":"⌘B","depth":16,"role_description":"text"},{"role":"AXStaticText","text":"Drag to resize","depth":16,"role_description":"text"},{"role":"AXButton","text":"Collapse sidebar","depth":15,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search","depth":15,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Chat","depth":16,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cowork","depth":16,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code","depth":16,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New chat ⌘N","depth":16,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"New chat","depth":17,"role_description":"text"},{"role":"AXStaticText","text":"⌘N","depth":18,"role_description":"text"},{"role":"AXButton","text":"Projects","depth":16,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Customize","depth":16,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Artifacts","depth":16,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Pinned","depth":16,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Bulgarian citizenship application process for EU residents","depth":17,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Bulgarian citizenship application process for EU residents","depth":18,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Dawarich location tracking project","depth":17,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Dawarich location tracking project","depth":18,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Recents","depth":16,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"View all","depth":16,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Boosteroid still recording despite ignored windows setting","depth":17,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Boosteroid still recording despite ignored windows setting","depth":18,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Missing JavaScript promise in authorization response","depth":17,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Missing JavaScript promise in authorization response","depth":18,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Linux SQLite UI for NAS","depth":17,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Linux SQLite UI for NAS","depth":18,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Claude API 500 internal server error","depth":17,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Claude API 500 internal server error","depth":18,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screenpipe query capabilities and usage","depth":17,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Screenpipe query capabilities and usage","depth":18,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"eGPU compatibility with Mac mini and Studio","depth":17,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for eGPU compatibility with Mac mini and Studio","depth":18,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Understanding OpenRouter API gateway","depth":17,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Understanding OpenRouter API gateway","depth":18,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screenpipe 14-day retention explained","depth":17,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Screenpipe 14-day retention explained","depth":18,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reddit homepage feed overview","depth":17,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Reddit homepage feed overview","depth":18,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Docker container not visible in console","depth":17,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Docker container not visible in console","depth":18,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"DIA browser RAM configuration","depth":17,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for DIA browser RAM configuration","depth":18,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screenpipe company background","depth":17,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Screenpipe company background","depth":18,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"DSK Bank Bulgaria secure investing explained","depth":17,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for DSK Bank Bulgaria secure investing explained","depth":18,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Building a comprehensive personal homelab system","depth":17,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Building a comprehensive personal homelab system","depth":18,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Importing Google Timeline data to Owntracks","depth":17,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Importing Google Timeline data to Owntracks","depth":18,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Chromecast remote volume buttons not working","depth":17,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Chromecast remote volume buttons not working","depth":18,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screenpipe free version guide","depth":17,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Screenpipe free version guide","depth":18,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Self-hosted LLM alternatives to Claude Sonnet","depth":17,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Self-hosted LLM alternatives to Claude Sonnet","depth":18,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screenpipe database query setup","depth":17,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Screenpipe database query setup","depth":18,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Running screenpipe for free","depth":17,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Running screenpipe for free","depth":18,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Updated to 1.3109.0","depth":16,"role_description":"text"},{"role":"AXStaticText","text":"Relaunch to apply","depth":16,"role_description":"text"},{"role":"AXButton","text":"Relaunch","depth":16,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Lukas Kovalik","depth":16,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Lukas Kovalik","depth":17,"role_description":"text"},{"role":"AXButton","text":"Get apps and extensions","depth":15,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Boosteroid still recording despite ignored windows setting","depth":20,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Boosteroid still recording despite ignored windows setting","depth":22,"role_description":"text"},{"role":"AXPopUpButton","text":"More options for Boosteroid still recording despite ignored windows setting","depth":20,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":22,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Share chat","depth":22,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Screenpipe I have set Boosteroid to ignored windows but it is still being recorded. lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ cat config.json\n{\n \"disable_audio\": true,\n \"monitor_ids\": [\"Display 1_1440x900_0,0\", \"Display 2_3008x1253_-813,-1253\"],\n \"ignored_windows\": [\n \"1Password\",\n \"Keychain Access\",\n \"Bitwarden\",\n \"System Preferences\",\n \"System Settings\",\n \"zoom.us\",\n \"HBO Max\",\n \"Screenpipe Dashboard\",\n \"Boosteroid\"\n ]\n}","depth":25,"role_description":"text"},{"role":"AXButton","text":"Show more","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"15:40","depth":23,"role_description":"text"},{"role":"AXButton","text":"Retry","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Edit","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Copy","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"The","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"ignored_windows","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"filter in Screenpipe matches against window","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"titles","depth":25,"role_description":"text"},{"role":"AXStaticText","text":", not app names. The issue is likely that Boosteroid runs in a browser (Safari), so the window title is something like","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"\"Boosteroid - Play PC Games on any device\"","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"or the game title — not just \"Boosteroid\".","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"To find the exact window title being captured, query the DB:","depth":24,"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"sqlite3 ~/.screenpipe/db.sqlite","depth":27,"role_description":"text"},{"role":"AXStaticText","text":"\"SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;\"","depth":27,"role_description":"text"},{"role":"AXStaticText","text":"Or if you use the","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"spq","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"alias:","depth":24,"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"spq","depth":27,"role_description":"text"},{"role":"AXStaticText","text":"\"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE window_name LIKE '%Boosteroid%' OR window_name LIKE '%boosteroid%' ORDER BY created_at DESC LIMIT 20;\"","depth":27,"role_description":"text"},{"role":"AXStaticText","text":"Once you see the exact","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"window_name","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"value, add that string to","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"ignored_windows","depth":25,"role_description":"text"},{"role":"AXStaticText","text":". For example it might need to be:","depth":24,"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"json","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"\"Boosteroid - Play PC Games on any device\"","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"A few other things worth knowing:","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"The match is","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"substring-based","depth":26,"role_description":"text"},{"role":"AXStaticText","text":", so adding","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"\"Boosteroid\"","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"should","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"work if the window name contains that string — but only if Screenpipe is checking","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"window_name","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"and not","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"app_name","depth":26,"role_description":"text"},{"role":"AXStaticText","text":". Safari's","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"app_name","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"is just","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"\"Safari\"","depth":26,"role_description":"text"},{"role":"AXStaticText","text":", so the filter on","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"\"Boosteroid\"","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"may not be hitting anything.","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"You could also try adding","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"\"Safari\"","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"to","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"ignored_windows","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"entirely, though that's a blunt instrument if you use Safari for other things.","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"After editing","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"config.json","depth":26,"role_description":"text"},{"role":"AXStaticText","text":", make sure to","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"restart Screenpipe","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"fully for the change to take effect.","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"The query above will tell you exactly what string to add.","depth":24,"role_description":"text"},{"role":"AXButton","text":"Copy","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give positive feedback","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give negative feedback","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Retry","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite \"SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;\"\nError: in prepare, no such column: created_at\n ari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;\n error here ---^\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite \"SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;\"\nError: in prepare, no such column: created_at\n ari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;\n error here ---^\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $","depth":25,"role_description":"text"},{"role":"AXButton","text":"Show more","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"15:50","depth":23,"role_description":"text"},{"role":"AXButton","text":"Retry","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Edit","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Copy","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"The","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"ocr_text","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"table uses a different timestamp column. Try:","depth":24,"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"sqlite3 ~/.screenpipe/db.sqlite","depth":27,"role_description":"text"},{"role":"AXStaticText","text":"\"SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;\"","depth":27,"role_description":"text"},{"role":"AXStaticText","text":"If you're unsure of the column names, check the schema first:","depth":24,"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"sqlite3 ~/.screenpipe/db.sqlite","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"\".schema ocr_text\"","depth":26,"role_description":"text"},{"role":"AXButton","text":"Copy","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give positive feedback","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give negative feedback","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Retry","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite \"SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;\"\nError: in prepare, no such column: timestamp\n ari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;\n error here ---^\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite \".schema ocr_text\"\nCREATE TABLE IF NOT EXISTS \"ocr_text\" (\n frame_id INTEGER NOT NULL,\n text TEXT NOT NULL,\n text_json TEXT,\n app_name TEXT NOT NULL DEFAULT '',\n ocr_engine TEXT NOT NULL DEFAULT 'unknown'\n, window_name TEXT, focused BOOLEAN DEFAULT FALSE, text_length INTEGER, sync_id TEXT, synced_at DATETIME);\nCREATE INDEX idx_ocr_text_frame_id ON ocr_text(frame_id);\nCREATE INDEX idx_ocr_text_frame_app_window ON ocr_text(frame_id, app_name, window_name);\nCREATE INDEX idx_ocr_text_length ON ocr_text (text_length);\nCREATE INDEX idx_ocr_text_sync_id ON ocr_text(sync_id) WHERE sync_id IS NOT NULL;\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $","depth":25,"role_description":"text"},{"role":"AXButton","text":"Show more","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"16:10","depth":23,"role_description":"text"},{"role":"AXButton","text":"Retry","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Edit","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Copy","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"No timestamp on","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"ocr_text","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"directly — it joins via","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"frame_id","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"to the","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"frames","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"table. Try:","depth":24,"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"sqlite3 ~/.screenpipe/db.sqlite","depth":27,"role_description":"text"},{"role":"AXStaticText","text":"\"SELECT DISTINCT o.app_name, o.window_name FROM ocr_text o JOIN frames f ON o.frame_id = f.id WHERE o.app_name LIKE '%Safari%' OR o.window_name LIKE '%Boosteroid%' ORDER BY f.id DESC LIMIT 20;\"","depth":27,"role_description":"text"},{"role":"AXStaticText","text":"Or just get distinct values without ordering by time:","depth":24,"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"sqlite3 ~/.screenpipe/db.sqlite","depth":27,"role_description":"text"},{"role":"AXStaticText","text":"\"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' LIMIT 20;\"","depth":27,"role_description":"text"},{"role":"AXButton","text":"Copy","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give positive feedback","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give negative feedback","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Retry","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite \"SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;\"\nError: in prepare, no such column: timestamp\n ari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;\n error here ---^\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite \".schema ocr_text\"\nCREATE TABLE IF NOT EXISTS \"ocr_text\" (\n frame_id INTEGER NOT NULL,\n text TEXT NOT NULL,\n text_json TEXT,\n app_name TEXT NOT NULL DEFAULT '',\n ocr_engine TEXT NOT NULL DEFAULT 'unknown'\n, window_name TEXT, focused BOOLEAN DEFAULT FALSE, text_length INTEGER, sync_id TEXT, synced_at DATETIME);\nCREATE INDEX idx_ocr_text_frame_id ON ocr_text(frame_id);\nCREATE INDEX idx_ocr_text_frame_app_window ON ocr_text(frame_id, app_name, window_name);\nCREATE INDEX idx_ocr_text_length ON ocr_text (text_length);\nCREATE INDEX idx_ocr_text_sync_id ON ocr_text(sync_id) WHERE sync_id IS NOT NULL;\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $","depth":25,"role_description":"text"},{"role":"AXButton","text":"Show more","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"16:27","depth":23,"role_description":"text"},{"role":"AXButton","text":"Retry","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Edit","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Copy","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Run the second query (no ORDER BY):","depth":24,"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"sqlite3 ~/.screenpipe/db.sqlite","depth":27,"role_description":"text"},{"role":"AXStaticText","text":"\"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' LIMIT 20;\"","depth":27,"role_description":"text"},{"role":"AXButton","text":"Copy","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give positive feedback","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give negative feedback","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Retry","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite \"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' LIMIT 20;\"\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"16:36","depth":23,"role_description":"text"},{"role":"AXButton","text":"Retry","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Edit","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Copy","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Empty result — Boosteroid isn't being captured via Safari's window name. It might be running in a different browser or as a PWA. Let's cast a wider net:","depth":24,"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"sqlite3 ~/.screenpipe/db.sqlite","depth":27,"role_description":"text"},{"role":"AXStaticText","text":"\"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE window_name LIKE '%Boosteroid%' OR window_name LIKE '%boosteroid%' OR app_name LIKE '%Boosteroid%' OR app_name LIKE '%Chrome%' OR app_name LIKE '%Firefox%' OR app_name LIKE '%Arc%' OR app_name LIKE '%Brave%' OR app_name LIKE '%Vivaldi%' LIMIT 50;\"","depth":27,"role_description":"text"},{"role":"AXStaticText","text":"And separately, check what apps are being recorded at all:","depth":24,"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"sqlite3 ~/.screenpipe/db.sqlite","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"\"SELECT DISTINCT app_name FROM ocr_text LIMIT 50;\"","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"That second query will show you exactly what Screenpipe is capturing, and you can spot which app Boosteroid is running under.","depth":24,"role_description":"text"},{"role":"AXButton","text":"Copy","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give positive feedback","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give negative feedback","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Retry","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite \"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE window_name LIKE '%Boosteroid%' OR window_name LIKE '%boosteroid%' OR app_name LIKE '%Boosteroid%' OR app_name LIKE '%Chrome%' OR app_name LIKE '%Firefox%' OR app_name LIKE '%Arc%' OR app_name LIKE '%Brave%' OR app_name LIKE '%Vivaldi%' LIMIT 50;\"\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite \"SELECT DISTINCT app_name FROM ocr_text LIMIT 50;\"\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"16:36","depth":23,"role_description":"text"},{"role":"AXButton","text":"Retry","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Edit","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Copy","depth":23,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"The DB is completely empty — no OCR data at all. This means Screenpipe isn't recording anything currently, or the DB you're querying is not the active one.","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"Check a few things:","depth":24,"role_description":"text"},{"role":"AXStaticText","text":"1. Is Screenpipe actually running?","depth":25,"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"pgrep -la screenpipe","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"2. Is this the right DB?","depth":25,"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"ls","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"-lh ~/.screenpipe/db.sqlite","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"# Check if there are other DB files","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"find","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"~ -name","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"\"db.sqlite\"","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":26,"role_description":"text"},{"role":"AXStaticText","text":">","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"/dev/null","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"3. Check DB size — if it's tiny, it's empty:","depth":25,"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"du","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"-sh ~/.screenpipe/db.sqlite","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"4. Check frames table too:","depth":25,"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"role_description":"text"},{"role":"AXStaticText","text":"sqlite3 ~/.screenpipe/db.sqlite","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"\"SELECT COUNT(*) FROM frames;\"","depth":26,"role_description":"text"}]...
|
-430807021609346284
|
-8544006821181072974
|
app_switch
|
accessibility
|
NULL
|
Skip to content
Skip to content
Click to collapse
Skip to content
Skip to content
Click to collapse
⌘B
Drag to resize
Collapse sidebar
Search
Chat
Cowork
Code
New chat ⌘N
New chat
⌘N
Projects
Customize
Artifacts
Pinned
Bulgarian citizenship application process for EU residents
More options for Bulgarian citizenship application process for EU residents
Dawarich location tracking project
More options for Dawarich location tracking project
Recents
View all
Boosteroid still recording despite ignored windows setting
More options for Boosteroid still recording despite ignored windows setting
Missing JavaScript promise in authorization response
More options for Missing JavaScript promise in authorization response
Linux SQLite UI for NAS
More options for Linux SQLite UI for NAS
Claude API 500 internal server error
More options for Claude API 500 internal server error
Screenpipe query capabilities and usage
More options for Screenpipe query capabilities and usage
eGPU compatibility with Mac mini and Studio
More options for eGPU compatibility with Mac mini and Studio
Understanding OpenRouter API gateway
More options for Understanding OpenRouter API gateway
Screenpipe 14-day retention explained
More options for Screenpipe 14-day retention explained
Reddit homepage feed overview
More options for Reddit homepage feed overview
Docker container not visible in console
More options for Docker container not visible in console
DIA browser RAM configuration
More options for DIA browser RAM configuration
Screenpipe company background
More options for Screenpipe company background
DSK Bank Bulgaria secure investing explained
More options for DSK Bank Bulgaria secure investing explained
Building a comprehensive personal homelab system
More options for Building a comprehensive personal homelab system
Importing Google Timeline data to Owntracks
More options for Importing Google Timeline data to Owntracks
Chromecast remote volume buttons not working
More options for Chromecast remote volume buttons not working
Screenpipe free version guide
More options for Screenpipe free version guide
Self-hosted LLM alternatives to Claude Sonnet
More options for Self-hosted LLM alternatives to Claude Sonnet
Screenpipe database query setup
More options for Screenpipe database query setup
Running screenpipe for free
More options for Running screenpipe for free
Updated to 1.3109.0
Relaunch to apply
Relaunch
Lukas Kovalik
Lukas Kovalik
Get apps and extensions
Boosteroid still recording despite ignored windows setting
Boosteroid still recording despite ignored windows setting
More options for Boosteroid still recording despite ignored windows setting
Close
Share chat
Screenpipe I have set Boosteroid to ignored windows but it is still being recorded. lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ cat config.json
{
"disable_audio": true,
"monitor_ids": ["Display 1_1440x900_0,0", "Display 2_3008x1253_-813,-1253"],
"ignored_windows": [
"1Password",
"Keychain Access",
"Bitwarden",
"System Preferences",
"System Settings",
"zoom.us",
"HBO Max",
"Screenpipe Dashboard",
"Boosteroid"
]
}
Show more
15:40
Retry
Edit
Copy
The
ignored_windows
filter in Screenpipe matches against window
titles
, not app names. The issue is likely that Boosteroid runs in a browser (Safari), so the window title is something like
"Boosteroid - Play PC Games on any device"
or the game title — not just "Boosteroid".
To find the exact window title being captured, query the DB:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;"
Or if you use the
spq
alias:
Copy to clipboard
bash
spq
"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE window_name LIKE '%Boosteroid%' OR window_name LIKE '%boosteroid%' ORDER BY created_at DESC LIMIT 20;"
Once you see the exact
window_name
value, add that string to
ignored_windows
. For example it might need to be:
Copy to clipboard
json
"Boosteroid - Play PC Games on any device"
A few other things worth knowing:
The match is
substring-based
, so adding
"Boosteroid"
should
work if the window name contains that string — but only if Screenpipe is checking
window_name
and not
app_name
. Safari's
app_name
is just
"Safari"
, so the filter on
"Boosteroid"
may not be hitting anything.
You could also try adding
"Safari"
to
ignored_windows
entirely, though that's a blunt instrument if you use Safari for other things.
After editing
config.json
, make sure to
restart Screenpipe
fully for the change to take effect.
The query above will tell you exactly what string to add.
Copy
Give positive feedback
Give negative feedback
Retry
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;"
Error: in prepare, no such column: created_at
ari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;"
Error: in prepare, no such column: created_at
ari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $
Show more
15:50
Retry
Edit
Copy
The
ocr_text
table uses a different timestamp column. Try:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;"
If you're unsure of the column names, check the schema first:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
".schema ocr_text"
Copy
Give positive feedback
Give negative feedback
Retry
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;"
Error: in prepare, no such column: timestamp
ari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite ".schema ocr_text"
CREATE TABLE IF NOT EXISTS "ocr_text" (
frame_id INTEGER NOT NULL,
text TEXT NOT NULL,
text_json TEXT,
app_name TEXT NOT NULL DEFAULT '',
ocr_engine TEXT NOT NULL DEFAULT 'unknown'
, window_name TEXT, focused BOOLEAN DEFAULT FALSE, text_length INTEGER, sync_id TEXT, synced_at DATETIME);
CREATE INDEX idx_ocr_text_frame_id ON ocr_text(frame_id);
CREATE INDEX idx_ocr_text_frame_app_window ON ocr_text(frame_id, app_name, window_name);
CREATE INDEX idx_ocr_text_length ON ocr_text (text_length);
CREATE INDEX idx_ocr_text_sync_id ON ocr_text(sync_id) WHERE sync_id IS NOT NULL;
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $
Show more
16:10
Retry
Edit
Copy
No timestamp on
ocr_text
directly — it joins via
frame_id
to the
frames
table. Try:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT DISTINCT o.app_name, o.window_name FROM ocr_text o JOIN frames f ON o.frame_id = f.id WHERE o.app_name LIKE '%Safari%' OR o.window_name LIKE '%Boosteroid%' ORDER BY f.id DESC LIMIT 20;"
Or just get distinct values without ordering by time:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' LIMIT 20;"
Copy
Give positive feedback
Give negative feedback
Retry
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;"
Error: in prepare, no such column: timestamp
ari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite ".schema ocr_text"
CREATE TABLE IF NOT EXISTS "ocr_text" (
frame_id INTEGER NOT NULL,
text TEXT NOT NULL,
text_json TEXT,
app_name TEXT NOT NULL DEFAULT '',
ocr_engine TEXT NOT NULL DEFAULT 'unknown'
, window_name TEXT, focused BOOLEAN DEFAULT FALSE, text_length INTEGER, sync_id TEXT, synced_at DATETIME);
CREATE INDEX idx_ocr_text_frame_id ON ocr_text(frame_id);
CREATE INDEX idx_ocr_text_frame_app_window ON ocr_text(frame_id, app_name, window_name);
CREATE INDEX idx_ocr_text_length ON ocr_text (text_length);
CREATE INDEX idx_ocr_text_sync_id ON ocr_text(sync_id) WHERE sync_id IS NOT NULL;
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $
Show more
16:27
Retry
Edit
Copy
Run the second query (no ORDER BY):
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' LIMIT 20;"
Copy
Give positive feedback
Give negative feedback
Retry
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT DISTINCT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' LIMIT 20;"
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $
16:36
Retry
Edit
Copy
Empty result — Boosteroid isn't being captured via Safari's window name. It might be running in a different browser or as a PWA. Let's cast a wider net:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE window_name LIKE '%Boosteroid%' OR window_name LIKE '%boosteroid%' OR app_name LIKE '%Boosteroid%' OR app_name LIKE '%Chrome%' OR app_name LIKE '%Firefox%' OR app_name LIKE '%Arc%' OR app_name LIKE '%Brave%' OR app_name LIKE '%Vivaldi%' LIMIT 50;"
And separately, check what apps are being recorded at all:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT DISTINCT app_name FROM ocr_text LIMIT 50;"
That second query will show you exactly what Screenpipe is capturing, and you can spot which app Boosteroid is running under.
Copy
Give positive feedback
Give negative feedback
Retry
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT DISTINCT app_name, window_name FROM ocr_text WHERE window_name LIKE '%Boosteroid%' OR window_name LIKE '%boosteroid%' OR app_name LIKE '%Boosteroid%' OR app_name LIKE '%Chrome%' OR app_name LIKE '%Firefox%' OR app_name LIKE '%Arc%' OR app_name LIKE '%Brave%' OR app_name LIKE '%Vivaldi%' LIMIT 50;"
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT DISTINCT app_name FROM ocr_text LIMIT 50;"
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $
16:36
Retry
Edit
Copy
The DB is completely empty — no OCR data at all. This means Screenpipe isn't recording anything currently, or the DB you're querying is not the active one.
Check a few things:
1. Is Screenpipe actually running?
Copy to clipboard
bash
pgrep -la screenpipe
2. Is this the right DB?
Copy to clipboard
bash
ls
-lh ~/.screenpipe/db.sqlite
# Check if there are other DB files
find
~ -name
"db.sqlite"
2
>
/dev/null
3. Check DB size — if it's tiny, it's empty:
Copy to clipboard
bash
du
-sh ~/.screenpipe/db.sqlite
4. Check frames table too:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT COUNT(*) FROM frames;"...
|
NULL
|
|
50862
|
1094
|
30
|
2026-04-17T15:33:25.695930+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776440005695_m2.jpg...
|
Claude
|
Claude
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Skip to content
Skip to content
Click to collapse
Skip to content
Skip to content
Click to collapse
⌘B
Drag to resize
Collapse sidebar
Search
Chat
Cowork
Code
New chat ⌘N
New chat
⌘N
Projects
Customize
Artifacts
Pinned
Bulgarian citizenship application process for EU residents
More options for Bulgarian citizenship application process for EU residents
Dawarich location tracking project
More options for Dawarich location tracking project
Recents
View all
Boosteroid still recording despite ignored windows setting
More options for Boosteroid still recording despite ignored windows setting
Missing JavaScript promise in authorization response
More options for Missing JavaScript promise in authorization response
Linux SQLite UI for NAS
More options for Linux SQLite UI for NAS
Claude API 500 internal server error
More options for Claude API 500 internal server error
Screenpipe query capabilities and usage
More options for Screenpipe query capabilities and usage
eGPU compatibility with Mac mini and Studio
More options for eGPU compatibility with Mac mini and Studio
Understanding OpenRouter API gateway
More options for Understanding OpenRouter API gateway
Screenpipe 14-day retention explained
More options for Screenpipe 14-day retention explained
Reddit homepage feed overview
More options for Reddit homepage feed overview
Docker container not visible in console
More options for Docker container not visible in console
DIA browser RAM configuration
More options for DIA browser RAM configuration
Screenpipe company background
More options for Screenpipe company background
DSK Bank Bulgaria secure investing explained
More options for DSK Bank Bulgaria secure investing explained
Building a comprehensive personal homelab system
More options for Building a comprehensive personal homelab system
Importing Google Timeline data to Owntracks
More options for Importing Google Timeline data to Owntracks
Chromecast remote volume buttons not working
More options for Chromecast remote volume buttons not working
Screenpipe free version guide
More options for Screenpipe free version guide
Self-hosted LLM alternatives to Claude Sonnet
More options for Self-hosted LLM alternatives to Claude Sonnet
Screenpipe database query setup
More options for Screenpipe database query setup
Running screenpipe for free
More options for Running screenpipe for free
Updated to 1.3109.0
Relaunch to apply
Relaunch
Lukas Kovalik
Lukas Kovalik
Get apps and extensions
Boosteroid still recording despite ignored windows setting
Boosteroid still recording despite ignored windows setting
More options for Boosteroid still recording despite ignored windows setting
Close
Share chat
Screenpipe I have set Boosteroid to ignored windows but it is still being recorded. lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ cat config.json
{
"disable_audio": true,
"monitor_ids": ["Display 1_1440x900_0,0", "Display 2_3008x1253_-813,-1253"],
"ignored_windows": [
"1Password",
"Keychain Access",
"Bitwarden",
"System Preferences",
"System Settings",
"zoom.us",
"HBO Max",
"Screenpipe Dashboard",
"Boosteroid"
]
}
Show more
15:40
Retry
Edit
Copy
The
ignored_windows
filter in Screenpipe matches against window
titles
, not app names. The issue is likely that Boosteroid runs in a browser (Safari), so the window title is something like
"Boosteroid - Play PC Games on any device"
or the game title — not just "Boosteroid".
To find the exact window title being captured, query the DB:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;"
Or if you use the
spq
alias:
Copy to clipboard
bash
spq
"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE window_name LIKE '%Boosteroid%' OR window_name LIKE '%boosteroid%' ORDER BY created_at DESC LIMIT 20;"
Once you see the exact
window_name
value, add that string to
ignored_windows
. For example it might need to be:
Copy to clipboard
json
"Boosteroid - Play PC Games on any device"
A few other things worth knowing:
The match is
substring-based
, so adding
"Boosteroid"
should
work if the window name contains that string — but only if Screenpipe is checking
window_name
and not
app_name
. Safari's
app_name
is just
"Safari"
, so the filter on
"Boosteroid"
may not be hitting anything.
You could also try adding
"Safari"
to
ignored_windows
entirely, though that's a blunt instrument if you use Safari for other things.
After editing
config.json
, make sure to
restart Screenpipe
fully for the change to take effect.
The query above will tell you exactly what string to add.
Copy
Give positive feedback
Give negative feedback
Retry
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;"
Error: in prepare, no such column: created_at
ari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;"
Error: in prepare, no such column: created_at
ari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $
Show more
15:50
Retry
Edit
Copy
The
ocr_text
table uses a different timestamp column. Try:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;"
If you're unsure of the column names, check the schema first:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
".schema ocr_text"
Copy
Give positive feedback
Give negative feedback
Retry
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;"
Error: in prepare, no such column: timestamp
ari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite ".schema ocr_text"
CREATE TABLE IF NOT EXISTS "ocr_text" (
frame_id INTEGER NOT NULL,
text TEXT NOT NULL,
text_json TEXT,
app_name TEXT NOT NULL DEFAULT '',
ocr_engine TEXT NOT NULL DEFAULT 'unknown'
, window_name TEXT, focused BOOLEAN DEFAULT FALSE, text_length INTEGER, sync_id TEXT, synced_at DATETIME);
CREATE INDEX idx_ocr_text_frame_id ON ocr_text(frame_id);
CREATE INDEX idx_ocr_text_frame_app_window ON ocr_text(frame_id, app_name, window_name);
CREATE INDEX idx_ocr_text_length ON ocr_text (text_length);
CREATE INDEX idx_ocr_text_sync_id ON ocr_text(sync_id) WHERE sync_id IS NOT NULL;
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $
Show more
16:10
Retry
Edit
Copy
No timestamp on
ocr_text
directly — it joins via
frame_id
to the
frames
table. Try:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT DISTINCT o.app_name, o.window_name FROM ocr_text o JOIN frames f ON o.frame_id = f.id WHERE o.app_name LIKE '%Safari%' OR o.window_name LIKE '%Boosteroid%' ORDER BY f.id DESC LIMIT 20;"
Or just get distinct values without ordering by time:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' LIMIT 20;"
Copy
Give positive feedback
Give negative feedback
Retry
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;"
Error: in prepare, no such column: timestamp
ari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite ".schema ocr_text"
CREATE TABLE IF NOT EXISTS "ocr_text" (
frame_id INTEGER NOT NULL,
text TEXT NOT NULL,
text_json TEXT,
app_name TEXT NOT NULL DEFAULT '',
ocr_engine TEXT NOT NULL DEFAULT 'unknown'
, window_name TEXT, focused BOOLEAN DEFAULT FALSE, text_length INTEGER, sync_id TEXT, synced_at DATETIME);
CREATE INDEX idx_ocr_text_frame_id ON ocr_text(frame_id);
CREATE INDEX idx_ocr_text_frame_app_window ON ocr_text(frame_id, app_name, window_name);
CREATE INDEX idx_ocr_text_length ON ocr_text (text_length);
CREATE INDEX idx_ocr_text_sync_id ON ocr_text(sync_id) WHERE sync_id IS NOT NULL;
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $
Show more
16:27
Retry
Edit
Copy
Run the second query (no ORDER BY):
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' LIMIT 20;"
Copy
Give positive feedback
Give negative feedback
Retry
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT DISTINCT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' LIMIT 20;"
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $
16:36
Retry
Edit
Copy
Empty result — Boosteroid isn't being captured via Safari's window name. It might be running in a different browser or as a PWA. Let's cast a wider net:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE window_name LIKE '%Boosteroid%' OR window_name LIKE '%boosteroid%' OR app_name LIKE '%Boosteroid%' OR app_name LIKE '%Chrome%' OR app_name LIKE '%Firefox%' OR app_name LIKE '%Arc%' OR app_name LIKE '%Brave%' OR app_name LIKE '%Vivaldi%' LIMIT 50;"
And separately, check what apps are being recorded at all:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT DISTINCT app_name FROM ocr_text LIMIT 50;"
That second query will show you exactly what Screenpipe is capturing, and you can spot which app Boosteroid is running under.
Copy
Give positive feedback
Give negative feedback
Retry
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT DISTINCT app_name, window_name FROM ocr_text WHERE window_name LIKE '%Boosteroid%' OR window_name LIKE '%boosteroid%' OR app_name LIKE '%Boosteroid%' OR app_name LIKE '%Chrome%' OR app_name LIKE '%Firefox%' OR app_name LIKE '%Arc%' OR app_name LIKE '%Brave%' OR app_name LIKE '%Vivaldi%' LIMIT 50;"
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT DISTINCT app_name FROM ocr_text LIMIT 50;"
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $
16:36
Retry
Edit
Copy
The DB is completely empty — no OCR data at all. This means Screenpipe isn't recording anything currently, or the DB you're querying is not the active one.
Check a few things:
1. Is Screenpipe actually running?
Copy to clipboard
bash
pgrep -la screenpipe
2. Is this the right DB?
Copy to clipboard
bash
ls
-lh ~/.screenpipe/db.sqlite
# Check if there are other DB files
find
~ -name
"db.sqlite"
2
>
/dev/null
3. Check DB size — if it's tiny, it's empty:
Copy to clipboard
bash
du
-sh ~/.screenpipe/db.sqlite
4. Check frames table too:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT COUNT(*) FROM frames;"...
|
[{"role":"AXLink","text":& [{"role":"AXLink","text":"Skip to content","depth":14,"bounds":{"left":0.002734375,"top":0.022222223,"width":0.000390625,"height":0.00069444446},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Skip to content","depth":15,"bounds":{"left":0.002734375,"top":0.022222223,"width":0.044921875,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Click to collapse","depth":16,"bounds":{"left":0.1234375,"top":0.74375,"width":0.036328126,"height":0.010416667},"role_description":"text"},{"role":"AXStaticText","text":"⌘B","depth":16,"bounds":{"left":0.16171876,"top":0.74375,"width":0.0078125,"height":0.010416667},"role_description":"text"},{"role":"AXStaticText","text":"Drag to resize","depth":16,"bounds":{"left":0.1234375,"top":0.75416666,"width":0.03046875,"height":0.010416667},"role_description":"text"},{"role":"AXButton","text":"Collapse sidebar","depth":15,"bounds":{"left":0.0359375,"top":0.025694445,"width":0.009375,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search","depth":15,"bounds":{"left":0.0453125,"top":0.025694445,"width":0.009375,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Chat","depth":16,"bounds":{"left":0.006640625,"top":0.05347222,"width":0.026171874,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cowork","depth":16,"bounds":{"left":0.033203125,"top":0.05347222,"width":0.011328125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code","depth":16,"bounds":{"left":0.044921875,"top":0.05347222,"width":0.011328125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New chat ⌘N","depth":16,"bounds":{"left":0.00625,"top":0.07986111,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"New chat","depth":17,"bounds":{"left":0.01796875,"top":0.08263889,"width":0.023046875,"height":0.011805556},"role_description":"text"},{"role":"AXStaticText","text":"⌘N","depth":18,"bounds":{"left":0.09882812,"top":0.083333336,"width":0.008203125,"height":0.010416667},"role_description":"text"},{"role":"AXButton","text":"Projects","depth":16,"bounds":{"left":0.00625,"top":0.09791667,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Customize","depth":16,"bounds":{"left":0.00625,"top":0.11597222,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Artifacts","depth":16,"bounds":{"left":0.00625,"top":0.13402778,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Pinned","depth":16,"bounds":{"left":0.00859375,"top":0.16875,"width":0.1,"height":0.011111111},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Bulgarian citizenship application process for EU residents","depth":17,"bounds":{"left":0.00625,"top":0.18333334,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Bulgarian citizenship application process for EU residents","depth":18,"bounds":{"left":0.10078125,"top":0.18611111,"width":0.00703125,"height":0.0125},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Dawarich location tracking project","depth":17,"bounds":{"left":0.00625,"top":0.20208333,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Dawarich location tracking project","depth":18,"bounds":{"left":0.10078125,"top":0.2048611,"width":0.00703125,"height":0.0125},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Recents","depth":16,"bounds":{"left":0.00859375,"top":0.22847222,"width":0.076171875,"height":0.011111111},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"View all","depth":16,"bounds":{"left":0.0859375,"top":0.22847222,"width":0.02265625,"height":0.011111111},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Boosteroid still recording despite ignored windows setting","depth":17,"bounds":{"left":0.00625,"top":0.24305555,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Boosteroid still recording despite ignored windows setting","depth":18,"bounds":{"left":0.10078125,"top":0.24583334,"width":0.00703125,"height":0.0125},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Missing JavaScript promise in authorization response","depth":17,"bounds":{"left":0.00625,"top":0.26180556,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Missing JavaScript promise in authorization response","depth":18,"bounds":{"left":0.10078125,"top":0.26458332,"width":0.00703125,"height":0.0125},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Linux SQLite UI for NAS","depth":17,"bounds":{"left":0.00625,"top":0.28055555,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Linux SQLite UI for NAS","depth":18,"bounds":{"left":0.10078125,"top":0.28333333,"width":0.00703125,"height":0.0125},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Claude API 500 internal server error","depth":17,"bounds":{"left":0.00625,"top":0.29930556,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Claude API 500 internal server error","depth":18,"bounds":{"left":0.10078125,"top":0.30208334,"width":0.00703125,"height":0.0125},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screenpipe query capabilities and usage","depth":17,"bounds":{"left":0.00625,"top":0.31805557,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Screenpipe query capabilities and usage","depth":18,"bounds":{"left":0.10078125,"top":0.32083333,"width":0.00703125,"height":0.0125},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"eGPU compatibility with Mac mini and Studio","depth":17,"bounds":{"left":0.00625,"top":0.33680555,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for eGPU compatibility with Mac mini and Studio","depth":18,"bounds":{"left":0.10078125,"top":0.33958334,"width":0.00703125,"height":0.0125},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Understanding OpenRouter API gateway","depth":17,"bounds":{"left":0.00625,"top":0.35555556,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Understanding OpenRouter API gateway","depth":18,"bounds":{"left":0.10078125,"top":0.35833332,"width":0.00703125,"height":0.0125},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screenpipe 14-day retention explained","depth":17,"bounds":{"left":0.00625,"top":0.37430555,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Screenpipe 14-day retention explained","depth":18,"bounds":{"left":0.10078125,"top":0.37708333,"width":0.00703125,"height":0.0125},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reddit homepage feed overview","depth":17,"bounds":{"left":0.00625,"top":0.39305556,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Reddit homepage feed overview","depth":18,"bounds":{"left":0.10078125,"top":0.39583334,"width":0.00703125,"height":0.0125},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Docker container not visible in console","depth":17,"bounds":{"left":0.00625,"top":0.41180557,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Docker container not visible in console","depth":18,"bounds":{"left":0.10078125,"top":0.41458333,"width":0.00703125,"height":0.0125},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"DIA browser RAM configuration","depth":17,"bounds":{"left":0.00625,"top":0.43055555,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for DIA browser RAM configuration","depth":18,"bounds":{"left":0.10078125,"top":0.43333334,"width":0.00703125,"height":0.0125},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screenpipe company background","depth":17,"bounds":{"left":0.00625,"top":0.44930556,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Screenpipe company background","depth":18,"bounds":{"left":0.10078125,"top":0.45208332,"width":0.00703125,"height":0.0125},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"DSK Bank Bulgaria secure investing explained","depth":17,"bounds":{"left":0.00625,"top":0.46805555,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for DSK Bank Bulgaria secure investing explained","depth":18,"bounds":{"left":0.10078125,"top":0.47083333,"width":0.00703125,"height":0.0125},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Building a comprehensive personal homelab system","depth":17,"bounds":{"left":0.00625,"top":0.48680556,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Building a comprehensive personal homelab system","depth":18,"bounds":{"left":0.10078125,"top":0.48958334,"width":0.00703125,"height":0.0125},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Importing Google Timeline data to Owntracks","depth":17,"bounds":{"left":0.00625,"top":0.50555557,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Importing Google Timeline data to Owntracks","depth":18,"bounds":{"left":0.10078125,"top":0.5083333,"width":0.00703125,"height":0.0125},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Chromecast remote volume buttons not working","depth":17,"bounds":{"left":0.00625,"top":0.5243056,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Chromecast remote volume buttons not working","depth":18,"bounds":{"left":0.10078125,"top":0.52708334,"width":0.00703125,"height":0.0125},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screenpipe free version guide","depth":17,"bounds":{"left":0.00625,"top":0.54305553,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Screenpipe free version guide","depth":18,"bounds":{"left":0.10078125,"top":0.54583335,"width":0.00703125,"height":0.0125},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Self-hosted LLM alternatives to Claude Sonnet","depth":17,"bounds":{"left":0.00625,"top":0.56180555,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Self-hosted LLM alternatives to Claude Sonnet","depth":18,"bounds":{"left":0.10078125,"top":0.56458336,"width":0.00703125,"height":0.0125},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screenpipe database query setup","depth":17,"bounds":{"left":0.00625,"top":0.58055556,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Screenpipe database query setup","depth":18,"bounds":{"left":0.10078125,"top":0.5833333,"width":0.00703125,"height":0.0125},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Running screenpipe for free","depth":17,"bounds":{"left":0.00625,"top":0.59930557,"width":0.103125,"height":0.018055556},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Running screenpipe for free","depth":18,"bounds":{"left":0.10078125,"top":0.6020833,"width":0.00703125,"height":0.0125},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Updated to 1.3109.0","depth":16,"bounds":{"left":0.032421876,"top":0.89166665,"width":0.05078125,"height":0.0125},"role_description":"text"},{"role":"AXStaticText","text":"Relaunch to apply","depth":16,"bounds":{"left":0.037890624,"top":0.90625,"width":0.03984375,"height":0.010416667},"role_description":"text"},{"role":"AXButton","text":"Relaunch","depth":16,"bounds":{"left":0.011328125,"top":0.92569447,"width":0.09296875,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Lukas Kovalik","depth":16,"bounds":{"left":0.00625,"top":0.97083336,"width":0.044921875,"height":0.016666668},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Lukas Kovalik","depth":17,"bounds":{"left":0.01796875,"top":0.9736111,"width":0.030078124,"height":0.010416667},"role_description":"text"},{"role":"AXButton","text":"Get apps and extensions","depth":15,"bounds":{"left":0.1,"top":0.97152776,"width":0.009375,"height":0.016666668},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Boosteroid still recording despite ignored windows setting","depth":20,"bounds":{"left":0.121875,"top":0.024305556,"width":0.15703125,"height":0.019444445},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Boosteroid still recording despite ignored windows setting","depth":22,"bounds":{"left":0.125,"top":0.027083334,"width":0.15078124,"height":0.013194445},"role_description":"text"},{"role":"AXPopUpButton","text":"More options for Boosteroid still recording despite ignored windows setting","depth":20,"bounds":{"left":0.27890626,"top":0.024305556,"width":0.011328125,"height":0.019444445},"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":22,"bounds":{"left":0.52382815,"top":0.022916667,"width":0.0125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Share chat","depth":22,"bounds":{"left":0.5378906,"top":0.022916667,"width":0.0125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Screenpipe I have set Boosteroid to ignored windows but it is still being recorded. lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ cat config.json\n{\n \"disable_audio\": true,\n \"monitor_ids\": [\"Display 1_1440x900_0,0\", \"Display 2_3008x1253_-813,-1253\"],\n \"ignored_windows\": [\n \"1Password\",\n \"Keychain Access\",\n \"Bitwarden\",\n \"System Preferences\",\n \"System Settings\",\n \"zoom.us\",\n \"HBO Max\",\n \"Screenpipe Dashboard\",\n \"Boosteroid\"\n ]\n}","depth":25,"bounds":{"left":0.23867187,"top":0.017361112,"width":0.22578125,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Show more","depth":23,"bounds":{"left":0.23867187,"top":0.017361112,"width":0.17421874,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"15:40","depth":23,"bounds":{"left":0.425,"top":0.017361112,"width":0.011328125,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Retry","depth":23,"bounds":{"left":0.43945312,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Edit","depth":23,"bounds":{"left":0.4519531,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Copy","depth":23,"bounds":{"left":0.46445313,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"The","depth":24,"bounds":{"left":0.19257812,"top":0.017361112,"width":0.012890625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"ignored_windows","depth":25,"bounds":{"left":0.20703125,"top":0.017361112,"width":0.05078125,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"filter in Screenpipe matches against window","depth":24,"bounds":{"left":0.259375,"top":0.017361112,"width":0.13085938,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"titles","depth":25,"bounds":{"left":0.38984376,"top":0.017361112,"width":0.015625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":", not app names. The issue is likely that Boosteroid runs in a browser (Safari), so the window title is something like","depth":24,"bounds":{"left":0.19257812,"top":0.017361112,"width":0.27148438,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"\"Boosteroid - Play PC Games on any device\"","depth":25,"bounds":{"left":0.19257812,"top":0.017361112,"width":0.13007812,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"or the game title — not just \"Boosteroid\".","depth":24,"bounds":{"left":0.32226562,"top":0.017361112,"width":0.11835937,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"To find the exact window title being captured, query the DB:","depth":24,"bounds":{"left":0.19257812,"top":0.017361112,"width":0.17226562,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"bounds":{"left":0.4609375,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.0109375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"sqlite3 ~/.screenpipe/db.sqlite","depth":27,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.10507812,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"\"SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;\"","depth":27,"bounds":{"left":0.3,"top":0.017361112,"width":0.1765625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Or if you use the","depth":24,"bounds":{"left":0.19257812,"top":0.017361112,"width":0.0484375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"spq","depth":25,"bounds":{"left":0.24257812,"top":0.017361112,"width":0.010546875,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"alias:","depth":24,"bounds":{"left":0.2546875,"top":0.017361112,"width":0.01640625,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"bounds":{"left":0.4609375,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.0109375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"spq","depth":27,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.01328125,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"\"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE window_name LIKE '%Boosteroid%' OR window_name LIKE '%boosteroid%' ORDER BY created_at DESC LIMIT 20;\"","depth":27,"bounds":{"left":0.20820312,"top":0.017361112,"width":0.26835936,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Once you see the exact","depth":24,"bounds":{"left":0.19257812,"top":0.017361112,"width":0.06757812,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"window_name","depth":25,"bounds":{"left":0.26171875,"top":0.017361112,"width":0.0375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"value, add that string to","depth":24,"bounds":{"left":0.30078125,"top":0.017361112,"width":0.0703125,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"ignored_windows","depth":25,"bounds":{"left":0.37265626,"top":0.017361112,"width":0.05078125,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":". For example it might need to be:","depth":24,"bounds":{"left":0.19257812,"top":0.017361112,"width":0.27148438,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"bounds":{"left":0.4609375,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"json","depth":25,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.009375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"\"Boosteroid - Play PC Games on any device\"","depth":26,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.13789062,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"A few other things worth knowing:","depth":24,"bounds":{"left":0.19257812,"top":0.017361112,"width":0.10039063,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"The match is","depth":25,"bounds":{"left":0.20507812,"top":0.017361112,"width":0.03828125,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"substring-based","depth":26,"bounds":{"left":0.24296875,"top":0.017361112,"width":0.04921875,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":", so adding","depth":25,"bounds":{"left":0.29179686,"top":0.017361112,"width":0.032421876,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"\"Boosteroid\"","depth":26,"bounds":{"left":0.32578126,"top":0.017361112,"width":0.040625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"","depth":25,"bounds":{"left":0.36796874,"top":0.017361112,"width":0.001953125,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"should","depth":26,"bounds":{"left":0.36953124,"top":0.017361112,"width":0.019921875,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"work if the window name contains that string — but only if Screenpipe is checking","depth":25,"bounds":{"left":0.20507812,"top":0.017361112,"width":0.25859374,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"window_name","depth":26,"bounds":{"left":0.36992186,"top":0.017361112,"width":0.0375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"and not","depth":25,"bounds":{"left":0.40898436,"top":0.017361112,"width":0.023828125,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"app_name","depth":26,"bounds":{"left":0.20703125,"top":0.017361112,"width":0.02734375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":". Safari's","depth":25,"bounds":{"left":0.2359375,"top":0.017361112,"width":0.025390625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"app_name","depth":26,"bounds":{"left":0.26289064,"top":0.017361112,"width":0.02734375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"is just","depth":25,"bounds":{"left":0.29179686,"top":0.017361112,"width":0.01953125,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"\"Safari\"","depth":26,"bounds":{"left":0.31289062,"top":0.017361112,"width":0.02734375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":", so the filter on","depth":25,"bounds":{"left":0.34179688,"top":0.017361112,"width":0.0453125,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"\"Boosteroid\"","depth":26,"bounds":{"left":0.38867188,"top":0.017361112,"width":0.041015625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"may not be hitting anything.","depth":25,"bounds":{"left":0.20507812,"top":0.017361112,"width":0.2511719,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"You could also try adding","depth":25,"bounds":{"left":0.20507812,"top":0.017361112,"width":0.07421875,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"\"Safari\"","depth":26,"bounds":{"left":0.28085938,"top":0.017361112,"width":0.02734375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"to","depth":25,"bounds":{"left":0.30976564,"top":0.017361112,"width":0.00859375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"ignored_windows","depth":26,"bounds":{"left":0.31992188,"top":0.017361112,"width":0.051171876,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"entirely, though that's a blunt instrument if you use Safari for other things.","depth":25,"bounds":{"left":0.20507812,"top":0.017361112,"width":0.25351563,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"After editing","depth":25,"bounds":{"left":0.20507812,"top":0.017361112,"width":0.038671874,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"config.json","depth":26,"bounds":{"left":0.2453125,"top":0.017361112,"width":0.0375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":", make sure to","depth":25,"bounds":{"left":0.284375,"top":0.017361112,"width":0.041015625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"restart Screenpipe","depth":26,"bounds":{"left":0.325,"top":0.017361112,"width":0.055859376,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"fully for the change to take effect.","depth":25,"bounds":{"left":0.20507812,"top":0.017361112,"width":0.25390625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"The query above will tell you exactly what string to add.","depth":24,"bounds":{"left":0.19257812,"top":0.017361112,"width":0.15976563,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Copy","depth":23,"bounds":{"left":0.18945312,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give positive feedback","depth":23,"bounds":{"left":0.20195313,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give negative feedback","depth":23,"bounds":{"left":0.21445313,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Retry","depth":23,"bounds":{"left":0.22695312,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite \"SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;\"\nError: in prepare, no such column: created_at\n ari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;\n error here ---^\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite \"SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;\"\nError: in prepare, no such column: created_at\n ari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;\n error here ---^\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $","depth":25,"bounds":{"left":0.23867187,"top":0.017361112,"width":0.2234375,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Show more","depth":23,"bounds":{"left":0.23867187,"top":0.017361112,"width":0.17421874,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"15:50","depth":23,"bounds":{"left":0.425,"top":0.017361112,"width":0.011328125,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Retry","depth":23,"bounds":{"left":0.43945312,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Edit","depth":23,"bounds":{"left":0.4519531,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Copy","depth":23,"bounds":{"left":0.46445313,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"The","depth":24,"bounds":{"left":0.19257812,"top":0.017361112,"width":0.012890625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"ocr_text","depth":25,"bounds":{"left":0.20703125,"top":0.017361112,"width":0.02734375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"table uses a different timestamp column. Try:","depth":24,"bounds":{"left":0.2359375,"top":0.017361112,"width":0.13164063,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"bounds":{"left":0.4609375,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.0109375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"sqlite3 ~/.screenpipe/db.sqlite","depth":27,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.10507812,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"\"SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;\"","depth":27,"bounds":{"left":0.3,"top":0.017361112,"width":0.1765625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"If you're unsure of the column names, check the schema first:","depth":24,"bounds":{"left":0.19257812,"top":0.017361112,"width":0.17578125,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"bounds":{"left":0.4609375,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.0109375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"sqlite3 ~/.screenpipe/db.sqlite","depth":26,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.10507812,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"\".schema ocr_text\"","depth":26,"bounds":{"left":0.3,"top":0.017361112,"width":0.059765626,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Copy","depth":23,"bounds":{"left":0.18945312,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give positive feedback","depth":23,"bounds":{"left":0.20195313,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give negative feedback","depth":23,"bounds":{"left":0.21445313,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Retry","depth":23,"bounds":{"left":0.22695312,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite \"SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;\"\nError: in prepare, no such column: timestamp\n ari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;\n error here ---^\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite \".schema ocr_text\"\nCREATE TABLE IF NOT EXISTS \"ocr_text\" (\n frame_id INTEGER NOT NULL,\n text TEXT NOT NULL,\n text_json TEXT,\n app_name TEXT NOT NULL DEFAULT '',\n ocr_engine TEXT NOT NULL DEFAULT 'unknown'\n, window_name TEXT, focused BOOLEAN DEFAULT FALSE, text_length INTEGER, sync_id TEXT, synced_at DATETIME);\nCREATE INDEX idx_ocr_text_frame_id ON ocr_text(frame_id);\nCREATE INDEX idx_ocr_text_frame_app_window ON ocr_text(frame_id, app_name, window_name);\nCREATE INDEX idx_ocr_text_length ON ocr_text (text_length);\nCREATE INDEX idx_ocr_text_sync_id ON ocr_text(sync_id) WHERE sync_id IS NOT NULL;\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $","depth":25,"bounds":{"left":0.23867187,"top":0.017361112,"width":0.23046875,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Show more","depth":23,"bounds":{"left":0.23867187,"top":0.017361112,"width":0.17421874,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"16:10","depth":23,"bounds":{"left":0.42617187,"top":0.017361112,"width":0.01015625,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Retry","depth":23,"bounds":{"left":0.43945312,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Edit","depth":23,"bounds":{"left":0.4519531,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Copy","depth":23,"bounds":{"left":0.46445313,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"No timestamp on","depth":24,"bounds":{"left":0.19257812,"top":0.017361112,"width":0.0515625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"ocr_text","depth":25,"bounds":{"left":0.24570313,"top":0.017361112,"width":0.02734375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"directly — it joins via","depth":24,"bounds":{"left":0.2746094,"top":0.017361112,"width":0.06289063,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"frame_id","depth":25,"bounds":{"left":0.3390625,"top":0.017361112,"width":0.027734375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"to the","depth":24,"bounds":{"left":0.3683594,"top":0.017361112,"width":0.019140625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"frames","depth":25,"bounds":{"left":0.3890625,"top":0.017361112,"width":0.020703126,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"table. Try:","depth":24,"bounds":{"left":0.41132814,"top":0.017361112,"width":0.03046875,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"bounds":{"left":0.4609375,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.0109375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"sqlite3 ~/.screenpipe/db.sqlite","depth":27,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.10507812,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"\"SELECT DISTINCT o.app_name, o.window_name FROM ocr_text o JOIN frames f ON o.frame_id = f.id WHERE o.app_name LIKE '%Safari%' OR o.window_name LIKE '%Boosteroid%' ORDER BY f.id DESC LIMIT 20;\"","depth":27,"bounds":{"left":0.3,"top":0.017361112,"width":0.1765625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Or just get distinct values without ordering by time:","depth":24,"bounds":{"left":0.19257812,"top":0.017361112,"width":0.14804688,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"bounds":{"left":0.4609375,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.0109375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"sqlite3 ~/.screenpipe/db.sqlite","depth":27,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.10507812,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"\"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' LIMIT 20;\"","depth":27,"bounds":{"left":0.3,"top":0.017361112,"width":0.1765625,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Copy","depth":23,"bounds":{"left":0.18945312,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give positive feedback","depth":23,"bounds":{"left":0.20195313,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give negative feedback","depth":23,"bounds":{"left":0.21445313,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Retry","depth":23,"bounds":{"left":0.22695312,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite \"SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;\"\nError: in prepare, no such column: timestamp\n ari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;\n error here ---^\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite \".schema ocr_text\"\nCREATE TABLE IF NOT EXISTS \"ocr_text\" (\n frame_id INTEGER NOT NULL,\n text TEXT NOT NULL,\n text_json TEXT,\n app_name TEXT NOT NULL DEFAULT '',\n ocr_engine TEXT NOT NULL DEFAULT 'unknown'\n, window_name TEXT, focused BOOLEAN DEFAULT FALSE, text_length INTEGER, sync_id TEXT, synced_at DATETIME);\nCREATE INDEX idx_ocr_text_frame_id ON ocr_text(frame_id);\nCREATE INDEX idx_ocr_text_frame_app_window ON ocr_text(frame_id, app_name, window_name);\nCREATE INDEX idx_ocr_text_length ON ocr_text (text_length);\nCREATE INDEX idx_ocr_text_sync_id ON ocr_text(sync_id) WHERE sync_id IS NOT NULL;\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $","depth":25,"bounds":{"left":0.23867187,"top":0.017361112,"width":0.23046875,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Show more","depth":23,"bounds":{"left":0.23867187,"top":0.017361112,"width":0.17421874,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"16:27","depth":23,"bounds":{"left":0.42539063,"top":0.017361112,"width":0.0109375,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Retry","depth":23,"bounds":{"left":0.43945312,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Edit","depth":23,"bounds":{"left":0.4519531,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Copy","depth":23,"bounds":{"left":0.46445313,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Run the second query (no ORDER BY):","depth":24,"bounds":{"left":0.19257812,"top":0.017361112,"width":0.11171875,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"bounds":{"left":0.4609375,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.0109375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"sqlite3 ~/.screenpipe/db.sqlite","depth":27,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.10507812,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"\"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' LIMIT 20;\"","depth":27,"bounds":{"left":0.3,"top":0.017361112,"width":0.1765625,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Copy","depth":23,"bounds":{"left":0.18945312,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give positive feedback","depth":23,"bounds":{"left":0.20195313,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give negative feedback","depth":23,"bounds":{"left":0.21445313,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Retry","depth":23,"bounds":{"left":0.22695312,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite \"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' LIMIT 20;\"\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $","depth":25,"bounds":{"left":0.23867187,"top":0.017361112,"width":0.2234375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"16:36","depth":23,"bounds":{"left":0.42539063,"top":0.017361112,"width":0.0109375,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Retry","depth":23,"bounds":{"left":0.43945312,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Edit","depth":23,"bounds":{"left":0.4519531,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Copy","depth":23,"bounds":{"left":0.46445313,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Empty result — Boosteroid isn't being captured via Safari's window name. It might be running in a different browser or as a PWA. Let's cast a wider net:","depth":24,"bounds":{"left":0.19257812,"top":0.017361112,"width":0.27070314,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"bounds":{"left":0.4609375,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.0109375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"sqlite3 ~/.screenpipe/db.sqlite","depth":27,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.10507812,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"\"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE window_name LIKE '%Boosteroid%' OR window_name LIKE '%boosteroid%' OR app_name LIKE '%Boosteroid%' OR app_name LIKE '%Chrome%' OR app_name LIKE '%Firefox%' OR app_name LIKE '%Arc%' OR app_name LIKE '%Brave%' OR app_name LIKE '%Vivaldi%' LIMIT 50;\"","depth":27,"bounds":{"left":0.3,"top":0.017361112,"width":0.1765625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"And separately, check what apps are being recorded at all:","depth":24,"bounds":{"left":0.19257812,"top":0.017361112,"width":0.16601562,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"bounds":{"left":0.4609375,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.0109375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"sqlite3 ~/.screenpipe/db.sqlite","depth":26,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.10507812,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"\"SELECT DISTINCT app_name FROM ocr_text LIMIT 50;\"","depth":26,"bounds":{"left":0.3,"top":0.017361112,"width":0.16445312,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"That second query will show you exactly what Screenpipe is capturing, and you can spot which app Boosteroid is running under.","depth":24,"bounds":{"left":0.19257812,"top":0.017361112,"width":0.25351563,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Copy","depth":23,"bounds":{"left":0.18945312,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give positive feedback","depth":23,"bounds":{"left":0.20195313,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give negative feedback","depth":23,"bounds":{"left":0.21445313,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Retry","depth":23,"bounds":{"left":0.22695312,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite \"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE window_name LIKE '%Boosteroid%' OR window_name LIKE '%boosteroid%' OR app_name LIKE '%Boosteroid%' OR app_name LIKE '%Chrome%' OR app_name LIKE '%Firefox%' OR app_name LIKE '%Arc%' OR app_name LIKE '%Brave%' OR app_name LIKE '%Vivaldi%' LIMIT 50;\"\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite \"SELECT DISTINCT app_name FROM ocr_text LIMIT 50;\"\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $","depth":25,"bounds":{"left":0.23867187,"top":0.017361112,"width":0.23085937,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"16:36","depth":23,"bounds":{"left":0.42539063,"top":0.017361112,"width":0.0109375,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Retry","depth":23,"bounds":{"left":0.43945312,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Edit","depth":23,"bounds":{"left":0.4519531,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Copy","depth":23,"bounds":{"left":0.46445313,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"The DB is completely empty — no OCR data at all. This means Screenpipe isn't recording anything currently, or the DB you're querying is not the active one.","depth":24,"bounds":{"left":0.19257812,"top":0.017361112,"width":0.25507814,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"Check a few things:","depth":24,"bounds":{"left":0.19257812,"top":0.017361112,"width":0.056640625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"1. Is Screenpipe actually running?","depth":25,"bounds":{"left":0.19257812,"top":0.017361112,"width":0.10078125,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"bounds":{"left":0.4609375,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.0109375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"pgrep -la screenpipe","depth":26,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.065625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"2. Is this the right DB?","depth":25,"bounds":{"left":0.19257812,"top":0.017361112,"width":0.06523438,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"bounds":{"left":0.4609375,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.0109375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"ls","depth":26,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.006640625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"-lh ~/.screenpipe/db.sqlite","depth":26,"bounds":{"left":0.2015625,"top":0.017361112,"width":0.09257813,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"# Check if there are other DB files","depth":26,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.11484375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"role_description":"text"},{"role":"AXStaticText","text":"find","depth":26,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.01328125,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"~ -name","depth":26,"bounds":{"left":0.20820312,"top":0.017361112,"width":0.030078124,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"\"db.sqlite\"","depth":26,"bounds":{"left":0.23789063,"top":0.017361112,"width":0.036328126,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.27382812,"top":0.017361112,"width":0.00390625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"2","depth":26,"bounds":{"left":0.27734375,"top":0.017361112,"width":0.003515625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":">","depth":26,"bounds":{"left":0.28046876,"top":0.017361112,"width":0.003515625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"/dev/null","depth":26,"bounds":{"left":0.28359374,"top":0.017361112,"width":0.030078124,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"3. Check DB size — if it's tiny, it's empty:","depth":25,"bounds":{"left":0.19257812,"top":0.017361112,"width":0.11875,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"bounds":{"left":0.4609375,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.0109375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"du","depth":26,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.006640625,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"-sh ~/.screenpipe/db.sqlite","depth":26,"bounds":{"left":0.2015625,"top":0.017361112,"width":0.09257813,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"4. Check frames table too:","depth":25,"bounds":{"left":0.19257812,"top":0.017361112,"width":0.07695313,"height":0.00069444446},"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":26,"bounds":{"left":0.4609375,"top":0.017361112,"width":0.0125,"height":0.00069444446},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"bash","depth":25,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.0109375,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"sqlite3 ~/.screenpipe/db.sqlite","depth":26,"bounds":{"left":0.1953125,"top":0.017361112,"width":0.10507812,"height":0.00069444446},"role_description":"text"},{"role":"AXStaticText","text":"\"SELECT COUNT(*) FROM frames;\"","depth":26,"bounds":{"left":0.3,"top":0.017361112,"width":0.09882812,"height":0.00069444446},"role_description":"text"}]...
|
-430807021609346284
|
-8544006821181072974
|
app_switch
|
accessibility
|
NULL
|
Skip to content
Skip to content
Click to collapse
Skip to content
Skip to content
Click to collapse
⌘B
Drag to resize
Collapse sidebar
Search
Chat
Cowork
Code
New chat ⌘N
New chat
⌘N
Projects
Customize
Artifacts
Pinned
Bulgarian citizenship application process for EU residents
More options for Bulgarian citizenship application process for EU residents
Dawarich location tracking project
More options for Dawarich location tracking project
Recents
View all
Boosteroid still recording despite ignored windows setting
More options for Boosteroid still recording despite ignored windows setting
Missing JavaScript promise in authorization response
More options for Missing JavaScript promise in authorization response
Linux SQLite UI for NAS
More options for Linux SQLite UI for NAS
Claude API 500 internal server error
More options for Claude API 500 internal server error
Screenpipe query capabilities and usage
More options for Screenpipe query capabilities and usage
eGPU compatibility with Mac mini and Studio
More options for eGPU compatibility with Mac mini and Studio
Understanding OpenRouter API gateway
More options for Understanding OpenRouter API gateway
Screenpipe 14-day retention explained
More options for Screenpipe 14-day retention explained
Reddit homepage feed overview
More options for Reddit homepage feed overview
Docker container not visible in console
More options for Docker container not visible in console
DIA browser RAM configuration
More options for DIA browser RAM configuration
Screenpipe company background
More options for Screenpipe company background
DSK Bank Bulgaria secure investing explained
More options for DSK Bank Bulgaria secure investing explained
Building a comprehensive personal homelab system
More options for Building a comprehensive personal homelab system
Importing Google Timeline data to Owntracks
More options for Importing Google Timeline data to Owntracks
Chromecast remote volume buttons not working
More options for Chromecast remote volume buttons not working
Screenpipe free version guide
More options for Screenpipe free version guide
Self-hosted LLM alternatives to Claude Sonnet
More options for Self-hosted LLM alternatives to Claude Sonnet
Screenpipe database query setup
More options for Screenpipe database query setup
Running screenpipe for free
More options for Running screenpipe for free
Updated to 1.3109.0
Relaunch to apply
Relaunch
Lukas Kovalik
Lukas Kovalik
Get apps and extensions
Boosteroid still recording despite ignored windows setting
Boosteroid still recording despite ignored windows setting
More options for Boosteroid still recording despite ignored windows setting
Close
Share chat
Screenpipe I have set Boosteroid to ignored windows but it is still being recorded. lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ cat config.json
{
"disable_audio": true,
"monitor_ids": ["Display 1_1440x900_0,0", "Display 2_3008x1253_-813,-1253"],
"ignored_windows": [
"1Password",
"Keychain Access",
"Bitwarden",
"System Preferences",
"System Settings",
"zoom.us",
"HBO Max",
"Screenpipe Dashboard",
"Boosteroid"
]
}
Show more
15:40
Retry
Edit
Copy
The
ignored_windows
filter in Screenpipe matches against window
titles
, not app names. The issue is likely that Boosteroid runs in a browser (Safari), so the window title is something like
"Boosteroid - Play PC Games on any device"
or the game title — not just "Boosteroid".
To find the exact window title being captured, query the DB:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;"
Or if you use the
spq
alias:
Copy to clipboard
bash
spq
"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE window_name LIKE '%Boosteroid%' OR window_name LIKE '%boosteroid%' ORDER BY created_at DESC LIMIT 20;"
Once you see the exact
window_name
value, add that string to
ignored_windows
. For example it might need to be:
Copy to clipboard
json
"Boosteroid - Play PC Games on any device"
A few other things worth knowing:
The match is
substring-based
, so adding
"Boosteroid"
should
work if the window name contains that string — but only if Screenpipe is checking
window_name
and not
app_name
. Safari's
app_name
is just
"Safari"
, so the filter on
"Boosteroid"
may not be hitting anything.
You could also try adding
"Safari"
to
ignored_windows
entirely, though that's a blunt instrument if you use Safari for other things.
After editing
config.json
, make sure to
restart Screenpipe
fully for the change to take effect.
The query above will tell you exactly what string to add.
Copy
Give positive feedback
Give negative feedback
Retry
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;"
Error: in prepare, no such column: created_at
ari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;"
Error: in prepare, no such column: created_at
ari%' OR window_name LIKE '%Boosteroid%' ORDER BY created_at DESC LIMIT 20;
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $
Show more
15:50
Retry
Edit
Copy
The
ocr_text
table uses a different timestamp column. Try:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;"
If you're unsure of the column names, check the schema first:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
".schema ocr_text"
Copy
Give positive feedback
Give negative feedback
Retry
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;"
Error: in prepare, no such column: timestamp
ari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite ".schema ocr_text"
CREATE TABLE IF NOT EXISTS "ocr_text" (
frame_id INTEGER NOT NULL,
text TEXT NOT NULL,
text_json TEXT,
app_name TEXT NOT NULL DEFAULT '',
ocr_engine TEXT NOT NULL DEFAULT 'unknown'
, window_name TEXT, focused BOOLEAN DEFAULT FALSE, text_length INTEGER, sync_id TEXT, synced_at DATETIME);
CREATE INDEX idx_ocr_text_frame_id ON ocr_text(frame_id);
CREATE INDEX idx_ocr_text_frame_app_window ON ocr_text(frame_id, app_name, window_name);
CREATE INDEX idx_ocr_text_length ON ocr_text (text_length);
CREATE INDEX idx_ocr_text_sync_id ON ocr_text(sync_id) WHERE sync_id IS NOT NULL;
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $
Show more
16:10
Retry
Edit
Copy
No timestamp on
ocr_text
directly — it joins via
frame_id
to the
frames
table. Try:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT DISTINCT o.app_name, o.window_name FROM ocr_text o JOIN frames f ON o.frame_id = f.id WHERE o.app_name LIKE '%Safari%' OR o.window_name LIKE '%Boosteroid%' ORDER BY f.id DESC LIMIT 20;"
Or just get distinct values without ordering by time:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' LIMIT 20;"
Copy
Give positive feedback
Give negative feedback
Retry
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;"
Error: in prepare, no such column: timestamp
ari%' OR window_name LIKE '%Boosteroid%' ORDER BY timestamp DESC LIMIT 20;
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite ".schema ocr_text"
CREATE TABLE IF NOT EXISTS "ocr_text" (
frame_id INTEGER NOT NULL,
text TEXT NOT NULL,
text_json TEXT,
app_name TEXT NOT NULL DEFAULT '',
ocr_engine TEXT NOT NULL DEFAULT 'unknown'
, window_name TEXT, focused BOOLEAN DEFAULT FALSE, text_length INTEGER, sync_id TEXT, synced_at DATETIME);
CREATE INDEX idx_ocr_text_frame_id ON ocr_text(frame_id);
CREATE INDEX idx_ocr_text_frame_app_window ON ocr_text(frame_id, app_name, window_name);
CREATE INDEX idx_ocr_text_length ON ocr_text (text_length);
CREATE INDEX idx_ocr_text_sync_id ON ocr_text(sync_id) WHERE sync_id IS NOT NULL;
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $
Show more
16:27
Retry
Edit
Copy
Run the second query (no ORDER BY):
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' LIMIT 20;"
Copy
Give positive feedback
Give negative feedback
Retry
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT DISTINCT app_name, window_name FROM ocr_text WHERE app_name LIKE '%Safari%' OR window_name LIKE '%Boosteroid%' LIMIT 20;"
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $
16:36
Retry
Edit
Copy
Empty result — Boosteroid isn't being captured via Safari's window name. It might be running in a different browser or as a PWA. Let's cast a wider net:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT DISTINCT app_name, window_name FROM ocr_text WHERE window_name LIKE '%Boosteroid%' OR window_name LIKE '%boosteroid%' OR app_name LIKE '%Boosteroid%' OR app_name LIKE '%Chrome%' OR app_name LIKE '%Firefox%' OR app_name LIKE '%Arc%' OR app_name LIKE '%Brave%' OR app_name LIKE '%Vivaldi%' LIMIT 50;"
And separately, check what apps are being recorded at all:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT DISTINCT app_name FROM ocr_text LIMIT 50;"
That second query will show you exactly what Screenpipe is capturing, and you can spot which app Boosteroid is running under.
Copy
Give positive feedback
Give negative feedback
Retry
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT DISTINCT app_name, window_name FROM ocr_text WHERE window_name LIKE '%Boosteroid%' OR window_name LIKE '%boosteroid%' OR app_name LIKE '%Boosteroid%' OR app_name LIKE '%Chrome%' OR app_name LIKE '%Firefox%' OR app_name LIKE '%Arc%' OR app_name LIKE '%Brave%' OR app_name LIKE '%Vivaldi%' LIMIT 50;"
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite "SELECT DISTINCT app_name FROM ocr_text LIMIT 50;"
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $
16:36
Retry
Edit
Copy
The DB is completely empty — no OCR data at all. This means Screenpipe isn't recording anything currently, or the DB you're querying is not the active one.
Check a few things:
1. Is Screenpipe actually running?
Copy to clipboard
bash
pgrep -la screenpipe
2. Is this the right DB?
Copy to clipboard
bash
ls
-lh ~/.screenpipe/db.sqlite
# Check if there are other DB files
find
~ -name
"db.sqlite"
2
>
/dev/null
3. Check DB size — if it's tiny, it's empty:
Copy to clipboard
bash
du
-sh ~/.screenpipe/db.sqlite
4. Check frames table too:
Copy to clipboard
bash
sqlite3 ~/.screenpipe/db.sqlite
"SELECT COUNT(*) FROM frames;"...
|
NULL
|
|
50868
|
1093
|
42
|
2026-04-17T15:33:45.261806+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776440025261_m1.jpg...
|
iTerm2
|
-zsh
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
iTerm2ShellEditViewSessionScriptsProfilesWindowHel iTerm2ShellEditViewSessionScriptsProfilesWindowHelp-zshInh100% <478Fri 17 Apr 18:33:44T*1* Review screenp...• X6ec2-user@ip-10-...• ₴7ec2-user@ip-10-...• 88DOCKER- ₴81DEV (docker)APP (-zsh)|X3-zsh-zsh• ₴53.6G/Users/lukas/.screenpipe/db.sqliteINFO: archive.dbdoes not exist yet - will be createdon firstsync[2026-04-17 18:09:41]ISSSSsssssssssssss=:====[2026-04-17 18:09:41]Screenpipe sync starting for: 2026-04-14[2026-04-17 18:09:41]=====[+00m00s] • Preflight checksSource DB:NAS mount:Archive DB:OK(3.6G)OK/Volumes/Test/screenpipewill be created[+00m00s] • Counting source rows for 2026-04-14frames:elements:ui_events:ocr_text:meetings:10733695969105428206[+00m00s] • Initialising tables, indexes, FTScreating tablescreating indexescreating FTS tables• 0mb2s• Om03s• 0m01s[+00m06s] • Syncing data for 2026-04-14video_chunks• 0m01sframes (10733 rows)• 8m46socr_text (8206 rows)• 9m50sui_events (10542 rows)• Om06selements (695969 rows):. zsh: terminated~/.screenpipe/screenpipe_sync.sh 2026-04-14lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-04-14OK: NASmountedOK:Source DBexists3.6G/Users/lukas/.screenpipe/db.sqliteOK: archive.db exists687M/Volumes/Test/screenpipe/archive.db17 tables[2026-04-17 18:33:18][2026-04-17 18:33:18]Screenpipe sync starting for: 2026-04-14[2026-04-17 18:33:18]:====|=====[+00m00s] • Preflight checksSource DB:OKNAS mount:OK(3.6G)/Volumes/Test/screenpipe[2026-04-17 18:33:19] Date 2026-04-14 already has 10733 frames in archive - skippingukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ U...
|
NULL
|
8087547896453206730
|
NULL
|
app_switch
|
ocr
|
NULL
|
iTerm2ShellEditViewSessionScriptsProfilesWindowHel iTerm2ShellEditViewSessionScriptsProfilesWindowHelp-zshInh100% <478Fri 17 Apr 18:33:44T*1* Review screenp...• X6ec2-user@ip-10-...• ₴7ec2-user@ip-10-...• 88DOCKER- ₴81DEV (docker)APP (-zsh)|X3-zsh-zsh• ₴53.6G/Users/lukas/.screenpipe/db.sqliteINFO: archive.dbdoes not exist yet - will be createdon firstsync[2026-04-17 18:09:41]ISSSSsssssssssssss=:====[2026-04-17 18:09:41]Screenpipe sync starting for: 2026-04-14[2026-04-17 18:09:41]=====[+00m00s] • Preflight checksSource DB:NAS mount:Archive DB:OK(3.6G)OK/Volumes/Test/screenpipewill be created[+00m00s] • Counting source rows for 2026-04-14frames:elements:ui_events:ocr_text:meetings:10733695969105428206[+00m00s] • Initialising tables, indexes, FTScreating tablescreating indexescreating FTS tables• 0mb2s• Om03s• 0m01s[+00m06s] • Syncing data for 2026-04-14video_chunks• 0m01sframes (10733 rows)• 8m46socr_text (8206 rows)• 9m50sui_events (10542 rows)• Om06selements (695969 rows):. zsh: terminated~/.screenpipe/screenpipe_sync.sh 2026-04-14lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-04-14OK: NASmountedOK:Source DBexists3.6G/Users/lukas/.screenpipe/db.sqliteOK: archive.db exists687M/Volumes/Test/screenpipe/archive.db17 tables[2026-04-17 18:33:18][2026-04-17 18:33:18]Screenpipe sync starting for: 2026-04-14[2026-04-17 18:33:18]:====|=====[+00m00s] • Preflight checksSource DB:OKNAS mount:OK(3.6G)/Volumes/Test/screenpipe[2026-04-17 18:33:19] Date 2026-04-14 already has 10733 frames in archive - skippingukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ U...
|
NULL
|