|
45141
|
1621
|
1
|
2026-05-14T14:21:46.455249+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768506455_m1.jpg...
|
Firefox
|
Jiminny — Work
|
1
|
app.staging.jiminny.com/dashboard
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Unnamed Group
Platform Sprint 4 Q2 - Platform Team Unnamed Group
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
Usage | Windsurf
Close tab
Unnamed Group
[SRD-6848] Sidekick SMS issue - Jira
Close tab
[JY-20891] Sidekick SMS issue - Jira
Close tab
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
Close tab
Unnamed Group
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
[JY-20903] Recorded call does not appear on the dashboard - Jira
Close tab
Jiminny
Close tab
Jiminny
Close tab
Jiminny
Close tab
Unnamed Group
[SRD-6853] Moxso - Potential deal stages bug - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Useful commands - Engineering - Confluence
Close tab
Unnamed Group
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
Close tab
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
Close tab
Jiminny
Close tab
New Tab
Close tab
New Tab
Close tab
CloudWatch | us-east-2
Close tab
Companies | All companies
Close tab
Developers | HubSpot
Close tab
Jiminny
Close tab
Feed — jiminny — Sentry
Close tab
Inbox (1,684) - [EMAIL] - Jiminny Mail
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
JY-20493-smart-instant-nudge-pre-filtering ■ 885799
75
75
My Recordings
My Recordings
Everyone's Recordings
Everyone's Recordings
No Recordings
Schedule
Schedule
Invite Notetaker
This Week
This Week
Everyone's Schedule
Everyone's Schedule
Unknown Customer
Backend Chapter
Tomorrow, 10:30 AM
Trending this month
Trending this month
Sort by Sort by: Most played
Sort by
Sort by:
Most played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
2
times played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
1
times played
Unknown Customer
Processing tickets review
Processing tickets review
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Refinement - Processing
Refinement - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Notetaker added by Todor Stamatov
Notetaker added by Todor Stamatov
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Todor Stamatov at Drun Drun Chiki Chiki Bam Bam
Test 42
Test 42
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Unknown Customer
Notetaker added by Mihail Mihaylov
Notetaker added by Mihail Mihaylov
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Kara / James
Kara / James
0
times played
Unknown Customer
Jiminny SF App
Jiminny SF App
0
times played
Unknown Customer
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Laura / James
Laura / James
0
times played
Live Feed
Live Feed
Veselin Kulov
listened to call
7 May, 3:47 PM
activity
with
unknown customer
Held:
7 May, 10:37 AM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
7 May, 11:12 AM
activity
with
unknown customer
Held:
7 May, 10:37 AM...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Usage | Windsurf","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6848] Sidekick SMS issue - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20891] Sidekick SMS issue - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20903] Recorded call does not appear on the dashboard - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.033680554,"height":0.045555554},"on_screen":false,"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.0,"width":0.010416667,"height":0.016666668},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.008680556,"top":0.0,"width":0.016666668,"height":0.026666667},"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6853] Moxso - Potential deal stages bug - Jira","depth":4,"bounds":{"left":0.0,"top":0.017222222,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.017222222,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.06277778,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.06277778,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.108333334,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.108333334,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Useful commands - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.1538889,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.1538889,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.008680556,"top":0.20666666,"width":0.016666668,"height":0.026666667},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.23944445,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.23944445,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira","depth":4,"bounds":{"left":0.0,"top":0.285,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.285,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.33055556,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.33055556,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.37611112,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.37611112,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.42166665,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.42166665,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0,"top":0.4672222,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.4672222,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Companies | All companies","depth":4,"bounds":{"left":0.0,"top":0.5127778,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.5127778,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Developers | HubSpot","depth":4,"bounds":{"left":0.0,"top":0.55833334,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.55833334,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.60388887,"width":0.033680554,"height":0.045555554},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.60388887,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.0,"top":0.64944446,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.64944446,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Inbox (1,684) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.695,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.695,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.005902778,"top":0.74666667,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.0,"top":0.7977778,"width":0.033680554,"height":0.043333333},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.0,"top":0.8411111,"width":0.033680554,"height":0.038333334},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.0,"top":0.8794444,"width":0.033680554,"height":0.03888889},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.0,"top":0.91833335,"width":0.033680554,"height":0.038333334},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0,"top":0.95666665,"width":0.033680554,"height":0.043333333},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20493-smart-instant-nudge-pre-filtering ■ 885799","depth":9,"bounds":{"left":0.035069443,"top":0.98055553,"width":0.21875,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"75","depth":12,"bounds":{"left":0.03923611,"top":0.88,"width":0.033333335,"height":0.04888889},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"75","depth":14,"bounds":{"left":0.05659722,"top":0.885,"width":0.009722223,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"My Recordings","depth":14,"bounds":{"left":0.14201389,"top":0.1,"width":0.08055556,"height":0.07333333},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"My Recordings","depth":15,"bounds":{"left":0.14618056,"top":0.1261111,"width":0.072222225,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Everyone's Recordings","depth":14,"bounds":{"left":0.22256945,"top":0.1,"width":0.11736111,"height":0.07333333},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Everyone's Recordings","depth":15,"bounds":{"left":0.22673611,"top":0.1261111,"width":0.10902778,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No Recordings","depth":17,"bounds":{"left":0.209375,"top":0.36055556,"width":0.063541666,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Schedule","depth":13,"bounds":{"left":0.396875,"top":0.36333334,"width":0.050347224,"height":0.037777778},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Schedule","depth":14,"bounds":{"left":0.396875,"top":0.37,"width":0.050347224,"height":0.024444444},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Invite Notetaker","depth":14,"bounds":{"left":0.5888889,"top":0.36,"width":0.09236111,"height":0.04},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXComboBox","text":"This Week","depth":14,"bounds":{"left":0.40729168,"top":0.4288889,"width":0.12847222,"height":0.04111111},"on_screen":true,"value":"This Week","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"This Week","depth":17,"bounds":{"left":0.41493055,"top":0.44,"width":0.045833334,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Everyone's Schedule","depth":14,"bounds":{"left":0.54270834,"top":0.4288889,"width":0.128125,"height":0.04111111},"on_screen":true,"value":"Everyone's Schedule","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Everyone's Schedule","depth":17,"bounds":{"left":0.5503472,"top":0.44,"width":0.088194445,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"bounds":{"left":0.44895834,"top":0.51055557,"width":0.08090278,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Backend Chapter","depth":17,"bounds":{"left":0.44895834,"top":0.5377778,"width":0.06979167,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Tomorrow, 10:30 AM","depth":16,"bounds":{"left":0.44895834,"top":0.565,"width":0.08576389,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Trending this month","depth":13,"bounds":{"left":0.40729168,"top":0.123333335,"width":0.096875,"height":0.026666667},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Trending this month","depth":14,"bounds":{"left":0.40729168,"top":0.1261111,"width":0.096875,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Sort by Sort by: Most played","depth":13,"bounds":{"left":0.55277777,"top":0.116111115,"width":0.11805555,"height":0.04111111},"on_screen":true,"value":"Sort by Sort by: Most played","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Sort by","depth":14,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sort by:","depth":15,"bounds":{"left":0.56041664,"top":0.12722223,"width":0.035416666,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Most played","depth":15,"bounds":{"left":0.59583336,"top":0.12722223,"width":0.054166667,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"bounds":{"left":0.48993057,"top":0.23333333,"width":0.084375,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Veselin Kulov","depth":15,"bounds":{"left":0.45520833,"top":0.25777778,"width":0.16770834,"height":0.022222223},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Veselin Kulov","depth":16,"bounds":{"left":0.47604167,"top":0.26055557,"width":0.12604167,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":16,"bounds":{"left":0.5420139,"top":0.28611112,"width":0.008680556,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"bounds":{"left":0.51944447,"top":0.30666667,"width":0.03888889,"height":0.013888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"bounds":{"left":0.7739583,"top":0.23333333,"width":0.084375,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Veselin Kulov","depth":15,"bounds":{"left":0.7392361,"top":0.25777778,"width":0.16770834,"height":0.022222223},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Veselin Kulov","depth":16,"bounds":{"left":0.76006943,"top":0.26055557,"width":0.12604167,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":16,"bounds":{"left":0.82604164,"top":0.28611112,"width":0.008680556,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"bounds":{"left":0.8034722,"top":0.30666667,"width":0.03888889,"height":0.013888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"bounds":{"left":1.0,"top":0.23333333,"width":-0.05798614,"height":0.017777778},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Processing tickets review","depth":15,"bounds":{"left":1.0,"top":0.25777778,"width":-0.03923607,"height":0.022222223},"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Processing tickets review","depth":16,"bounds":{"left":1.0,"top":0.26055557,"width":-0.06006944,"height":0.016666668},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"bounds":{"left":1.0,"top":0.30666667,"width":-0.087499976,"height":0.013888889},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Planing - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Planing - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Refinement - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Refinement - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Todor Stamatov","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Todor Stamatov","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Stefka / James Weekly","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Stefka / James Weekly","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Robinson Crusoe Cruises Limited","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sprint Review","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sprint Review","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Todor Stamatov at Drun Drun Chiki Chiki Bam Bam","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Test 42","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Test 42","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Robinson Crusoe Cruises Limited","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sprint Review","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sprint Review","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Mihail Mihaylov","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Mihail Mihaylov","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Planing - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Planing - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Stefka / James Weekly","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Stefka / James Weekly","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Kara / James","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Kara / James","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Jiminny SF App","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny SF App","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2026-04-28-jiminny-x-cmbio-at-partnership-kick-off","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2026-04-28-jiminny-x-cmbio-at-partnership-kick-off","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Laura / James","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Laura / James","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Live Feed","depth":13,"bounds":{"left":0.70555556,"top":0.123333335,"width":0.04548611,"height":0.026666667},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Live Feed","depth":14,"bounds":{"left":0.70555556,"top":0.1261111,"width":0.04548611,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Veselin Kulov","depth":18,"bounds":{"left":0.74375,"top":0.19722222,"width":0.059027776,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"bounds":{"left":0.80625,"top":0.19777778,"width":0.05659722,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7 May, 3:47 PM","depth":18,"bounds":{"left":0.74375,"top":0.22,"width":0.060763888,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"bounds":{"left":0.74375,"top":0.26555556,"width":0.030555556,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"bounds":{"left":0.7760417,"top":0.26555556,"width":0.017361112,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"unknown customer","depth":18,"bounds":{"left":0.7951389,"top":0.26555556,"width":0.07777778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"bounds":{"left":0.75972223,"top":0.3188889,"width":0.021527778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7 May, 10:37 AM","depth":18,"bounds":{"left":0.7826389,"top":0.3188889,"width":0.06666667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"bounds":{"left":0.90208334,"top":0.3188889,"width":0.03888889,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4m","depth":18,"bounds":{"left":0.9423611,"top":0.3188889,"width":0.0125,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"bounds":{"left":0.75972223,"top":0.34666666,"width":0.025347222,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"bounds":{"left":0.7864583,"top":0.34666666,"width":0.010416667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Veselin Kulov","depth":18,"bounds":{"left":0.74375,"top":0.41277778,"width":0.059027776,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"bounds":{"left":0.80625,"top":0.41333333,"width":0.05659722,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7 May, 11:12 AM","depth":18,"bounds":{"left":0.74375,"top":0.43555555,"width":0.06666667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"bounds":{"left":0.74375,"top":0.4811111,"width":0.030555556,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"bounds":{"left":0.7760417,"top":0.4811111,"width":0.017361112,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"unknown customer","depth":18,"bounds":{"left":0.7951389,"top":0.4811111,"width":0.07777778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"bounds":{"left":0.75972223,"top":0.53444445,"width":0.021527778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7 May, 10:37 AM","depth":18,"bounds":{"left":0.7826389,"top":0.53444445,"width":0.06666667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
941764828010952693
|
1895058728585095555
|
visual_change
|
accessibility
|
NULL
|
Unnamed Group
Platform Sprint 4 Q2 - Platform Team Unnamed Group
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
Usage | Windsurf
Close tab
Unnamed Group
[SRD-6848] Sidekick SMS issue - Jira
Close tab
[JY-20891] Sidekick SMS issue - Jira
Close tab
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
Close tab
Unnamed Group
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
[JY-20903] Recorded call does not appear on the dashboard - Jira
Close tab
Jiminny
Close tab
Jiminny
Close tab
Jiminny
Close tab
Unnamed Group
[SRD-6853] Moxso - Potential deal stages bug - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Useful commands - Engineering - Confluence
Close tab
Unnamed Group
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
Close tab
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
Close tab
Jiminny
Close tab
New Tab
Close tab
New Tab
Close tab
CloudWatch | us-east-2
Close tab
Companies | All companies
Close tab
Developers | HubSpot
Close tab
Jiminny
Close tab
Feed — jiminny — Sentry
Close tab
Inbox (1,684) - [EMAIL] - Jiminny Mail
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
JY-20493-smart-instant-nudge-pre-filtering ■ 885799
75
75
My Recordings
My Recordings
Everyone's Recordings
Everyone's Recordings
No Recordings
Schedule
Schedule
Invite Notetaker
This Week
This Week
Everyone's Schedule
Everyone's Schedule
Unknown Customer
Backend Chapter
Tomorrow, 10:30 AM
Trending this month
Trending this month
Sort by Sort by: Most played
Sort by
Sort by:
Most played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
2
times played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
1
times played
Unknown Customer
Processing tickets review
Processing tickets review
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Refinement - Processing
Refinement - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Notetaker added by Todor Stamatov
Notetaker added by Todor Stamatov
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Todor Stamatov at Drun Drun Chiki Chiki Bam Bam
Test 42
Test 42
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Unknown Customer
Notetaker added by Mihail Mihaylov
Notetaker added by Mihail Mihaylov
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Kara / James
Kara / James
0
times played
Unknown Customer
Jiminny SF App
Jiminny SF App
0
times played
Unknown Customer
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Laura / James
Laura / James
0
times played
Live Feed
Live Feed
Veselin Kulov
listened to call
7 May, 3:47 PM
activity
with
unknown customer
Held:
7 May, 10:37 AM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
7 May, 11:12 AM
activity
with
unknown customer
Held:
7 May, 10:37 AM...
|
45140
|
NULL
|
NULL
|
NULL
|
|
45142
|
1622
|
0
|
2026-05-14T14:21:47.460854+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768507460_m2.jpg...
|
Firefox
|
Jiminny — Work
|
1
|
app.staging.jiminny.com/dashboard
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Unnamed Group
Platform Sprint 4 Q2 - Platform Team Unnamed Group
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
Usage | Windsurf
Close tab
Unnamed Group
[SRD-6848] Sidekick SMS issue - Jira
Close tab
[JY-20891] Sidekick SMS issue - Jira
Close tab
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
Close tab
Unnamed Group
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
[JY-20903] Recorded call does not appear on the dashboard - Jira
Close tab
Jiminny
Close tab
Jiminny
Close tab
Jiminny
Close tab
Unnamed Group
[SRD-6853] Moxso - Potential deal stages bug - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Useful commands - Engineering - Confluence
Close tab
Unnamed Group
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
Close tab
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
Close tab
Jiminny
Close tab
New Tab
Close tab
New Tab
Close tab
CloudWatch | us-east-2
Close tab
Companies | All companies
Close tab
Developers | HubSpot
Close tab
Jiminny
Close tab
Feed — jiminny — Sentry
Close tab
Inbox (1,684) - [EMAIL] - Jiminny Mail
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
JY-20493-smart-instant-nudge-pre-filtering ■ 885799
75
75
My Recordings
My Recordings
Everyone's Recordings
Everyone's Recordings
No Recordings
Schedule
Schedule
Invite Notetaker
This Week
This Week
Everyone's Schedule
Everyone's Schedule
Unknown Customer
Backend Chapter
Tomorrow, 10:30 AM
Trending this month
Trending this month
Sort by Sort by: Most played
Sort by
Sort by:
Most played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
2
times played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
1
times played
Unknown Customer
Processing tickets review
Processing tickets review
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Refinement - Processing
Refinement - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Notetaker added by Todor Stamatov
Notetaker added by Todor Stamatov
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Todor Stamatov at Drun Drun Chiki Chiki Bam Bam
Test 42
Test 42
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Unknown Customer
Notetaker added by Mihail Mihaylov
Notetaker added by Mihail Mihaylov
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Kara / James
Kara / James
0
times played
Unknown Customer
Jiminny SF App
Jiminny SF App
0
times played
Unknown Customer
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Laura / James
Laura / James
0
times played
Live Feed
Live Feed
Veselin Kulov
listened to call
7 May, 3:47 PM
activity
with
unknown customer
Held:
7 May, 10:37 AM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
7 May, 11:12 AM
activity
with
unknown customer
Held:
7 May, 10:37 AM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
29 Apr, 2:03 PM
activity
with
unknown customer
Held:
29 Apr, 1:33 PM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
8 Apr, 12:51 AM
activity
with
unknown customer
Held:
8 Apr, 12:36 AM
Duration:
7m
Value:
$0
Nikolay Nikolov
listened to call
27 Feb, 4:38 PM
Web Demo
with
Martin Petkov
Held:
13 Feb, 2:15 PM
Duration:
11m
Value:
$1
Nikolay Yankov
listened to call
12 Feb, 10:07 AM
activity
with
Nikolay Yankov
Held:
12 Feb, 9:42 AM
Duration:
1m
Value:
$0
Nikolay Yankov
listened to call
12 Feb, 10:06 AM
activity
with
Nikolay Yankov
Held:
12 Feb, 9:42 AM
Duration:
1m
Value:
$0
Martin Petkov
listened to call
22 Jan, 1:30 PM
Discovery
with
Martin Petkov
Held:
21 Jan, 4:00 PM
Duration:
35m
Value:
$2
Martin Petkov
listened to call
20 Jan, 11:42 AM
activity
with
Jonathan Heaton
Held:
25 Sep, 2024, 1:33 PM
Duration:
37m
Value:
£8,886
Galya Dimitrova
listened to call
26 Nov, 2025, 1:29 PM
activity
with
David Ehrlich
Held:
9 Oct, 2024, 3:31 PM
Duration:
39m
Value:
$23,000
Aneliya Angelova
listened to call
18 Aug, 2025, 8:45 PM
Cold call
with
Robinson Crusoe New 13333
Held:
23 Jul, 2025, 5:43 PM
Duration:
2m
Value:
$5
Jiminny Web SA
shared in Slack
23 Jul, 2025, 5:48 PM
Cold call
with
Robinson Crusoe New 13333
Held:
23 Jul, 2025, 5:43 PM
Duration:
2m
Value:
$5
Jiminny Web SA
shared in Slack
23 Jul, 2025, 5:32 PM
activity
with
Reward Gateway
Held:
19 Sep, 2024, 4:00 PM
Duration:
52m
Value:
$0
Jiminny Web SA
shared in Slack
17 Jul, 2025, 1:32 PM
activity
with
Robinson Crusoe New 13333
Held:
17 Jul, 2025, 1:28 PM
Duration:
10s
Value:
$5
Jiminny Web SA
shared in Slack
17 Jul, 2025, 1:29 PM
activity
with
Storyclash GmbH
Held:
6 Jun, 2024, 11:00 AM
Duration:
37m
Value:
$4,725
Jiminny Web SA
shared in Slack
16 Jul, 2025, 9:25 AM
activity
with
Robinson Crusoe New 13333
Held:
16 Jul, 2025, 9:21 AM
Duration:
37s
Value:
$5
Veselin Kulov
listened to call
11 Jul, 2025, 9:53 AM
Discovery
with
Drun Drun Chiki Chiki Bam Bam
Held:
2 Jul, 2025, 5:08 PM
Duration:
30m
Value:
$0
Veselin Kulov
listened to call
11 Jul, 2025, 9:46 AM
Discovery
with
Drun Drun Chiki Chiki Bam Bam
Held:
2 Jul, 2025, 5:08 PM
Duration:
30m
Value:
$0
Jiminny Web SA
shared in Slack
9 Jul, 2025, 12:11 PM
activity
with
Robinson Crusoe New 13333
Held:
9 Jul, 2025, 12:08 PM
Duration:
2m
Value:
$5...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.27443483,"top":0.509976,"width":0.007978723,"height":0.01915403},"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.27027926,"top":0.53351957,"width":0.016123671,"height":0.032721467},"on_screen":false,"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.27094415,"top":0.53351957,"width":0.004986702,"height":0.011971269},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app","depth":4,"bounds":{"left":0.27027926,"top":0.566241,"width":0.016123671,"height":0.032721467},"on_screen":false,"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.27094415,"top":0.566241,"width":0.004986702,"height":0.011971269},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":4,"bounds":{"left":0.27027926,"top":0.5989625,"width":0.016123671,"height":0.032721467},"on_screen":false,"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.27094415,"top":0.5989625,"width":0.004986702,"height":0.011971269},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Usage | Windsurf","depth":4,"bounds":{"left":0.27027926,"top":0.63168395,"width":0.016123671,"height":0.032721467},"on_screen":false,"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.27094415,"top":0.63168395,"width":0.004986702,"height":0.011971269},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.27443483,"top":0.669593,"width":0.007978723,"height":0.01915403},"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6848] Sidekick SMS issue - Jira","depth":4,"bounds":{"left":0.27027926,"top":0.69313645,"width":0.016123671,"height":0.032721467},"on_screen":false,"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.27094415,"top":0.69313645,"width":0.004986702,"height":0.011971269},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20891] Sidekick SMS issue - Jira","depth":4,"bounds":{"left":0.27027926,"top":0.7258579,"width":0.016123671,"height":0.032721467},"on_screen":false,"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.27094415,"top":0.7258579,"width":0.004986702,"height":0.011971269},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app","depth":4,"bounds":{"left":0.27027926,"top":0.75857943,"width":0.016123671,"height":0.032721467},"on_screen":false,"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.27094415,"top":0.75857943,"width":0.004986702,"height":0.011971269},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.27443483,"top":0.7964884,"width":0.007978723,"height":0.01915403},"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":4,"bounds":{"left":0.27027926,"top":0.82003194,"width":0.016123671,"height":0.032721467},"on_screen":false,"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.27094415,"top":0.82003194,"width":0.004986702,"height":0.011971269},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20903] Recorded call does not appear on the dashboard - Jira","depth":4,"bounds":{"left":0.27027926,"top":0.8527534,"width":0.016123671,"height":0.032721467},"on_screen":false,"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.27094415,"top":0.8527534,"width":0.004986702,"height":0.011971269},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.27027926,"top":0.88547486,"width":0.016123671,"height":0.032721467},"on_screen":false,"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.27094415,"top":0.88547486,"width":0.004986702,"height":0.011971269},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.27027926,"top":0.9181963,"width":0.016123671,"height":0.032721467},"on_screen":false,"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.27094415,"top":0.9181963,"width":0.004986702,"height":0.011971269},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.27027926,"top":0.9509178,"width":0.016123671,"height":0.032721467},"on_screen":false,"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.27094415,"top":0.9509178,"width":0.004986702,"height":0.011971269},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.27443483,"top":0.9888268,"width":0.007978723,"height":0.011173189},"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6853] Moxso - Potential deal stages bug - Jira","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.016123671,"height":-0.012370348},"on_screen":true,"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.27094415,"top":1.0,"width":0.004986702,"height":-0.012370348},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.016123671,"height":-0.04509175},"on_screen":true,"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.27094415,"top":1.0,"width":0.004986702,"height":-0.04509175},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.016123671,"height":-0.07781327},"on_screen":true,"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.27094415,"top":1.0,"width":0.004986702,"height":-0.07781327},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Useful commands - Engineering - Confluence","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Companies | All companies","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Developers | HubSpot","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Inbox (1,684) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20493-smart-instant-nudge-pre-filtering ■ 885799","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"75","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"75","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"My Recordings","depth":14,"bounds":{"left":0.3382646,"top":1.0,"width":0.03856383,"height":-0.07182765},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"My Recordings","depth":15,"bounds":{"left":0.3402593,"top":1.0,"width":0.034574468,"height":-0.09058261},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Everyone's Recordings","depth":14,"bounds":{"left":0.37682846,"top":1.0,"width":0.05618351,"height":-0.07182765},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Everyone's Recordings","depth":15,"bounds":{"left":0.37882313,"top":1.0,"width":0.05219415,"height":-0.09058261},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No Recordings","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Schedule","depth":13,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Schedule","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Invite Notetaker","depth":14,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXComboBox","text":"This Week","depth":14,"on_screen":true,"value":"This Week","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"This Week","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Everyone's Schedule","depth":14,"on_screen":true,"value":"Everyone's Schedule","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Everyone's Schedule","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Backend Chapter","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Tomorrow, 10:30 AM","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Trending this month","depth":13,"bounds":{"left":0.4652593,"top":1.0,"width":0.04637633,"height":-0.0885874},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Trending this month","depth":14,"bounds":{"left":0.4652593,"top":1.0,"width":0.04637633,"height":-0.09058261},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Sort by Sort by: Most played","depth":13,"bounds":{"left":0.5349069,"top":1.0,"width":0.05651596,"height":-0.08339989},"on_screen":true,"value":"Sort by Sort by: Most played","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Sort by","depth":14,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sort by:","depth":15,"bounds":{"left":0.53856385,"top":1.0,"width":0.016954787,"height":-0.091380715},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Most played","depth":15,"bounds":{"left":0.5555186,"top":1.0,"width":0.025930852,"height":-0.091380715},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Veselin Kulov","depth":15,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Veselin Kulov","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Veselin Kulov","depth":15,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Veselin Kulov","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Processing tickets review","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Processing tickets review","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Planing - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Planing - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Refinement - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Refinement - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Todor Stamatov","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Todor Stamatov","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Stefka / James Weekly","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Stefka / James Weekly","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Robinson Crusoe Cruises Limited","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sprint Review","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sprint Review","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Todor Stamatov at Drun Drun Chiki Chiki Bam Bam","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Test 42","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Test 42","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Robinson Crusoe Cruises Limited","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sprint Review","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sprint Review","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Mihail Mihaylov","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Mihail Mihaylov","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Planing - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Planing - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Stefka / James Weekly","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Stefka / James Weekly","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Kara / James","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Kara / James","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Jiminny SF App","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny SF App","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2026-04-28-jiminny-x-cmbio-at-partnership-kick-off","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2026-04-28-jiminny-x-cmbio-at-partnership-kick-off","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Laura / James","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Laura / James","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Live Feed","depth":13,"bounds":{"left":0.6080452,"top":1.0,"width":0.021775266,"height":-0.0885874},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Live Feed","depth":14,"bounds":{"left":0.6080452,"top":1.0,"width":0.021775266,"height":-0.09058261},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Veselin Kulov","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7 May, 3:47 PM","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"unknown customer","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7 May, 10:37 AM","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4m","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Veselin Kulov","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7 May, 11:12 AM","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"unknown customer","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7 May, 10:37 AM","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4m","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Veselin Kulov","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"29 Apr, 2:03 PM","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"unknown customer","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"29 Apr, 1:33 PM","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4m","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Veselin Kulov","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8 Apr, 12:51 AM","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"unknown customer","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8 Apr, 12:36 AM","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7m","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Nikolov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"27 Feb, 4:38 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web Demo","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Martin Petkov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"13 Feb, 2:15 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11m","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$1","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12 Feb, 10:07 AM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12 Feb, 9:42 AM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1m","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12 Feb, 10:06 AM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12 Feb, 9:42 AM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1m","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Martin Petkov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"22 Jan, 1:30 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Discovery","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Martin Petkov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"21 Jan, 4:00 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"35m","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$2","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Martin Petkov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"20 Jan, 11:42 AM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Jonathan Heaton","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25 Sep, 2024, 1:33 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"37m","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"£8,886","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"26 Nov, 2025, 1:29 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"David Ehrlich","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"9 Oct, 2024, 3:31 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"39m","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$23,000","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Aneliya Angelova","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"18 Aug, 2025, 8:45 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Cold call","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Robinson Crusoe New 13333","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"23 Jul, 2025, 5:43 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2m","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$5","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Jiminny Web SA","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shared in Slack","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"23 Jul, 2025, 5:48 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Cold call","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Robinson Crusoe New 13333","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"23 Jul, 2025, 5:43 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2m","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$5","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Jiminny Web SA","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shared in Slack","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"23 Jul, 2025, 5:32 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Reward Gateway","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"19 Sep, 2024, 4:00 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"52m","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Jiminny Web SA","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shared in Slack","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"17 Jul, 2025, 1:32 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Robinson Crusoe New 13333","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"17 Jul, 2025, 1:28 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"10s","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$5","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Jiminny Web SA","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shared in Slack","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"17 Jul, 2025, 1:29 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Storyclash GmbH","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6 Jun, 2024, 11:00 AM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"37m","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$4,725","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Jiminny Web SA","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shared in Slack","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"16 Jul, 2025, 9:25 AM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Robinson Crusoe New 13333","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"16 Jul, 2025, 9:21 AM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"37s","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$5","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Veselin Kulov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11 Jul, 2025, 9:53 AM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Discovery","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Drun Drun Chiki Chiki Bam Bam","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2 Jul, 2025, 5:08 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"30m","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Veselin Kulov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11 Jul, 2025, 9:46 AM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Discovery","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Drun Drun Chiki Chiki Bam Bam","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2 Jul, 2025, 5:08 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"30m","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Jiminny Web SA","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shared in Slack","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"9 Jul, 2025, 12:11 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Robinson Crusoe New 13333","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"9 Jul, 2025, 12:08 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2m","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$5","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-4670819890294558105
|
2174280806339471746
|
visual_change
|
accessibility
|
NULL
|
Unnamed Group
Platform Sprint 4 Q2 - Platform Team Unnamed Group
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
Usage | Windsurf
Close tab
Unnamed Group
[SRD-6848] Sidekick SMS issue - Jira
Close tab
[JY-20891] Sidekick SMS issue - Jira
Close tab
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
Close tab
Unnamed Group
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
[JY-20903] Recorded call does not appear on the dashboard - Jira
Close tab
Jiminny
Close tab
Jiminny
Close tab
Jiminny
Close tab
Unnamed Group
[SRD-6853] Moxso - Potential deal stages bug - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Useful commands - Engineering - Confluence
Close tab
Unnamed Group
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
Close tab
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
Close tab
Jiminny
Close tab
New Tab
Close tab
New Tab
Close tab
CloudWatch | us-east-2
Close tab
Companies | All companies
Close tab
Developers | HubSpot
Close tab
Jiminny
Close tab
Feed — jiminny — Sentry
Close tab
Inbox (1,684) - [EMAIL] - Jiminny Mail
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
JY-20493-smart-instant-nudge-pre-filtering ■ 885799
75
75
My Recordings
My Recordings
Everyone's Recordings
Everyone's Recordings
No Recordings
Schedule
Schedule
Invite Notetaker
This Week
This Week
Everyone's Schedule
Everyone's Schedule
Unknown Customer
Backend Chapter
Tomorrow, 10:30 AM
Trending this month
Trending this month
Sort by Sort by: Most played
Sort by
Sort by:
Most played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
2
times played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
1
times played
Unknown Customer
Processing tickets review
Processing tickets review
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Refinement - Processing
Refinement - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Notetaker added by Todor Stamatov
Notetaker added by Todor Stamatov
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Todor Stamatov at Drun Drun Chiki Chiki Bam Bam
Test 42
Test 42
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Unknown Customer
Notetaker added by Mihail Mihaylov
Notetaker added by Mihail Mihaylov
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Kara / James
Kara / James
0
times played
Unknown Customer
Jiminny SF App
Jiminny SF App
0
times played
Unknown Customer
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Laura / James
Laura / James
0
times played
Live Feed
Live Feed
Veselin Kulov
listened to call
7 May, 3:47 PM
activity
with
unknown customer
Held:
7 May, 10:37 AM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
7 May, 11:12 AM
activity
with
unknown customer
Held:
7 May, 10:37 AM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
29 Apr, 2:03 PM
activity
with
unknown customer
Held:
29 Apr, 1:33 PM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
8 Apr, 12:51 AM
activity
with
unknown customer
Held:
8 Apr, 12:36 AM
Duration:
7m
Value:
$0
Nikolay Nikolov
listened to call
27 Feb, 4:38 PM
Web Demo
with
Martin Petkov
Held:
13 Feb, 2:15 PM
Duration:
11m
Value:
$1
Nikolay Yankov
listened to call
12 Feb, 10:07 AM
activity
with
Nikolay Yankov
Held:
12 Feb, 9:42 AM
Duration:
1m
Value:
$0
Nikolay Yankov
listened to call
12 Feb, 10:06 AM
activity
with
Nikolay Yankov
Held:
12 Feb, 9:42 AM
Duration:
1m
Value:
$0
Martin Petkov
listened to call
22 Jan, 1:30 PM
Discovery
with
Martin Petkov
Held:
21 Jan, 4:00 PM
Duration:
35m
Value:
$2
Martin Petkov
listened to call
20 Jan, 11:42 AM
activity
with
Jonathan Heaton
Held:
25 Sep, 2024, 1:33 PM
Duration:
37m
Value:
£8,886
Galya Dimitrova
listened to call
26 Nov, 2025, 1:29 PM
activity
with
David Ehrlich
Held:
9 Oct, 2024, 3:31 PM
Duration:
39m
Value:
$23,000
Aneliya Angelova
listened to call
18 Aug, 2025, 8:45 PM
Cold call
with
Robinson Crusoe New 13333
Held:
23 Jul, 2025, 5:43 PM
Duration:
2m
Value:
$5
Jiminny Web SA
shared in Slack
23 Jul, 2025, 5:48 PM
Cold call
with
Robinson Crusoe New 13333
Held:
23 Jul, 2025, 5:43 PM
Duration:
2m
Value:
$5
Jiminny Web SA
shared in Slack
23 Jul, 2025, 5:32 PM
activity
with
Reward Gateway
Held:
19 Sep, 2024, 4:00 PM
Duration:
52m
Value:
$0
Jiminny Web SA
shared in Slack
17 Jul, 2025, 1:32 PM
activity
with
Robinson Crusoe New 13333
Held:
17 Jul, 2025, 1:28 PM
Duration:
10s
Value:
$5
Jiminny Web SA
shared in Slack
17 Jul, 2025, 1:29 PM
activity
with
Storyclash GmbH
Held:
6 Jun, 2024, 11:00 AM
Duration:
37m
Value:
$4,725
Jiminny Web SA
shared in Slack
16 Jul, 2025, 9:25 AM
activity
with
Robinson Crusoe New 13333
Held:
16 Jul, 2025, 9:21 AM
Duration:
37s
Value:
$5
Veselin Kulov
listened to call
11 Jul, 2025, 9:53 AM
Discovery
with
Drun Drun Chiki Chiki Bam Bam
Held:
2 Jul, 2025, 5:08 PM
Duration:
30m
Value:
$0
Veselin Kulov
listened to call
11 Jul, 2025, 9:46 AM
Discovery
with
Drun Drun Chiki Chiki Bam Bam
Held:
2 Jul, 2025, 5:08 PM
Duration:
30m
Value:
$0
Jiminny Web SA
shared in Slack
9 Jul, 2025, 12:11 PM
activity
with
Robinson Crusoe New 13333
Held:
9 Jul, 2025, 12:08 PM
Duration:
2m
Value:
$5...
|
45139
|
NULL
|
NULL
|
NULL
|
|
45143
|
1621
|
2
|
2026-05-14T14:21:49.460003+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768509460_m1.jpg...
|
Firefox
|
Jiminny — Work
|
1
|
app.staging.jiminny.com/dashboard
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Unnamed Group
Platform Sprint 4 Q2 - Platform Team Unnamed Group
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
Usage | Windsurf
Close tab
Unnamed Group
[SRD-6848] Sidekick SMS issue - Jira
Close tab
[JY-20891] Sidekick SMS issue - Jira
Close tab
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
Close tab
Unnamed Group
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
[JY-20903] Recorded call does not appear on the dashboard - Jira
Close tab
Jiminny
Close tab
Jiminny
Close tab
Jiminny
Close tab
Unnamed Group
[SRD-6853] Moxso - Potential deal stages bug - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Useful commands - Engineering - Confluence
Close tab
Unnamed Group
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
Close tab
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
Close tab
Jiminny
Close tab
New Tab
Close tab
New Tab
Close tab
CloudWatch | us-east-2
Close tab
Companies | All companies
Close tab
Developers | HubSpot
Close tab
Jiminny
Close tab
Feed — jiminny — Sentry
Close tab
Inbox (1,684) - [EMAIL] - Jiminny Mail
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
JY-20493-smart-instant-nudge-pre-filtering ■ 885799
75
75
My Recordings
My Recordings
Everyone's Recordings
Everyone's Recordings
No Recordings
Schedule
Schedule
Invite Notetaker
This Week
This Week
Everyone's Schedule
Everyone's Schedule
Unknown Customer
Backend Chapter
Tomorrow, 10:30 AM
Trending this month
Trending this month
Sort by Sort by: Most played
Sort by
Sort by:
Most played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
2
times played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
1
times played
Unknown Customer
Processing tickets review
Processing tickets review
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Refinement - Processing
Refinement - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Notetaker added by Todor Stamatov
Notetaker added by Todor Stamatov
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Todor Stamatov at Drun Drun Chiki Chiki Bam Bam
Test 42
Test 42
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Unknown Customer
Notetaker added by Mihail Mihaylov
Notetaker added by Mihail Mihaylov
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Kara / James
Kara / James
0
times played
Unknown Customer
Jiminny SF App
Jiminny SF App
0
times played
Unknown Customer
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Laura / James
Laura / James
0
times played
Live Feed
Live Feed
Veselin Kulov
listened to call
7 May, 3:47 PM
activity
with
unknown customer
Held:
7 May, 10:37 AM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
7 May, 11:12 AM
activity
with
unknown customer
Held:
7 May, 10:37 AM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
29 Apr, 2:03 PM
activity
with
unknown customer
Held:
29 Apr, 1:33 PM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
8 Apr, 12:51 AM
activity
with
unknown customer
Held:
8 Apr, 12:36 AM
Duration:
7m
Value:
$0
Nikolay Nikolov
listened to call
27 Feb, 4:38 PM
Web Demo
with
Martin Petkov
Held:
13 Feb, 2:15 PM
Duration:
11m
Value:
$1
Nikolay Yankov
listened to call
12 Feb, 10:07 AM
activity
with
Nikolay Yankov
Held:
12 Feb, 9:42 AM
Duration:
1m
Value:
$0
Nikolay Yankov
listened to call
12 Feb, 10:06 AM
activity
with
Nikolay Yankov
Held:
12 Feb, 9:42 AM
Duration:
1m
Value:
$0
Martin Petkov
listened to call
22 Jan, 1:30 PM
Discovery
with
Martin Petkov
Held:
21 Jan, 4:00 PM
Duration:
35m
Value:
$2
Martin Petkov
listened to call
20 Jan, 11:42 AM
activity
with
Jonathan Heaton
Held:
25 Sep, 2024, 1:33 PM
Duration:
37m
Value:
£8,886
Galya Dimitrova
listened to call
26 Nov, 2025, 1:29 PM
activity
with
David Ehrlich
Held:
9 Oct, 2024, 3:31 PM
Duration:
39m
Value:
$23,000
Aneliya Angelova
listened to call
18 Aug, 2025, 8:45 PM
Cold call
with
Robinson Crusoe New 13333
Held:
23 Jul, 2025, 5:43 PM
Duration:
2m
Value:
$5
Jiminny Web SA
shared in Slack...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Usage | Windsurf","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6848] Sidekick SMS issue - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20891] Sidekick SMS issue - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20903] Recorded call does not appear on the dashboard - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.033680554,"height":0.045555554},"on_screen":false,"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.0,"width":0.010416667,"height":0.016666668},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.008680556,"top":0.0,"width":0.016666668,"height":0.026666667},"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6853] Moxso - Potential deal stages bug - Jira","depth":4,"bounds":{"left":0.0,"top":0.017222222,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.017222222,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.06277778,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.06277778,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.108333334,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.108333334,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Useful commands - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.1538889,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.1538889,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.008680556,"top":0.20666666,"width":0.016666668,"height":0.026666667},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.23944445,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.23944445,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira","depth":4,"bounds":{"left":0.0,"top":0.285,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.285,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.33055556,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.33055556,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.37611112,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.37611112,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.42166665,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.42166665,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0,"top":0.4672222,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.4672222,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Companies | All companies","depth":4,"bounds":{"left":0.0,"top":0.5127778,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.5127778,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Developers | HubSpot","depth":4,"bounds":{"left":0.0,"top":0.55833334,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.55833334,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.60388887,"width":0.033680554,"height":0.045555554},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.60388887,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.0,"top":0.64944446,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.64944446,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Inbox (1,684) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.695,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.695,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.005902778,"top":0.74666667,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.0,"top":0.7977778,"width":0.033680554,"height":0.043333333},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.0,"top":0.8411111,"width":0.033680554,"height":0.038333334},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.0,"top":0.8794444,"width":0.033680554,"height":0.03888889},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.0,"top":0.91833335,"width":0.033680554,"height":0.038333334},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0,"top":0.95666665,"width":0.033680554,"height":0.043333333},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20493-smart-instant-nudge-pre-filtering ■ 885799","depth":9,"bounds":{"left":0.035069443,"top":0.98055553,"width":0.21875,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"75","depth":12,"bounds":{"left":0.03923611,"top":0.88,"width":0.033333335,"height":0.04888889},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"75","depth":14,"bounds":{"left":0.05659722,"top":0.885,"width":0.009722223,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"My Recordings","depth":14,"bounds":{"left":0.14201389,"top":0.1,"width":0.08055556,"height":0.07333333},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"My Recordings","depth":15,"bounds":{"left":0.14618056,"top":0.1261111,"width":0.072222225,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Everyone's Recordings","depth":14,"bounds":{"left":0.22256945,"top":0.1,"width":0.11736111,"height":0.07333333},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Everyone's Recordings","depth":15,"bounds":{"left":0.22673611,"top":0.1261111,"width":0.10902778,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No Recordings","depth":17,"bounds":{"left":0.209375,"top":0.36055556,"width":0.063541666,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Schedule","depth":13,"bounds":{"left":0.396875,"top":0.36333334,"width":0.050347224,"height":0.037777778},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Schedule","depth":14,"bounds":{"left":0.396875,"top":0.37,"width":0.050347224,"height":0.024444444},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Invite Notetaker","depth":14,"bounds":{"left":0.5888889,"top":0.36,"width":0.09236111,"height":0.04},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXComboBox","text":"This Week","depth":14,"bounds":{"left":0.40729168,"top":0.4288889,"width":0.12847222,"height":0.04111111},"on_screen":true,"value":"This Week","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"This Week","depth":17,"bounds":{"left":0.41493055,"top":0.44,"width":0.045833334,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Everyone's Schedule","depth":14,"bounds":{"left":0.54270834,"top":0.4288889,"width":0.128125,"height":0.04111111},"on_screen":true,"value":"Everyone's Schedule","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Everyone's Schedule","depth":17,"bounds":{"left":0.5503472,"top":0.44,"width":0.088194445,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"bounds":{"left":0.44895834,"top":0.51055557,"width":0.08090278,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Backend Chapter","depth":17,"bounds":{"left":0.44895834,"top":0.5377778,"width":0.06979167,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Tomorrow, 10:30 AM","depth":16,"bounds":{"left":0.44895834,"top":0.565,"width":0.08576389,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Trending this month","depth":13,"bounds":{"left":0.40729168,"top":0.123333335,"width":0.096875,"height":0.026666667},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Trending this month","depth":14,"bounds":{"left":0.40729168,"top":0.1261111,"width":0.096875,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Sort by Sort by: Most played","depth":13,"bounds":{"left":0.55277777,"top":0.116111115,"width":0.11805555,"height":0.04111111},"on_screen":true,"value":"Sort by Sort by: Most played","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Sort by","depth":14,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sort by:","depth":15,"bounds":{"left":0.56041664,"top":0.12722223,"width":0.035416666,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Most played","depth":15,"bounds":{"left":0.59583336,"top":0.12722223,"width":0.054166667,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"bounds":{"left":0.48993057,"top":0.23333333,"width":0.084375,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Veselin Kulov","depth":15,"bounds":{"left":0.45520833,"top":0.25777778,"width":0.16770834,"height":0.022222223},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Veselin Kulov","depth":16,"bounds":{"left":0.47604167,"top":0.26055557,"width":0.12604167,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":16,"bounds":{"left":0.5420139,"top":0.28611112,"width":0.008680556,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"bounds":{"left":0.51944447,"top":0.30666667,"width":0.03888889,"height":0.013888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"bounds":{"left":0.7739583,"top":0.23333333,"width":0.084375,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Veselin Kulov","depth":15,"bounds":{"left":0.7392361,"top":0.25777778,"width":0.16770834,"height":0.022222223},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Veselin Kulov","depth":16,"bounds":{"left":0.76006943,"top":0.26055557,"width":0.12604167,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":16,"bounds":{"left":0.82604164,"top":0.28611112,"width":0.008680556,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"bounds":{"left":0.8034722,"top":0.30666667,"width":0.03888889,"height":0.013888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"bounds":{"left":1.0,"top":0.23333333,"width":-0.05798614,"height":0.017777778},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Processing tickets review","depth":15,"bounds":{"left":1.0,"top":0.25777778,"width":-0.03923607,"height":0.022222223},"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Processing tickets review","depth":16,"bounds":{"left":1.0,"top":0.26055557,"width":-0.06006944,"height":0.016666668},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"bounds":{"left":1.0,"top":0.30666667,"width":-0.087499976,"height":0.013888889},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Planing - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Planing - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Refinement - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Refinement - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Todor Stamatov","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Todor Stamatov","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Stefka / James Weekly","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Stefka / James Weekly","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Robinson Crusoe Cruises Limited","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sprint Review","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sprint Review","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Todor Stamatov at Drun Drun Chiki Chiki Bam Bam","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Test 42","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Test 42","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Robinson Crusoe Cruises Limited","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sprint Review","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sprint Review","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Mihail Mihaylov","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Mihail Mihaylov","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Planing - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Planing - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Stefka / James Weekly","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Stefka / James Weekly","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Kara / James","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Kara / James","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Jiminny SF App","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny SF App","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2026-04-28-jiminny-x-cmbio-at-partnership-kick-off","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2026-04-28-jiminny-x-cmbio-at-partnership-kick-off","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Laura / James","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Laura / James","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Live Feed","depth":13,"bounds":{"left":0.70555556,"top":0.123333335,"width":0.04548611,"height":0.026666667},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Live Feed","depth":14,"bounds":{"left":0.70555556,"top":0.1261111,"width":0.04548611,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Veselin Kulov","depth":18,"bounds":{"left":0.74375,"top":0.19722222,"width":0.059027776,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"bounds":{"left":0.80625,"top":0.19777778,"width":0.05659722,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7 May, 3:47 PM","depth":18,"bounds":{"left":0.74375,"top":0.22,"width":0.060763888,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"bounds":{"left":0.74375,"top":0.26555556,"width":0.030555556,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"bounds":{"left":0.7760417,"top":0.26555556,"width":0.017361112,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"unknown customer","depth":18,"bounds":{"left":0.7951389,"top":0.26555556,"width":0.07777778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"bounds":{"left":0.75972223,"top":0.3188889,"width":0.021527778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7 May, 10:37 AM","depth":18,"bounds":{"left":0.7826389,"top":0.3188889,"width":0.06666667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"bounds":{"left":0.90208334,"top":0.3188889,"width":0.03888889,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4m","depth":18,"bounds":{"left":0.9423611,"top":0.3188889,"width":0.0125,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"bounds":{"left":0.75972223,"top":0.34666666,"width":0.025347222,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"bounds":{"left":0.7864583,"top":0.34666666,"width":0.010416667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Veselin Kulov","depth":18,"bounds":{"left":0.74375,"top":0.41277778,"width":0.059027776,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"bounds":{"left":0.80625,"top":0.41333333,"width":0.05659722,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7 May, 11:12 AM","depth":18,"bounds":{"left":0.74375,"top":0.43555555,"width":0.06666667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"bounds":{"left":0.74375,"top":0.4811111,"width":0.030555556,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"bounds":{"left":0.7760417,"top":0.4811111,"width":0.017361112,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"unknown customer","depth":18,"bounds":{"left":0.7951389,"top":0.4811111,"width":0.07777778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"bounds":{"left":0.75972223,"top":0.53444445,"width":0.021527778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7 May, 10:37 AM","depth":18,"bounds":{"left":0.7826389,"top":0.53444445,"width":0.06666667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"bounds":{"left":0.90208334,"top":0.53444445,"width":0.03888889,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4m","depth":18,"bounds":{"left":0.9423611,"top":0.53444445,"width":0.0125,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"bounds":{"left":0.75972223,"top":0.56222224,"width":0.025347222,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"bounds":{"left":0.7864583,"top":0.56222224,"width":0.010416667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Veselin Kulov","depth":18,"bounds":{"left":0.74375,"top":0.62833333,"width":0.059027776,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"bounds":{"left":0.80625,"top":0.6288889,"width":0.05659722,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"29 Apr, 2:03 PM","depth":18,"bounds":{"left":0.74375,"top":0.6511111,"width":0.063541666,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"bounds":{"left":0.74375,"top":0.69666666,"width":0.030555556,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"bounds":{"left":0.7760417,"top":0.69666666,"width":0.017361112,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"unknown customer","depth":18,"bounds":{"left":0.7951389,"top":0.69666666,"width":0.07777778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"bounds":{"left":0.75972223,"top":0.75,"width":0.021527778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"29 Apr, 1:33 PM","depth":18,"bounds":{"left":0.7826389,"top":0.75,"width":0.063541666,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"bounds":{"left":0.90208334,"top":0.75,"width":0.03888889,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4m","depth":18,"bounds":{"left":0.9423611,"top":0.75,"width":0.0125,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"bounds":{"left":0.75972223,"top":0.7777778,"width":0.025347222,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"bounds":{"left":0.7864583,"top":0.7777778,"width":0.010416667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Veselin Kulov","depth":18,"bounds":{"left":0.74375,"top":0.8438889,"width":0.059027776,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"bounds":{"left":0.80625,"top":0.84444445,"width":0.05659722,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8 Apr, 12:51 AM","depth":18,"bounds":{"left":0.74375,"top":0.8666667,"width":0.06423611,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"bounds":{"left":0.74375,"top":0.9122222,"width":0.030555556,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"bounds":{"left":0.7760417,"top":0.9122222,"width":0.017361112,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"unknown customer","depth":18,"bounds":{"left":0.7951389,"top":0.9122222,"width":0.07777778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"bounds":{"left":0.75972223,"top":0.96555555,"width":0.021527778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8 Apr, 12:36 AM","depth":18,"bounds":{"left":0.7826389,"top":0.96555555,"width":0.06423611,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"bounds":{"left":0.90208334,"top":0.96555555,"width":0.03888889,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7m","depth":18,"bounds":{"left":0.9423611,"top":0.96555555,"width":0.0125,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"bounds":{"left":0.75972223,"top":0.99333334,"width":0.025347222,"height":0.0066666603},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"bounds":{"left":0.7864583,"top":0.99333334,"width":0.010416667,"height":0.0066666603},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Nikolov","depth":18,"bounds":{"left":0.74375,"top":1.0,"width":0.06875,"height":-0.059444427},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"bounds":{"left":0.8159722,"top":1.0,"width":0.056944445,"height":-0.059999943},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"27 Feb, 4:38 PM","depth":18,"bounds":{"left":0.74375,"top":1.0,"width":0.06388889,"height":-0.08222222},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web Demo","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Martin Petkov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"13 Feb, 2:15 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11m","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$1","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12 Feb, 10:07 AM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12 Feb, 9:42 AM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1m","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12 Feb, 10:06 AM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12 Feb, 9:42 AM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1m","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Martin Petkov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"22 Jan, 1:30 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Discovery","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Martin Petkov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"21 Jan, 4:00 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"35m","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$2","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Martin Petkov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"20 Jan, 11:42 AM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Jonathan Heaton","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25 Sep, 2024, 1:33 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"37m","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"£8,886","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"26 Nov, 2025, 1:29 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"David Ehrlich","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"9 Oct, 2024, 3:31 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"39m","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$23,000","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Aneliya Angelova","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"18 Aug, 2025, 8:45 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Cold call","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Robinson Crusoe New 13333","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"23 Jul, 2025, 5:43 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2m","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$5","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Jiminny Web SA","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"shared in Slack","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-8063002748596934845
|
2174562212261161859
|
visual_change
|
accessibility
|
NULL
|
Unnamed Group
Platform Sprint 4 Q2 - Platform Team Unnamed Group
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
Usage | Windsurf
Close tab
Unnamed Group
[SRD-6848] Sidekick SMS issue - Jira
Close tab
[JY-20891] Sidekick SMS issue - Jira
Close tab
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
Close tab
Unnamed Group
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
[JY-20903] Recorded call does not appear on the dashboard - Jira
Close tab
Jiminny
Close tab
Jiminny
Close tab
Jiminny
Close tab
Unnamed Group
[SRD-6853] Moxso - Potential deal stages bug - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Useful commands - Engineering - Confluence
Close tab
Unnamed Group
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
Close tab
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
Close tab
Jiminny
Close tab
New Tab
Close tab
New Tab
Close tab
CloudWatch | us-east-2
Close tab
Companies | All companies
Close tab
Developers | HubSpot
Close tab
Jiminny
Close tab
Feed — jiminny — Sentry
Close tab
Inbox (1,684) - [EMAIL] - Jiminny Mail
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
JY-20493-smart-instant-nudge-pre-filtering ■ 885799
75
75
My Recordings
My Recordings
Everyone's Recordings
Everyone's Recordings
No Recordings
Schedule
Schedule
Invite Notetaker
This Week
This Week
Everyone's Schedule
Everyone's Schedule
Unknown Customer
Backend Chapter
Tomorrow, 10:30 AM
Trending this month
Trending this month
Sort by Sort by: Most played
Sort by
Sort by:
Most played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
2
times played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
1
times played
Unknown Customer
Processing tickets review
Processing tickets review
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Refinement - Processing
Refinement - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Notetaker added by Todor Stamatov
Notetaker added by Todor Stamatov
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Todor Stamatov at Drun Drun Chiki Chiki Bam Bam
Test 42
Test 42
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Unknown Customer
Notetaker added by Mihail Mihaylov
Notetaker added by Mihail Mihaylov
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Kara / James
Kara / James
0
times played
Unknown Customer
Jiminny SF App
Jiminny SF App
0
times played
Unknown Customer
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Laura / James
Laura / James
0
times played
Live Feed
Live Feed
Veselin Kulov
listened to call
7 May, 3:47 PM
activity
with
unknown customer
Held:
7 May, 10:37 AM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
7 May, 11:12 AM
activity
with
unknown customer
Held:
7 May, 10:37 AM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
29 Apr, 2:03 PM
activity
with
unknown customer
Held:
29 Apr, 1:33 PM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
8 Apr, 12:51 AM
activity
with
unknown customer
Held:
8 Apr, 12:36 AM
Duration:
7m
Value:
$0
Nikolay Nikolov
listened to call
27 Feb, 4:38 PM
Web Demo
with
Martin Petkov
Held:
13 Feb, 2:15 PM
Duration:
11m
Value:
$1
Nikolay Yankov
listened to call
12 Feb, 10:07 AM
activity
with
Nikolay Yankov
Held:
12 Feb, 9:42 AM
Duration:
1m
Value:
$0
Nikolay Yankov
listened to call
12 Feb, 10:06 AM
activity
with
Nikolay Yankov
Held:
12 Feb, 9:42 AM
Duration:
1m
Value:
$0
Martin Petkov
listened to call
22 Jan, 1:30 PM
Discovery
with
Martin Petkov
Held:
21 Jan, 4:00 PM
Duration:
35m
Value:
$2
Martin Petkov
listened to call
20 Jan, 11:42 AM
activity
with
Jonathan Heaton
Held:
25 Sep, 2024, 1:33 PM
Duration:
37m
Value:
£8,886
Galya Dimitrova
listened to call
26 Nov, 2025, 1:29 PM
activity
with
David Ehrlich
Held:
9 Oct, 2024, 3:31 PM
Duration:
39m
Value:
$23,000
Aneliya Angelova
listened to call
18 Aug, 2025, 8:45 PM
Cold call
with
Robinson Crusoe New 13333
Held:
23 Jul, 2025, 5:43 PM
Duration:
2m
Value:
$5
Jiminny Web SA
shared in Slack...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45144
|
1621
|
3
|
2026-05-14T14:22:13.747584+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768533747_m1.jpg...
|
Firefox
|
Jiminny — Work
|
1
|
app.staging.jiminny.com/dashboard
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Unnamed Group
Platform Sprint 4 Q2 - Platform Team Unnamed Group
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
Usage | Windsurf
Close tab
Unnamed Group
[SRD-6848] Sidekick SMS issue - Jira
Close tab
[JY-20891] Sidekick SMS issue - Jira
Close tab
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
Close tab
Unnamed Group
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
[JY-20903] Recorded call does not appear on the dashboard - Jira
Close tab
Jiminny
Close tab
Jiminny
Close tab
Jiminny
Close tab
Unnamed Group
[SRD-6853] Moxso - Potential deal stages bug - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Useful commands - Engineering - Confluence
Close tab
Unnamed Group
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
Close tab
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
Close tab
Jiminny
Close tab
New Tab
Close tab
New Tab
Close tab
CloudWatch | us-east-2
Close tab
Companies | All companies
Close tab
Developers | HubSpot
Close tab
Jiminny
Close tab
Feed — jiminny — Sentry
Close tab
Inbox (1,684) - [EMAIL] - Jiminny Mail
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
JY-20493-smart-instant-nudge-pre-filtering ■ 885799
75
75
My Recordings
My Recordings
Everyone's Recordings
Everyone's Recordings
No Recordings
Schedule
Schedule
Invite Notetaker
This Week
This Week
Everyone's Schedule
Everyone's Schedule
Unknown Customer
Backend Chapter
Tomorrow, 10:30 AM
Trending this month
Trending this month
Sort by Sort by: Most played
Sort by
Sort by:
Most played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
2
times played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
1
times played
Unknown Customer
Processing tickets review
Processing tickets review
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Refinement - Processing
Refinement - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Notetaker added by Todor Stamatov
Notetaker added by Todor Stamatov
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Todor Stamatov at Drun Drun Chiki Chiki Bam Bam
Test 42
Test 42
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Unknown Customer
Notetaker added by Mihail Mihaylov
Notetaker added by Mihail Mihaylov
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Kara / James
Kara / James
0
times played
Unknown Customer
Jiminny SF App
Jiminny SF App
0
times played
Unknown Customer
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Laura / James
Laura / James
0
times played
Live Feed
Live Feed
Veselin Kulov
listened to call
7 May, 3:47 PM
activity
with
unknown customer
Held:
7 May, 10:37 AM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
7 May, 11:12 AM
activity
with
unknown customer
Held:
7 May, 10:37 AM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
29 Apr, 2:03 PM
activity
with
unknown customer
Held:
29 Apr, 1:33 PM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
8 Apr, 12:51 AM
activity
with
unknown customer
Held:
8 Apr, 12:36 AM
Duration:
7m
Value:
$0
Nikolay Nikolov
listened to call
27 Feb, 4:38 PM
Web Demo
with
Martin Petkov...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Usage | Windsurf","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6848] Sidekick SMS issue - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20891] Sidekick SMS issue - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20903] Recorded call does not appear on the dashboard - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.033680554,"height":0.045555554},"on_screen":false,"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.0,"width":0.010416667,"height":0.016666668},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.008680556,"top":0.0,"width":0.016666668,"height":0.026666667},"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6853] Moxso - Potential deal stages bug - Jira","depth":4,"bounds":{"left":0.0,"top":0.017222222,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.017222222,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.06277778,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.06277778,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.108333334,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.108333334,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Useful commands - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.1538889,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.1538889,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.008680556,"top":0.20666666,"width":0.016666668,"height":0.026666667},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.23944445,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.23944445,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira","depth":4,"bounds":{"left":0.0,"top":0.285,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.285,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.33055556,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.33055556,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.37611112,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.37611112,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.42166665,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.42166665,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0,"top":0.4672222,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.4672222,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Companies | All companies","depth":4,"bounds":{"left":0.0,"top":0.5127778,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.5127778,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Developers | HubSpot","depth":4,"bounds":{"left":0.0,"top":0.55833334,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.55833334,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.60388887,"width":0.033680554,"height":0.045555554},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.60388887,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.0,"top":0.64944446,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.64944446,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Inbox (1,684) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.695,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.695,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.005902778,"top":0.74666667,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.0,"top":0.7977778,"width":0.033680554,"height":0.043333333},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.0,"top":0.8411111,"width":0.033680554,"height":0.038333334},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.0,"top":0.8794444,"width":0.033680554,"height":0.03888889},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.0,"top":0.91833335,"width":0.033680554,"height":0.038333334},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0,"top":0.95666665,"width":0.033680554,"height":0.043333333},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20493-smart-instant-nudge-pre-filtering ■ 885799","depth":9,"bounds":{"left":0.035069443,"top":0.98055553,"width":0.21875,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"75","depth":12,"bounds":{"left":0.03923611,"top":0.88,"width":0.033333335,"height":0.04888889},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"75","depth":14,"bounds":{"left":0.05659722,"top":0.885,"width":0.009722223,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"My Recordings","depth":14,"bounds":{"left":0.14201389,"top":0.1,"width":0.08055556,"height":0.07333333},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"My Recordings","depth":15,"bounds":{"left":0.14618056,"top":0.1261111,"width":0.072222225,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Everyone's Recordings","depth":14,"bounds":{"left":0.22256945,"top":0.1,"width":0.11736111,"height":0.07333333},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Everyone's Recordings","depth":15,"bounds":{"left":0.22673611,"top":0.1261111,"width":0.10902778,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No Recordings","depth":17,"bounds":{"left":0.209375,"top":0.36055556,"width":0.063541666,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Schedule","depth":13,"bounds":{"left":0.396875,"top":0.36333334,"width":0.050347224,"height":0.037777778},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Schedule","depth":14,"bounds":{"left":0.396875,"top":0.37,"width":0.050347224,"height":0.024444444},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Invite Notetaker","depth":14,"bounds":{"left":0.5888889,"top":0.36,"width":0.09236111,"height":0.04},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXComboBox","text":"This Week","depth":14,"bounds":{"left":0.40729168,"top":0.4288889,"width":0.12847222,"height":0.04111111},"on_screen":true,"value":"This Week","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"This Week","depth":17,"bounds":{"left":0.41493055,"top":0.44,"width":0.045833334,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Everyone's Schedule","depth":14,"bounds":{"left":0.54270834,"top":0.4288889,"width":0.128125,"height":0.04111111},"on_screen":true,"value":"Everyone's Schedule","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Everyone's Schedule","depth":17,"bounds":{"left":0.5503472,"top":0.44,"width":0.088194445,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"bounds":{"left":0.44895834,"top":0.51055557,"width":0.08090278,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Backend Chapter","depth":17,"bounds":{"left":0.44895834,"top":0.5377778,"width":0.06979167,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Tomorrow, 10:30 AM","depth":16,"bounds":{"left":0.44895834,"top":0.565,"width":0.08576389,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Trending this month","depth":13,"bounds":{"left":0.40729168,"top":0.123333335,"width":0.096875,"height":0.026666667},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Trending this month","depth":14,"bounds":{"left":0.40729168,"top":0.1261111,"width":0.096875,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Sort by Sort by: Most played","depth":13,"bounds":{"left":0.55277777,"top":0.116111115,"width":0.11805555,"height":0.04111111},"on_screen":true,"value":"Sort by Sort by: Most played","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Sort by","depth":14,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sort by:","depth":15,"bounds":{"left":0.56041664,"top":0.12722223,"width":0.035416666,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Most played","depth":15,"bounds":{"left":0.59583336,"top":0.12722223,"width":0.054166667,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"bounds":{"left":0.48993057,"top":0.23333333,"width":0.084375,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Veselin Kulov","depth":15,"bounds":{"left":0.45520833,"top":0.25777778,"width":0.16770834,"height":0.022222223},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Veselin Kulov","depth":16,"bounds":{"left":0.47604167,"top":0.26055557,"width":0.12604167,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":16,"bounds":{"left":0.5420139,"top":0.28611112,"width":0.008680556,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"bounds":{"left":0.51944447,"top":0.30666667,"width":0.03888889,"height":0.013888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"bounds":{"left":0.7739583,"top":0.23333333,"width":0.084375,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Veselin Kulov","depth":15,"bounds":{"left":0.7392361,"top":0.25777778,"width":0.16770834,"height":0.022222223},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Veselin Kulov","depth":16,"bounds":{"left":0.76006943,"top":0.26055557,"width":0.12604167,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":16,"bounds":{"left":0.82604164,"top":0.28611112,"width":0.008680556,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"bounds":{"left":0.8034722,"top":0.30666667,"width":0.03888889,"height":0.013888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"bounds":{"left":1.0,"top":0.23333333,"width":-0.05798614,"height":0.017777778},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Processing tickets review","depth":15,"bounds":{"left":1.0,"top":0.25777778,"width":-0.03923607,"height":0.022222223},"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Processing tickets review","depth":16,"bounds":{"left":1.0,"top":0.26055557,"width":-0.06006944,"height":0.016666668},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"bounds":{"left":1.0,"top":0.30666667,"width":-0.087499976,"height":0.013888889},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Planing - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Planing - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Refinement - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Refinement - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Todor Stamatov","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Todor Stamatov","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Stefka / James Weekly","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Stefka / James Weekly","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Robinson Crusoe Cruises Limited","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sprint Review","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sprint Review","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Todor Stamatov at Drun Drun Chiki Chiki Bam Bam","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Test 42","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Test 42","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Robinson Crusoe Cruises Limited","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sprint Review","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sprint Review","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Mihail Mihaylov","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Mihail Mihaylov","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Planing - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Planing - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Stefka / James Weekly","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Stefka / James Weekly","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Kara / James","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Kara / James","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Jiminny SF App","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny SF App","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2026-04-28-jiminny-x-cmbio-at-partnership-kick-off","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2026-04-28-jiminny-x-cmbio-at-partnership-kick-off","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Laura / James","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Laura / James","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Live Feed","depth":13,"bounds":{"left":0.70555556,"top":0.123333335,"width":0.04548611,"height":0.026666667},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Live Feed","depth":14,"bounds":{"left":0.70555556,"top":0.1261111,"width":0.04548611,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Veselin Kulov","depth":18,"bounds":{"left":0.74375,"top":0.19722222,"width":0.059027776,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"bounds":{"left":0.80625,"top":0.19777778,"width":0.05659722,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7 May, 3:47 PM","depth":18,"bounds":{"left":0.74375,"top":0.22,"width":0.060763888,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"bounds":{"left":0.74375,"top":0.26555556,"width":0.030555556,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"bounds":{"left":0.7760417,"top":0.26555556,"width":0.017361112,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"unknown customer","depth":18,"bounds":{"left":0.7951389,"top":0.26555556,"width":0.07777778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"bounds":{"left":0.75972223,"top":0.3188889,"width":0.021527778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7 May, 10:37 AM","depth":18,"bounds":{"left":0.7826389,"top":0.3188889,"width":0.06666667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"bounds":{"left":0.90208334,"top":0.3188889,"width":0.03888889,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4m","depth":18,"bounds":{"left":0.9423611,"top":0.3188889,"width":0.0125,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"bounds":{"left":0.75972223,"top":0.34666666,"width":0.025347222,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"bounds":{"left":0.7864583,"top":0.34666666,"width":0.010416667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Veselin Kulov","depth":18,"bounds":{"left":0.74375,"top":0.41277778,"width":0.059027776,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"bounds":{"left":0.80625,"top":0.41333333,"width":0.05659722,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7 May, 11:12 AM","depth":18,"bounds":{"left":0.74375,"top":0.43555555,"width":0.06666667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"bounds":{"left":0.74375,"top":0.4811111,"width":0.030555556,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"bounds":{"left":0.7760417,"top":0.4811111,"width":0.017361112,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"unknown customer","depth":18,"bounds":{"left":0.7951389,"top":0.4811111,"width":0.07777778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"bounds":{"left":0.75972223,"top":0.53444445,"width":0.021527778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7 May, 10:37 AM","depth":18,"bounds":{"left":0.7826389,"top":0.53444445,"width":0.06666667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"bounds":{"left":0.90208334,"top":0.53444445,"width":0.03888889,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4m","depth":18,"bounds":{"left":0.9423611,"top":0.53444445,"width":0.0125,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"bounds":{"left":0.75972223,"top":0.56222224,"width":0.025347222,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"bounds":{"left":0.7864583,"top":0.56222224,"width":0.010416667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Veselin Kulov","depth":18,"bounds":{"left":0.74375,"top":0.62833333,"width":0.059027776,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"bounds":{"left":0.80625,"top":0.6288889,"width":0.05659722,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"29 Apr, 2:03 PM","depth":18,"bounds":{"left":0.74375,"top":0.6511111,"width":0.063541666,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"bounds":{"left":0.74375,"top":0.69666666,"width":0.030555556,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"bounds":{"left":0.7760417,"top":0.69666666,"width":0.017361112,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"unknown customer","depth":18,"bounds":{"left":0.7951389,"top":0.69666666,"width":0.07777778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"bounds":{"left":0.75972223,"top":0.75,"width":0.021527778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"29 Apr, 1:33 PM","depth":18,"bounds":{"left":0.7826389,"top":0.75,"width":0.063541666,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"bounds":{"left":0.90208334,"top":0.75,"width":0.03888889,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4m","depth":18,"bounds":{"left":0.9423611,"top":0.75,"width":0.0125,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"bounds":{"left":0.75972223,"top":0.7777778,"width":0.025347222,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"bounds":{"left":0.7864583,"top":0.7777778,"width":0.010416667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Veselin Kulov","depth":18,"bounds":{"left":0.74375,"top":0.8438889,"width":0.059027776,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"bounds":{"left":0.80625,"top":0.84444445,"width":0.05659722,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8 Apr, 12:51 AM","depth":18,"bounds":{"left":0.74375,"top":0.8666667,"width":0.06423611,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"bounds":{"left":0.74375,"top":0.9122222,"width":0.030555556,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"bounds":{"left":0.7760417,"top":0.9122222,"width":0.017361112,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"unknown customer","depth":18,"bounds":{"left":0.7951389,"top":0.9122222,"width":0.07777778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"bounds":{"left":0.75972223,"top":0.96555555,"width":0.021527778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8 Apr, 12:36 AM","depth":18,"bounds":{"left":0.7826389,"top":0.96555555,"width":0.06423611,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"bounds":{"left":0.90208334,"top":0.96555555,"width":0.03888889,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7m","depth":18,"bounds":{"left":0.9423611,"top":0.96555555,"width":0.0125,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"bounds":{"left":0.75972223,"top":0.99333334,"width":0.025347222,"height":0.0066666603},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"bounds":{"left":0.7864583,"top":0.99333334,"width":0.010416667,"height":0.0066666603},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Nikolov","depth":18,"bounds":{"left":0.74375,"top":1.0,"width":0.06875,"height":-0.059444427},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"bounds":{"left":0.8159722,"top":1.0,"width":0.056944445,"height":-0.059999943},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"27 Feb, 4:38 PM","depth":18,"bounds":{"left":0.74375,"top":1.0,"width":0.06388889,"height":-0.08222222},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web Demo","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Martin Petkov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
1866336423906054086
|
1588812854445829003
|
visual_change
|
accessibility
|
NULL
|
Unnamed Group
Platform Sprint 4 Q2 - Platform Team Unnamed Group
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
Usage | Windsurf
Close tab
Unnamed Group
[SRD-6848] Sidekick SMS issue - Jira
Close tab
[JY-20891] Sidekick SMS issue - Jira
Close tab
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
Close tab
Unnamed Group
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
[JY-20903] Recorded call does not appear on the dashboard - Jira
Close tab
Jiminny
Close tab
Jiminny
Close tab
Jiminny
Close tab
Unnamed Group
[SRD-6853] Moxso - Potential deal stages bug - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Useful commands - Engineering - Confluence
Close tab
Unnamed Group
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
Close tab
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
Close tab
Jiminny
Close tab
New Tab
Close tab
New Tab
Close tab
CloudWatch | us-east-2
Close tab
Companies | All companies
Close tab
Developers | HubSpot
Close tab
Jiminny
Close tab
Feed — jiminny — Sentry
Close tab
Inbox (1,684) - [EMAIL] - Jiminny Mail
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
JY-20493-smart-instant-nudge-pre-filtering ■ 885799
75
75
My Recordings
My Recordings
Everyone's Recordings
Everyone's Recordings
No Recordings
Schedule
Schedule
Invite Notetaker
This Week
This Week
Everyone's Schedule
Everyone's Schedule
Unknown Customer
Backend Chapter
Tomorrow, 10:30 AM
Trending this month
Trending this month
Sort by Sort by: Most played
Sort by
Sort by:
Most played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
2
times played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
1
times played
Unknown Customer
Processing tickets review
Processing tickets review
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Refinement - Processing
Refinement - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Notetaker added by Todor Stamatov
Notetaker added by Todor Stamatov
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Todor Stamatov at Drun Drun Chiki Chiki Bam Bam
Test 42
Test 42
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Unknown Customer
Notetaker added by Mihail Mihaylov
Notetaker added by Mihail Mihaylov
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Kara / James
Kara / James
0
times played
Unknown Customer
Jiminny SF App
Jiminny SF App
0
times played
Unknown Customer
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Laura / James
Laura / James
0
times played
Live Feed
Live Feed
Veselin Kulov
listened to call
7 May, 3:47 PM
activity
with
unknown customer
Held:
7 May, 10:37 AM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
7 May, 11:12 AM
activity
with
unknown customer
Held:
7 May, 10:37 AM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
29 Apr, 2:03 PM
activity
with
unknown customer
Held:
29 Apr, 1:33 PM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
8 Apr, 12:51 AM
activity
with
unknown customer
Held:
8 Apr, 12:36 AM
Duration:
7m
Value:
$0
Nikolay Nikolov
listened to call
27 Feb, 4:38 PM
Web Demo
with
Martin Petkov...
|
45143
|
NULL
|
NULL
|
NULL
|
|
45145
|
1621
|
4
|
2026-05-14T14:22:14.666885+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768534666_m1.jpg...
|
Firefox
|
Jiminny — Work
|
1
|
app.staging.jiminny.com/dashboard
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Unnamed Group
Platform Sprint 4 Q2 - Platform Team Unnamed Group
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
Usage | Windsurf
Close tab
Unnamed Group
[SRD-6848] Sidekick SMS issue - Jira
Close tab
[JY-20891] Sidekick SMS issue - Jira
Close tab
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
Close tab
Unnamed Group
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
[JY-20903] Recorded call does not appear on the dashboard - Jira
Close tab
Jiminny
Close tab
Jiminny
Close tab
Jiminny
Close tab
Unnamed Group
[SRD-6853] Moxso - Potential deal stages bug - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Useful commands - Engineering - Confluence
Close tab
Unnamed Group
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
Close tab
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
Close tab
Jiminny
Close tab
New Tab
Close tab
New Tab
Close tab
CloudWatch | us-east-2
Close tab
Companies | All companies
Close tab
Developers | HubSpot
Close tab
Jiminny
Close tab
Feed — jiminny — Sentry
Close tab
Inbox (1,684) - [EMAIL] - Jiminny Mail
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
JY-20493-smart-instant-nudge-pre-filtering ■ 885799
75
75
My Recordings...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Usage | Windsurf","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6848] Sidekick SMS issue - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20891] Sidekick SMS issue - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20903] Recorded call does not appear on the dashboard - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.033680554,"height":0.045555554},"on_screen":false,"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.0,"width":0.010416667,"height":0.016666668},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.008680556,"top":0.0,"width":0.016666668,"height":0.026666667},"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6853] Moxso - Potential deal stages bug - Jira","depth":4,"bounds":{"left":0.0,"top":0.017222222,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.017222222,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.06277778,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.06277778,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.108333334,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.108333334,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Useful commands - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.1538889,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.1538889,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.008680556,"top":0.20666666,"width":0.016666668,"height":0.026666667},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.23944445,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.23944445,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira","depth":4,"bounds":{"left":0.0,"top":0.285,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.285,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.33055556,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.33055556,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.37611112,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.37611112,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.42166665,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.42166665,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0,"top":0.4672222,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.4672222,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Companies | All companies","depth":4,"bounds":{"left":0.0,"top":0.5127778,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.5127778,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Developers | HubSpot","depth":4,"bounds":{"left":0.0,"top":0.55833334,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.55833334,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.60388887,"width":0.033680554,"height":0.045555554},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.60388887,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.0,"top":0.64944446,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.64944446,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Inbox (1,684) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.695,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.695,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.005902778,"top":0.74666667,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.0,"top":0.7977778,"width":0.033680554,"height":0.043333333},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.0,"top":0.8411111,"width":0.033680554,"height":0.038333334},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.0,"top":0.8794444,"width":0.033680554,"height":0.03888889},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.0,"top":0.91833335,"width":0.033680554,"height":0.038333334},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0,"top":0.95666665,"width":0.033680554,"height":0.043333333},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20493-smart-instant-nudge-pre-filtering ■ 885799","depth":9,"bounds":{"left":0.035069443,"top":0.98055553,"width":0.21875,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"75","depth":12,"bounds":{"left":0.03923611,"top":0.88,"width":0.033333335,"height":0.04888889},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"75","depth":14,"bounds":{"left":0.05659722,"top":0.885,"width":0.009722223,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"My Recordings","depth":14,"bounds":{"left":0.14201389,"top":0.1,"width":0.08055556,"height":0.07333333},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true}]...
|
3821918636809896399
|
1613786588277663586
|
click
|
hybrid
|
NULL
|
Unnamed Group
Platform Sprint 4 Q2 - Platform Team Unnamed Group
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
Usage | Windsurf
Close tab
Unnamed Group
[SRD-6848] Sidekick SMS issue - Jira
Close tab
[JY-20891] Sidekick SMS issue - Jira
Close tab
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
Close tab
Unnamed Group
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
[JY-20903] Recorded call does not appear on the dashboard - Jira
Close tab
Jiminny
Close tab
Jiminny
Close tab
Jiminny
Close tab
Unnamed Group
[SRD-6853] Moxso - Potential deal stages bug - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Useful commands - Engineering - Confluence
Close tab
Unnamed Group
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
Close tab
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
Close tab
Jiminny
Close tab
New Tab
Close tab
New Tab
Close tab
CloudWatch | us-east-2
Close tab
Companies | All companies
Close tab
Developers | HubSpot
Close tab
Jiminny
Close tab
Feed — jiminny — Sentry
Close tab
Inbox (1,684) - [EMAIL] - Jiminny Mail
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
JY-20493-smart-instant-nudge-pre-filtering ■ 885799
75
75
My Recordings
FirefoxFileEditViewHistoryBookmarksCProfilesTools WindowHelp= app.staging.jiminny.com/dashboard• 80 lbl100% C 8• Thu 14 May 17:22:14=My Recordings Everyone's RecordingsTrending this monthSort by: Mos...D1IOUnknown Customer MNotetaker added by Veselin KulovD 2times playedNo RecordingsScheduleThis WeekInvite NotetakerEveryone's Schedule•Unknown CustomerBackend ChapterTomorrow, 10:30 AM|M+Live FeedVeselin Kulov listened to call 607 May, 3:47 PMactivity with unknown customerHeld: 7 May, 10:37 AM≥ Value: $0Veselin Kulov listened to call 6д7 May, 11:12 AMactivity with unknown customerHeld: 7 May, 10:37 AM§ Value: $0Veselin Kulov listened to call 6d29 Apr, 2:03 PMactivity with unknown customerHeld: 29 Apr, 1:33 PMValue: $0Veselin Kulov listened to call 608 Apr, 12:51 AMactivity with unknown customerỞ Duration: 4mỞ Duration: 4mỞ Duration: 4mSettingsJY-20493-smart-instant-nudge-pre-filtering = 885799...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45146
|
1622
|
1
|
2026-05-14T14:22:14.666915+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768534666_m2.jpg...
|
Firefox
|
Jiminny — Work
|
1
|
app.staging.jiminny.com/dashboard
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Unnamed Group
Platform Sprint 4 Q2 - Platform Team Unnamed Group
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
Usage | Windsurf
Close tab
Unnamed Group
[SRD-6848] Sidekick SMS issue - Jira
Close tab
[JY-20891] Sidekick SMS issue - Jira
Close tab
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
Close tab
Unnamed Group
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
[JY-20903] Recorded call does not appear on the dashboard - Jira
Close tab
Jiminny
Close tab
Jiminny
Close tab
Jiminny
Close tab
Unnamed Group
[SRD-6853] Moxso - Potential deal stages bug - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Useful commands - Engineering - Confluence
Close tab
Unnamed Group
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
Close tab
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
Close tab
Jiminny
Close tab
New Tab
Close tab
New Tab
Close tab
CloudWatch | us-east-2
Close tab
Companies | All companies
Close tab
Developers | HubSpot
Close tab
Jiminny
Close tab
Feed — jiminny — Sentry
Close tab
Inbox (1,684) - [EMAIL] - Jiminny Mail
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
JY-20493-smart-instant-nudge-pre-filtering ■ 885799
75
75
My Recordings
My Recordings
Everyone's Recordings
Everyone's Recordings
No Recordings
Schedule
Schedule
Invite Notetaker
This Week
This Week
Everyone's Schedule
Everyone's Schedule
Unknown Customer
Backend Chapter
Tomorrow, 10:30 AM
Trending this month
Trending this month
Sort by Sort by: Most played
Sort by
Sort by:
Most played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
2
times played...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.27443483,"top":0.509976,"width":0.007978723,"height":0.01915403},"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.27027926,"top":0.53351957,"width":0.016123671,"height":0.032721467},"on_screen":false,"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.27094415,"top":0.53351957,"width":0.004986702,"height":0.011971269},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app","depth":4,"bounds":{"left":0.27027926,"top":0.566241,"width":0.016123671,"height":0.032721467},"on_screen":false,"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.27094415,"top":0.566241,"width":0.004986702,"height":0.011971269},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":4,"bounds":{"left":0.27027926,"top":0.5989625,"width":0.016123671,"height":0.032721467},"on_screen":false,"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.27094415,"top":0.5989625,"width":0.004986702,"height":0.011971269},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Usage | Windsurf","depth":4,"bounds":{"left":0.27027926,"top":0.63168395,"width":0.016123671,"height":0.032721467},"on_screen":false,"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.27094415,"top":0.63168395,"width":0.004986702,"height":0.011971269},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.27443483,"top":0.669593,"width":0.007978723,"height":0.01915403},"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6848] Sidekick SMS issue - Jira","depth":4,"bounds":{"left":0.27027926,"top":0.69313645,"width":0.016123671,"height":0.032721467},"on_screen":false,"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.27094415,"top":0.69313645,"width":0.004986702,"height":0.011971269},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20891] Sidekick SMS issue - Jira","depth":4,"bounds":{"left":0.27027926,"top":0.7258579,"width":0.016123671,"height":0.032721467},"on_screen":false,"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.27094415,"top":0.7258579,"width":0.004986702,"height":0.011971269},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app","depth":4,"bounds":{"left":0.27027926,"top":0.75857943,"width":0.016123671,"height":0.032721467},"on_screen":false,"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.27094415,"top":0.75857943,"width":0.004986702,"height":0.011971269},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.27443483,"top":0.7964884,"width":0.007978723,"height":0.01915403},"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":4,"bounds":{"left":0.27027926,"top":0.82003194,"width":0.016123671,"height":0.032721467},"on_screen":false,"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.27094415,"top":0.82003194,"width":0.004986702,"height":0.011971269},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20903] Recorded call does not appear on the dashboard - Jira","depth":4,"bounds":{"left":0.27027926,"top":0.8527534,"width":0.016123671,"height":0.032721467},"on_screen":false,"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.27094415,"top":0.8527534,"width":0.004986702,"height":0.011971269},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.27027926,"top":0.88547486,"width":0.016123671,"height":0.032721467},"on_screen":false,"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.27094415,"top":0.88547486,"width":0.004986702,"height":0.011971269},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.27027926,"top":0.9181963,"width":0.016123671,"height":0.032721467},"on_screen":false,"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.27094415,"top":0.9181963,"width":0.004986702,"height":0.011971269},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.27027926,"top":0.9509178,"width":0.016123671,"height":0.032721467},"on_screen":false,"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.27094415,"top":0.9509178,"width":0.004986702,"height":0.011971269},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.27443483,"top":0.9888268,"width":0.007978723,"height":0.011173189},"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6853] Moxso - Potential deal stages bug - Jira","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.016123671,"height":-0.012370348},"on_screen":true,"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.27094415,"top":1.0,"width":0.004986702,"height":-0.012370348},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.016123671,"height":-0.04509175},"on_screen":true,"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.27094415,"top":1.0,"width":0.004986702,"height":-0.04509175},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.016123671,"height":-0.07781327},"on_screen":true,"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.27094415,"top":1.0,"width":0.004986702,"height":-0.07781327},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Useful commands - Engineering - Confluence","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Companies | All companies","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Developers | HubSpot","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Inbox (1,684) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20493-smart-instant-nudge-pre-filtering ■ 885799","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"75","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"75","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"My Recordings","depth":14,"bounds":{"left":0.3382646,"top":1.0,"width":0.03856383,"height":-0.07182765},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"My Recordings","depth":15,"bounds":{"left":0.3402593,"top":1.0,"width":0.034574468,"height":-0.09058261},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Everyone's Recordings","depth":14,"bounds":{"left":0.37682846,"top":1.0,"width":0.05618351,"height":-0.07182765},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Everyone's Recordings","depth":15,"bounds":{"left":0.37882313,"top":1.0,"width":0.05219415,"height":-0.09058261},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No Recordings","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Schedule","depth":13,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Schedule","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Invite Notetaker","depth":14,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXComboBox","text":"This Week","depth":14,"on_screen":true,"value":"This Week","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"This Week","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Everyone's Schedule","depth":14,"on_screen":true,"value":"Everyone's Schedule","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Everyone's Schedule","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Backend Chapter","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Tomorrow, 10:30 AM","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Trending this month","depth":13,"bounds":{"left":0.4652593,"top":1.0,"width":0.04637633,"height":-0.0885874},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Trending this month","depth":14,"bounds":{"left":0.4652593,"top":1.0,"width":0.04637633,"height":-0.09058261},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Sort by Sort by: Most played","depth":13,"bounds":{"left":0.5349069,"top":1.0,"width":0.05651596,"height":-0.08339989},"on_screen":true,"value":"Sort by Sort by: Most played","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Sort by","depth":14,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sort by:","depth":15,"bounds":{"left":0.53856385,"top":1.0,"width":0.016954787,"height":-0.091380715},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Most played","depth":15,"bounds":{"left":0.5555186,"top":1.0,"width":0.025930852,"height":-0.091380715},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Veselin Kulov","depth":15,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Veselin Kulov","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-2434759482289430340
|
1613787137765042146
|
click
|
hybrid
|
NULL
|
Unnamed Group
Platform Sprint 4 Q2 - Platform Team Unnamed Group
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
Usage | Windsurf
Close tab
Unnamed Group
[SRD-6848] Sidekick SMS issue - Jira
Close tab
[JY-20891] Sidekick SMS issue - Jira
Close tab
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
Close tab
Unnamed Group
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
[JY-20903] Recorded call does not appear on the dashboard - Jira
Close tab
Jiminny
Close tab
Jiminny
Close tab
Jiminny
Close tab
Unnamed Group
[SRD-6853] Moxso - Potential deal stages bug - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Useful commands - Engineering - Confluence
Close tab
Unnamed Group
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
Close tab
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
Close tab
Jiminny
Close tab
New Tab
Close tab
New Tab
Close tab
CloudWatch | us-east-2
Close tab
Companies | All companies
Close tab
Developers | HubSpot
Close tab
Jiminny
Close tab
Feed — jiminny — Sentry
Close tab
Inbox (1,684) - [EMAIL] - Jiminny Mail
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
JY-20493-smart-instant-nudge-pre-filtering ■ 885799
75
75
My Recordings
My Recordings
Everyone's Recordings
Everyone's Recordings
No Recordings
Schedule
Schedule
Invite Notetaker
This Week
This Week
Everyone's Schedule
Everyone's Schedule
Unknown Customer
Backend Chapter
Tomorrow, 10:30 AM
Trending this month
Trending this month
Sort by Sort by: Most played
Sort by
Sort by:
Most played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
2
times played
ActivityFilesLaterJiminny... ~@ jiminny-x-integrati& platform-inner-team© Channels# ai-chapter# alertsi backend# bugscontusion-cllnia# curiosity_lab# engineering# general# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the people of iimi..• Direct messages8. A...• A3 62P. Galya Dimitrova Mvasil VasilevA. Stefka Stoyanova%: Todor Stamatovf. Mario GeorgievP. Nikolay Ivanov2o James Graham "2. Stoyan Tanev. Steliyan Georgiev. Petko Kashinski*. Lukas Kovali...a: Apps® ToastS lira Gloud6 Huddle with Aneliya Angelova& R. Aneliya Angelova •• Messagest Add canvasur FilesAneliya Angelova 2:30 PMЛукаш, само при Сейлсфорс и Хубспот се синкваха активити типовете, когато се направи плейбук,нали?пои останалите скМі тоябва оъчно ла се въвелат.Lukas Kovalik 2:47 PMтряова да се за всички, някьде не се ли попьлвапри зохо май беше hardcoded но май и там си връщаха две категорииAneliya Angelova @ 4:00 PMЛукаш имаш ли време да се чуем за тестването на https://jiminny.atlassian.net/browse/JY-20725X Preview insackt[HubSpot] Optimise CRM rematctC OpenReady for QA- MediumAA Aneliya AngelovaAs of today at 4:00 PM RetreshOpen in Jira+ SummariseLukas Kovalik 4:02 PMzean-iurwiniareelYou joined the huddle LIVE 4:06 PMAneliva Angelova is here toolAneliya Angelova 4:44 PM11512582Lukas Kovalik O 5:16 PMcurl --location "httns:ani.hubani.com/crm/v3/obiects/contacts/search--header 'Content-Type: application/json'\--header "Authorization: BearerC|Kvma?iMx7OINOM8kOFwrAowAcAkUAhIR24?A05D?xiVrIYMILuP6SA01Kwcmhr=n.v2 hytorDniMvDalYck-CMalovolNOM9kOFwrAUAcrkcaws-ThwRARIRAOF-ATFSAOFRAO-ROIEBAQUBEggBAQEBAYICFIґ7Au8O2Nwal9YW2CbCCxYvwngmSgNldTFSAFoAYABoAHAAeAA'\--data "{ "limit", 13'Message Aneliva Angelova+ AaIAl Notes: OffLeave~ (Q SearchDate ModifiedYesterdav at 13.33Yesterday at 13:33Yesterday at 13.32Yesterdav at 13:31Yesterday at 13:30Yesterdav at 13:30Yesterday at 13:29Yesterday at 13.28Yesterday at 13:28resterday at 15.21Yesterdav at 13:26Yesterday at 13:26Yesterday at 13:25Yesterday at 13:23Yesterday at 13:24resterday at 13-43Yesterdav at 13:22Yesterday at 13:20Yesterday at 13:20Yesterday at 13.19Yesterdav at 13:18Yesterday at 13:18Yesterday at 13:17Yesterday at 13:17Yesterday at 13:16Yesterday at 13.15Yecterdav at 12:15Yesterday at 13:14Yesterday at 13-14Yesterdav at 13:13Yesterday at 13:13Yesterday at 13:12Yecterdav at 12:11Yesterday at 13:10Yesterdav at 13:10Yesterday at 13:09Yesterday at 13:08Yoctorday at 12:09Yesterday at 13.0/Yocterdav at 13:06Yesterday at 13:06Yesterdav at 13:05MPEG-4 movie12 KBMPEG-4 movie9 KBMPEG-4 movieMPEG-4 movie8 KE37 KB10 KBMPEG-4 movie7 K:8 KBMPEG-4 movie9 KBMPEG-4 movie72 KBMPEG-4 movie14 KB15 KbMPEG-4 movie9 K:18 KBMPEG-4 movie12 KB10 K:16 KRIMPEG-4 movieMPEG-4 movie6 KBMPEG-4 movie12 KE23 KBMPEG-4 movie8 KB6 KB1MPEG-4 movie6 KBMPEG-4 movie11 KB11 KBMPEG-4 movie20 KEMPEG-4 movie34 KBMPEG-4 movie10 KB7K:MPEG-4 movie5 KBMPEG-4 movie11 KB26 KBMPEG-4 movie111 KPMPEG-4 movie102 KBMPEG-4 movie88 KB59 K:MPEG-4 movie98 KBMPEG-4 movie97 KB66 KBЛAKEMPEG-4 movieMDEG-A movie93 KB78 KBMPEG-4 movie50 K:MPEG-A movid58 KB MPEG-4 movie27 KB7 KB112 KрMPEG-4 movieMPEG-4 movie32 KB17 KBMPEG-4 movie19 Kг32 KBMPEG-4 movie10 KB24 K8MPEG-4 movieFavouritesE jiminny© Recents* ApplicationsiCloudiCloud Drive228 Sync folderQ DXP4800PLUS-B5FA@ Network|• CRMI• Orange• Red• Yellow• Green• Purple•) All lags..lohlDownloadsNameLoom.pkgAlfred copv.alfredoreterencesB KeychronAssist-1.0.2 (1).dmgA Keychron Assist-1.0.2.dmgmazanoke-images-ywJo.ziPhotos-3-001.zipD Transcript.pdf→mage U.loge1 Orioninstaller.dma- image (2).1pc• ПО-22221726037035-004-001_ORGES.pdf• %D0%9F%D0%9E-22221726037035-004-001_archive.zipПO-22221726037035-004-001_archive (1).zip• repon 4).XmAltred copy2.altredoreterences05012026_0000000026574472_ SWIFT_OB70501260015890.pdf27022026_0000000026574472_SWIFT_OB72702260049200.pdf= 03042026 [CREDIT_CARD] SWIFT [CREDIT_CARD].001B reporti) xm=pdt.odipdf-1.pdfD pdf-2.pdf-pdf-5.pd1= ndf-1 ndipdf-3.pdfB Rovaix Famly Treo gedbitwarden export 20251031122528.isonKoválik Family Tree.zip*macOS Storage_Cleanup.mdal favicon icofirst_aid_notes_complete.docxrenortl2).esvconfig.ymlIteration run Search HS.postman_collection.json--report(1).csvm licence hettertouchtoalMariusHosting Config.json1ooks-891a6503-bbb7-4b2b-9c3.csv•Alfredmazanoke-images-YWJ6ана Ковалик.jpg•искане даниел Ковалик..pg• Фактура Март Даниел Ковалик.jрс• Фактура Април Даниел Ковалик.jpg• Dhotac 2.001Q SearchKind00,4 MDinstdlle..dckage55.9 MBAlfred...ferences10,1 MBDisk Image10,1 MBDisk ImageIL MBLiP archive6,6 MBZIP archive2,5 MB PDF Document2,5 MBJreo lmage2.2 MBDisk Image2,2 MB PDF Document2 MBJPEG image1,9 MBJPEG imaqe192 KBPDF Document140 KbZIP archive148 KВZIP archivel148 KBZIP archive122 KBXML document111 KBAlfred...ferences94 KBPDF Document92 KBPDr DocumentK:91 KB91 KB30 KB29 KBPDF DocumentXML documentPDF Document28 KBPDF Document28 KR28 KB27 KB14 KB11 KB6 KB6KBPDE DocumentDocumentJSONICSV DocumentZIP archiveMarkdo…..ument5KRWindo...n image4 KB3 KBword ..cumentCSV Document2 KBYAML document1 KBcSV Document928 byteshttlicence183 bytesZero butesJSONAlfred.. ferencesZero bytesFolde1,9 MB1,8 MB17 MB1,7 MBColdorJPEG imageJPEG ImageIPEG imadeJPEG imageColdo1 of 58 selected, 9.64 GB availabld• Inu 14 Mаy 1/•22-14Date AddedIs Mdl ZUzo dl 19:4530 Jan 2026 at 12:3617 Mar 2026 at 20:2717 Mar 2026 at 20:26Z3 Aor 2020 al 13:0229 Jan 2026 at 15:2019 Dec 2025 at 10:1619 Dec 2025 at 12:238 Aor 2026 at 20:3519 Dec 2025 at 10:2919 Dec 2025 at 12:1819 Dec 2025 at 12:4026 Mar 2026 at 11:2410 May 2026 at 13:5326 Mar 2026 at 11:2426 Mar 2026 at 11:2426 Mar 2026 at 11:2310 May 2026 at 14:3730 Jan 2026 at 12:3713 Feb 2026 at 11:54Z3 Apr 2026 at 13.0823 Aor 2026 at 13:0810 May 2026 at 13:5410 May 2026 at 14:3710 May 2026 at 13:4910 May 2026 at 13:5010 May 2026 at 13:5110 Mav 2026 at 12:51110 May 2026 at 13:5019 Dec 2025 at 11:3431 Oct 2025 at 12:2525 Nov 2025 at 17:596 Mar 2026 at 11.2224 Anr 2026 at 16:5220 Oct 2025 at 11:0218 Mar 2026 at 15:29• Mav 2026 at 11:09129 Oct 2025 at 19:329 May 2026 at 10:0418 Mar 2026 at 11:5510 May 2026 at 13:5712 Jun 2025 at 19:0430 Jan 2026 at 12:3630 Jan 2026 at 12:3616 Oct 2025 aт 16:0123 Apr 2026 at 13:0223 Apr 2026 at 13:0223 Apr 2026 at 13:0222 Anr 2026 at 12:0223 Apr 2026 at 13:0229 Jan 2026 at 15:20...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45147
|
1621
|
5
|
2026-05-14T14:22:19.815290+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768539815_m1.jpg...
|
Firefox
|
Jiminny — Work
|
1
|
app.staging.jiminny.com/dashboard
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Unnamed Group
Platform Sprint 4 Q2 - Platform Team Unnamed Group
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
Usage | Windsurf
Close tab
Unnamed Group
[SRD-6848] Sidekick SMS issue - Jira
Close tab
[JY-20891] Sidekick SMS issue - Jira
Close tab
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
Close tab
Unnamed Group
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
[JY-20903] Recorded call does not appear on the dashboard - Jira
Close tab
Jiminny
Close tab
Jiminny
Close tab
Jiminny
Close tab
Unnamed Group
[SRD-6853] Moxso - Potential deal stages bug - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Useful commands - Engineering - Confluence
Close tab
Unnamed Group
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
Close tab
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
Close tab
Jiminny
Close tab
New Tab
Close tab
New Tab
Close tab
CloudWatch | us-east-2
Close tab
Companies | All companies
Close tab
Developers | HubSpot
Close tab
Jiminny
Close tab
Feed — jiminny — Sentry
Close tab
Inbox (1,684) - [EMAIL] - Jiminny Mail
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
JY-20493-smart-instant-nudge-pre-filtering ■ 885799
75
75
My Recordings
My Recordings
Everyone's Recordings
Everyone's Recordings
No Recordings
Schedule
Schedule
Invite Notetaker
This Week
This Week
Everyone's Schedule
Everyone's Schedule
Unknown Customer
Backend Chapter
Tomorrow, 10:30 AM
Trending this month
Trending this month
Sort by Sort by: Most played
Sort by
Sort by:
Most played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
2
times played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
1
times played
Unknown Customer
Processing tickets review
Processing tickets review
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Refinement - Processing
Refinement - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Notetaker added by Todor Stamatov
Notetaker added by Todor Stamatov
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Todor Stamatov at Drun Drun Chiki Chiki Bam Bam
Test 42
Test 42
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Unknown Customer
Notetaker added by Mihail Mihaylov
Notetaker added by Mihail Mihaylov
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Kara / James
Kara / James
0
times played
Unknown Customer
Jiminny SF App
Jiminny SF App
0
times played
Unknown Customer
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Laura / James
Laura / James
0
times played
Live Feed
Live Feed
Veselin Kulov
listened to call
7 May, 3:47 PM
activity
with
unknown customer
Held:
7 May, 10:37 AM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
7 May, 11:12 AM
activity
with
unknown customer
Held:
7 May, 10:37 AM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
29 Apr, 2:03 PM
activity
with
unknown customer
Held:
29 Apr, 1:33 PM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
8 Apr, 12:51 AM
activity
with
unknown customer
Held:
8 Apr, 12:36 AM
Duration:
7m
Value:
$0
Nikolay Nikolov
listened to call
27 Feb, 4:38 PM
Web Demo
with
Martin Petkov
Held:
13 Feb, 2:15 PM
Duration:
11m
Value:
$1
Nikolay Yankov
listened to call
12 Feb, 10:07 AM
activity
with
Nikolay Yankov
Held:
12 Feb, 9:42 AM
Duration:
1m
Value:
$0
Nikolay Yankov
listened to call
12 Feb, 10:06 AM
activity
with
Nikolay Yankov
Held:
12 Feb, 9:42 AM
Duration:
1m
Value:
$0
Martin Petkov
listened to call
22 Jan, 1:30 PM
Discovery
with
Martin Petkov
Held:
21 Jan, 4:00 PM
Duration:
35m
Value:
$2
Martin Petkov
listened to call
20 Jan, 11:42 AM
activity
with...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Usage | Windsurf","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6848] Sidekick SMS issue - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20891] Sidekick SMS issue - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20903] Recorded call does not appear on the dashboard - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.033680554,"height":0.045555554},"on_screen":false,"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.0,"width":0.010416667,"height":0.016666668},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.008680556,"top":0.0,"width":0.016666668,"height":0.026666667},"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6853] Moxso - Potential deal stages bug - Jira","depth":4,"bounds":{"left":0.0,"top":0.017222222,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.017222222,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.06277778,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.06277778,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.108333334,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.108333334,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Useful commands - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.1538889,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.1538889,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.008680556,"top":0.20666666,"width":0.016666668,"height":0.026666667},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.23944445,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.23944445,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira","depth":4,"bounds":{"left":0.0,"top":0.285,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.285,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.33055556,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.33055556,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.37611112,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.37611112,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.42166665,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.42166665,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0,"top":0.4672222,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.4672222,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Companies | All companies","depth":4,"bounds":{"left":0.0,"top":0.5127778,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.5127778,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Developers | HubSpot","depth":4,"bounds":{"left":0.0,"top":0.55833334,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.55833334,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.60388887,"width":0.033680554,"height":0.045555554},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.60388887,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.0,"top":0.64944446,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.64944446,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Inbox (1,684) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.695,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.695,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.005902778,"top":0.74666667,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.0,"top":0.7977778,"width":0.033680554,"height":0.043333333},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.0,"top":0.8411111,"width":0.033680554,"height":0.038333334},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.0,"top":0.8794444,"width":0.033680554,"height":0.03888889},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.0,"top":0.91833335,"width":0.033680554,"height":0.038333334},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0,"top":0.95666665,"width":0.033680554,"height":0.043333333},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20493-smart-instant-nudge-pre-filtering ■ 885799","depth":9,"bounds":{"left":0.035069443,"top":0.98055553,"width":0.21875,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"75","depth":12,"bounds":{"left":0.03923611,"top":0.88,"width":0.033333335,"height":0.04888889},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"75","depth":14,"bounds":{"left":0.05659722,"top":0.885,"width":0.009722223,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"My Recordings","depth":14,"bounds":{"left":0.14201389,"top":0.1,"width":0.08055556,"height":0.07333333},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"My Recordings","depth":15,"bounds":{"left":0.14618056,"top":0.1261111,"width":0.072222225,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Everyone's Recordings","depth":14,"bounds":{"left":0.22256945,"top":0.1,"width":0.11736111,"height":0.07333333},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Everyone's Recordings","depth":15,"bounds":{"left":0.22673611,"top":0.1261111,"width":0.10902778,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No Recordings","depth":17,"bounds":{"left":0.209375,"top":0.36055556,"width":0.063541666,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Schedule","depth":13,"bounds":{"left":0.396875,"top":0.36333334,"width":0.050347224,"height":0.037777778},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Schedule","depth":14,"bounds":{"left":0.396875,"top":0.37,"width":0.050347224,"height":0.024444444},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Invite Notetaker","depth":14,"bounds":{"left":0.5888889,"top":0.36,"width":0.09236111,"height":0.04},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXComboBox","text":"This Week","depth":14,"bounds":{"left":0.40729168,"top":0.4288889,"width":0.12847222,"height":0.04111111},"on_screen":true,"value":"This Week","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"This Week","depth":17,"bounds":{"left":0.41493055,"top":0.44,"width":0.045833334,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Everyone's Schedule","depth":14,"bounds":{"left":0.54270834,"top":0.4288889,"width":0.128125,"height":0.04111111},"on_screen":true,"value":"Everyone's Schedule","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Everyone's Schedule","depth":17,"bounds":{"left":0.5503472,"top":0.44,"width":0.088194445,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"bounds":{"left":0.44895834,"top":0.51055557,"width":0.08090278,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Backend Chapter","depth":17,"bounds":{"left":0.44895834,"top":0.5377778,"width":0.06979167,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Tomorrow, 10:30 AM","depth":16,"bounds":{"left":0.44895834,"top":0.565,"width":0.08576389,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Trending this month","depth":13,"bounds":{"left":0.40729168,"top":0.123333335,"width":0.096875,"height":0.026666667},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Trending this month","depth":14,"bounds":{"left":0.40729168,"top":0.1261111,"width":0.096875,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Sort by Sort by: Most played","depth":13,"bounds":{"left":0.55277777,"top":0.116111115,"width":0.11805555,"height":0.04111111},"on_screen":true,"value":"Sort by Sort by: Most played","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Sort by","depth":14,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sort by:","depth":15,"bounds":{"left":0.56041664,"top":0.12722223,"width":0.035416666,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Most played","depth":15,"bounds":{"left":0.59583336,"top":0.12722223,"width":0.054166667,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"bounds":{"left":0.48993057,"top":0.23333333,"width":0.084375,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Veselin Kulov","depth":15,"bounds":{"left":0.45520833,"top":0.25777778,"width":0.16770834,"height":0.022222223},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Veselin Kulov","depth":16,"bounds":{"left":0.47604167,"top":0.26055557,"width":0.12604167,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":16,"bounds":{"left":0.5420139,"top":0.28611112,"width":0.008680556,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"bounds":{"left":0.51944447,"top":0.30666667,"width":0.03888889,"height":0.013888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"bounds":{"left":0.7739583,"top":0.23333333,"width":0.084375,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Veselin Kulov","depth":15,"bounds":{"left":0.7392361,"top":0.25777778,"width":0.16770834,"height":0.022222223},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Veselin Kulov","depth":16,"bounds":{"left":0.76006943,"top":0.26055557,"width":0.12604167,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":16,"bounds":{"left":0.82604164,"top":0.28611112,"width":0.008680556,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"bounds":{"left":0.8034722,"top":0.30666667,"width":0.03888889,"height":0.013888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"bounds":{"left":1.0,"top":0.23333333,"width":-0.05798614,"height":0.017777778},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Processing tickets review","depth":15,"bounds":{"left":1.0,"top":0.25777778,"width":-0.03923607,"height":0.022222223},"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Processing tickets review","depth":16,"bounds":{"left":1.0,"top":0.26055557,"width":-0.06006944,"height":0.016666668},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"bounds":{"left":1.0,"top":0.30666667,"width":-0.087499976,"height":0.013888889},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Planing - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Planing - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Refinement - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Refinement - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Todor Stamatov","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Todor Stamatov","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Stefka / James Weekly","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Stefka / James Weekly","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Robinson Crusoe Cruises Limited","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sprint Review","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sprint Review","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Todor Stamatov at Drun Drun Chiki Chiki Bam Bam","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Test 42","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Test 42","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Robinson Crusoe Cruises Limited","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sprint Review","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sprint Review","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Mihail Mihaylov","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Mihail Mihaylov","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Planing - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Planing - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Stefka / James Weekly","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Stefka / James Weekly","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Kara / James","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Kara / James","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Jiminny SF App","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny SF App","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2026-04-28-jiminny-x-cmbio-at-partnership-kick-off","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2026-04-28-jiminny-x-cmbio-at-partnership-kick-off","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Laura / James","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Laura / James","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Live Feed","depth":13,"bounds":{"left":0.70555556,"top":0.123333335,"width":0.04548611,"height":0.026666667},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Live Feed","depth":14,"bounds":{"left":0.70555556,"top":0.1261111,"width":0.04548611,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Veselin Kulov","depth":18,"bounds":{"left":0.74375,"top":0.19722222,"width":0.059027776,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"bounds":{"left":0.80625,"top":0.19777778,"width":0.05659722,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7 May, 3:47 PM","depth":18,"bounds":{"left":0.74375,"top":0.22,"width":0.060763888,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"bounds":{"left":0.74375,"top":0.26555556,"width":0.030555556,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"bounds":{"left":0.7760417,"top":0.26555556,"width":0.017361112,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"unknown customer","depth":18,"bounds":{"left":0.7951389,"top":0.26555556,"width":0.07777778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"bounds":{"left":0.75972223,"top":0.3188889,"width":0.021527778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7 May, 10:37 AM","depth":18,"bounds":{"left":0.7826389,"top":0.3188889,"width":0.06666667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"bounds":{"left":0.90208334,"top":0.3188889,"width":0.03888889,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4m","depth":18,"bounds":{"left":0.9423611,"top":0.3188889,"width":0.0125,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"bounds":{"left":0.75972223,"top":0.34666666,"width":0.025347222,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"bounds":{"left":0.7864583,"top":0.34666666,"width":0.010416667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Veselin Kulov","depth":18,"bounds":{"left":0.74375,"top":0.41277778,"width":0.059027776,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"bounds":{"left":0.80625,"top":0.41333333,"width":0.05659722,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7 May, 11:12 AM","depth":18,"bounds":{"left":0.74375,"top":0.43555555,"width":0.06666667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"bounds":{"left":0.74375,"top":0.4811111,"width":0.030555556,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"bounds":{"left":0.7760417,"top":0.4811111,"width":0.017361112,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"unknown customer","depth":18,"bounds":{"left":0.7951389,"top":0.4811111,"width":0.07777778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"bounds":{"left":0.75972223,"top":0.53444445,"width":0.021527778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7 May, 10:37 AM","depth":18,"bounds":{"left":0.7826389,"top":0.53444445,"width":0.06666667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"bounds":{"left":0.90208334,"top":0.53444445,"width":0.03888889,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4m","depth":18,"bounds":{"left":0.9423611,"top":0.53444445,"width":0.0125,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"bounds":{"left":0.75972223,"top":0.56222224,"width":0.025347222,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"bounds":{"left":0.7864583,"top":0.56222224,"width":0.010416667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Veselin Kulov","depth":18,"bounds":{"left":0.74375,"top":0.62833333,"width":0.059027776,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"bounds":{"left":0.80625,"top":0.6288889,"width":0.05659722,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"29 Apr, 2:03 PM","depth":18,"bounds":{"left":0.74375,"top":0.6511111,"width":0.063541666,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"bounds":{"left":0.74375,"top":0.69666666,"width":0.030555556,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"bounds":{"left":0.7760417,"top":0.69666666,"width":0.017361112,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"unknown customer","depth":18,"bounds":{"left":0.7951389,"top":0.69666666,"width":0.07777778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"bounds":{"left":0.75972223,"top":0.75,"width":0.021527778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"29 Apr, 1:33 PM","depth":18,"bounds":{"left":0.7826389,"top":0.75,"width":0.063541666,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"bounds":{"left":0.90208334,"top":0.75,"width":0.03888889,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4m","depth":18,"bounds":{"left":0.9423611,"top":0.75,"width":0.0125,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"bounds":{"left":0.75972223,"top":0.7777778,"width":0.025347222,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"bounds":{"left":0.7864583,"top":0.7777778,"width":0.010416667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Veselin Kulov","depth":18,"bounds":{"left":0.74375,"top":0.8438889,"width":0.059027776,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"bounds":{"left":0.80625,"top":0.84444445,"width":0.05659722,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8 Apr, 12:51 AM","depth":18,"bounds":{"left":0.74375,"top":0.8666667,"width":0.06423611,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"bounds":{"left":0.74375,"top":0.9122222,"width":0.030555556,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"bounds":{"left":0.7760417,"top":0.9122222,"width":0.017361112,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"unknown customer","depth":18,"bounds":{"left":0.7951389,"top":0.9122222,"width":0.07777778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"bounds":{"left":0.75972223,"top":0.96555555,"width":0.021527778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8 Apr, 12:36 AM","depth":18,"bounds":{"left":0.7826389,"top":0.96555555,"width":0.06423611,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"bounds":{"left":0.90208334,"top":0.96555555,"width":0.03888889,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7m","depth":18,"bounds":{"left":0.9423611,"top":0.96555555,"width":0.0125,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"bounds":{"left":0.75972223,"top":0.99333334,"width":0.025347222,"height":0.0066666603},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"bounds":{"left":0.7864583,"top":0.99333334,"width":0.010416667,"height":0.0066666603},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Nikolov","depth":18,"bounds":{"left":0.74375,"top":1.0,"width":0.06875,"height":-0.059444427},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"bounds":{"left":0.8159722,"top":1.0,"width":0.056944445,"height":-0.059999943},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"27 Feb, 4:38 PM","depth":18,"bounds":{"left":0.74375,"top":1.0,"width":0.06388889,"height":-0.08222222},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web Demo","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Martin Petkov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"13 Feb, 2:15 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11m","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$1","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12 Feb, 10:07 AM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12 Feb, 9:42 AM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1m","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12 Feb, 10:06 AM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12 Feb, 9:42 AM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1m","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Martin Petkov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"22 Jan, 1:30 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Discovery","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Martin Petkov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"21 Jan, 4:00 PM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"35m","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$2","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Martin Petkov","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"20 Jan, 11:42 AM","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-6877061559072944459
|
1877324636854709131
|
visual_change
|
accessibility
|
NULL
|
Unnamed Group
Platform Sprint 4 Q2 - Platform Team Unnamed Group
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
Usage | Windsurf
Close tab
Unnamed Group
[SRD-6848] Sidekick SMS issue - Jira
Close tab
[JY-20891] Sidekick SMS issue - Jira
Close tab
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
Close tab
Unnamed Group
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
[JY-20903] Recorded call does not appear on the dashboard - Jira
Close tab
Jiminny
Close tab
Jiminny
Close tab
Jiminny
Close tab
Unnamed Group
[SRD-6853] Moxso - Potential deal stages bug - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Useful commands - Engineering - Confluence
Close tab
Unnamed Group
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
Close tab
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
Close tab
Jiminny
Close tab
New Tab
Close tab
New Tab
Close tab
CloudWatch | us-east-2
Close tab
Companies | All companies
Close tab
Developers | HubSpot
Close tab
Jiminny
Close tab
Feed — jiminny — Sentry
Close tab
Inbox (1,684) - [EMAIL] - Jiminny Mail
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
JY-20493-smart-instant-nudge-pre-filtering ■ 885799
75
75
My Recordings
My Recordings
Everyone's Recordings
Everyone's Recordings
No Recordings
Schedule
Schedule
Invite Notetaker
This Week
This Week
Everyone's Schedule
Everyone's Schedule
Unknown Customer
Backend Chapter
Tomorrow, 10:30 AM
Trending this month
Trending this month
Sort by Sort by: Most played
Sort by
Sort by:
Most played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
2
times played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
1
times played
Unknown Customer
Processing tickets review
Processing tickets review
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Refinement - Processing
Refinement - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Notetaker added by Todor Stamatov
Notetaker added by Todor Stamatov
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Todor Stamatov at Drun Drun Chiki Chiki Bam Bam
Test 42
Test 42
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Unknown Customer
Notetaker added by Mihail Mihaylov
Notetaker added by Mihail Mihaylov
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Kara / James
Kara / James
0
times played
Unknown Customer
Jiminny SF App
Jiminny SF App
0
times played
Unknown Customer
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Laura / James
Laura / James
0
times played
Live Feed
Live Feed
Veselin Kulov
listened to call
7 May, 3:47 PM
activity
with
unknown customer
Held:
7 May, 10:37 AM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
7 May, 11:12 AM
activity
with
unknown customer
Held:
7 May, 10:37 AM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
29 Apr, 2:03 PM
activity
with
unknown customer
Held:
29 Apr, 1:33 PM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
8 Apr, 12:51 AM
activity
with
unknown customer
Held:
8 Apr, 12:36 AM
Duration:
7m
Value:
$0
Nikolay Nikolov
listened to call
27 Feb, 4:38 PM
Web Demo
with
Martin Petkov
Held:
13 Feb, 2:15 PM
Duration:
11m
Value:
$1
Nikolay Yankov
listened to call
12 Feb, 10:07 AM
activity
with
Nikolay Yankov
Held:
12 Feb, 9:42 AM
Duration:
1m
Value:
$0
Nikolay Yankov
listened to call
12 Feb, 10:06 AM
activity
with
Nikolay Yankov
Held:
12 Feb, 9:42 AM
Duration:
1m
Value:
$0
Martin Petkov
listened to call
22 Jan, 1:30 PM
Discovery
with
Martin Petkov
Held:
21 Jan, 4:00 PM
Duration:
35m
Value:
$2
Martin Petkov
listened to call
20 Jan, 11:42 AM
activity
with...
|
45145
|
NULL
|
NULL
|
NULL
|
|
45148
|
1621
|
6
|
2026-05-14T14:22:28.865379+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768548865_m1.jpg...
|
Firefox
|
Jiminny — Work
|
1
|
app.staging.jiminny.com/dashboard
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Unnamed Group
Platform Sprint 4 Q2 - Platform Team Unnamed Group
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
Usage | Windsurf
Close tab
Unnamed Group
[SRD-6848] Sidekick SMS issue - Jira
Close tab
[JY-20891] Sidekick SMS issue - Jira
Close tab
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
Close tab
Unnamed Group
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
[JY-20903] Recorded call does not appear on the dashboard - Jira
Close tab
Jiminny
Close tab
Jiminny
Close tab
Jiminny
Close tab
Unnamed Group
[SRD-6853] Moxso - Potential deal stages bug - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Useful commands - Engineering - Confluence
Close tab
Unnamed Group
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
Close tab
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
Close tab
Jiminny
Close tab
New Tab
Close tab
New Tab
Close tab
CloudWatch | us-east-2
Close tab
Companies | All companies
Close tab
Developers | HubSpot
Close tab
Jiminny
Close tab
Feed — jiminny — Sentry
Close tab
Inbox (1,684) - [EMAIL] - Jiminny Mail
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
JY-20493-smart-instant-nudge-pre-filtering ■ 885799
75
75
My Recordings
My Recordings
Everyone's Recordings
Everyone's Recordings
No Recordings
Schedule
Schedule
Invite Notetaker
This Week
This Week
Everyone's Schedule
Everyone's Schedule
Unknown Customer
Backend Chapter
Tomorrow, 10:30 AM
Trending this month
Trending this month
Sort by Sort by: Most played
Sort by
Sort by:
Most played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
2
times played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
1
times played
Unknown Customer
Processing tickets review
Processing tickets review
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Refinement - Processing
Refinement - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Notetaker added by Todor Stamatov
Notetaker added by Todor Stamatov
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Todor Stamatov at Drun Drun Chiki Chiki Bam Bam
Test 42
Test 42
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Unknown Customer
Notetaker added by Mihail Mihaylov
Notetaker added by Mihail Mihaylov
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Kara / James
Kara / James
0
times played
Unknown Customer
Jiminny SF App
Jiminny SF App
0
times played
Unknown Customer
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Laura / James
Laura / James
0
times played
Live Feed
Live Feed
Veselin Kulov
listened to call
7 May, 3:47 PM
activity
with
unknown customer
Held:
7 May, 10:37 AM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
7 May, 11:12 AM
activity
with
unknown customer
Held:
7 May, 10:37 AM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
29 Apr, 2:03 PM
activity
with
unknown customer
Held:
29 Apr, 1:33 PM
Duration:
4m
Value:
$0...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Usage | Windsurf","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6848] Sidekick SMS issue - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20891] Sidekick SMS issue - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20903] Recorded call does not appear on the dashboard - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.033680554,"height":0.045555554},"on_screen":false,"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.0,"width":0.010416667,"height":0.016666668},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.008680556,"top":0.0,"width":0.016666668,"height":0.026666667},"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6853] Moxso - Potential deal stages bug - Jira","depth":4,"bounds":{"left":0.0,"top":0.017222222,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.017222222,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.06277778,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.06277778,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.108333334,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.108333334,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Useful commands - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.1538889,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.1538889,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.008680556,"top":0.20666666,"width":0.016666668,"height":0.026666667},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.23944445,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.23944445,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira","depth":4,"bounds":{"left":0.0,"top":0.285,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.285,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.33055556,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.33055556,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.37611112,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.37611112,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.42166665,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.42166665,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0,"top":0.4672222,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.4672222,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Companies | All companies","depth":4,"bounds":{"left":0.0,"top":0.5127778,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.5127778,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Developers | HubSpot","depth":4,"bounds":{"left":0.0,"top":0.55833334,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.55833334,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.60388887,"width":0.033680554,"height":0.045555554},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.60388887,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.0,"top":0.64944446,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.64944446,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Inbox (1,684) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.695,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.695,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.005902778,"top":0.74666667,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.0,"top":0.7977778,"width":0.033680554,"height":0.043333333},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.0,"top":0.8411111,"width":0.033680554,"height":0.038333334},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.0,"top":0.8794444,"width":0.033680554,"height":0.03888889},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.0,"top":0.91833335,"width":0.033680554,"height":0.038333334},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0,"top":0.95666665,"width":0.033680554,"height":0.043333333},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20493-smart-instant-nudge-pre-filtering ■ 885799","depth":9,"bounds":{"left":0.035069443,"top":0.98055553,"width":0.21875,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"75","depth":12,"bounds":{"left":0.03923611,"top":0.88,"width":0.033333335,"height":0.04888889},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"75","depth":14,"bounds":{"left":0.05659722,"top":0.885,"width":0.009722223,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"My Recordings","depth":14,"bounds":{"left":0.14201389,"top":0.1,"width":0.08055556,"height":0.07333333},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"My Recordings","depth":15,"bounds":{"left":0.14618056,"top":0.1261111,"width":0.072222225,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Everyone's Recordings","depth":14,"bounds":{"left":0.22256945,"top":0.1,"width":0.11736111,"height":0.07333333},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Everyone's Recordings","depth":15,"bounds":{"left":0.22673611,"top":0.1261111,"width":0.10902778,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No Recordings","depth":17,"bounds":{"left":0.209375,"top":0.36055556,"width":0.063541666,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Schedule","depth":13,"bounds":{"left":0.396875,"top":0.36333334,"width":0.050347224,"height":0.037777778},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Schedule","depth":14,"bounds":{"left":0.396875,"top":0.37,"width":0.050347224,"height":0.024444444},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Invite Notetaker","depth":14,"bounds":{"left":0.5888889,"top":0.36,"width":0.09236111,"height":0.04},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXComboBox","text":"This Week","depth":14,"bounds":{"left":0.40729168,"top":0.4288889,"width":0.12847222,"height":0.04111111},"on_screen":true,"value":"This Week","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"This Week","depth":17,"bounds":{"left":0.41493055,"top":0.44,"width":0.045833334,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Everyone's Schedule","depth":14,"bounds":{"left":0.54270834,"top":0.4288889,"width":0.128125,"height":0.04111111},"on_screen":true,"value":"Everyone's Schedule","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Everyone's Schedule","depth":17,"bounds":{"left":0.5503472,"top":0.44,"width":0.088194445,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"bounds":{"left":0.44895834,"top":0.51055557,"width":0.08090278,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Backend Chapter","depth":17,"bounds":{"left":0.44895834,"top":0.5377778,"width":0.06979167,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Tomorrow, 10:30 AM","depth":16,"bounds":{"left":0.44895834,"top":0.565,"width":0.08576389,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Trending this month","depth":13,"bounds":{"left":0.40729168,"top":0.123333335,"width":0.096875,"height":0.026666667},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Trending this month","depth":14,"bounds":{"left":0.40729168,"top":0.1261111,"width":0.096875,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Sort by Sort by: Most played","depth":13,"bounds":{"left":0.55277777,"top":0.116111115,"width":0.11805555,"height":0.04111111},"on_screen":true,"value":"Sort by Sort by: Most played","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Sort by","depth":14,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sort by:","depth":15,"bounds":{"left":0.56041664,"top":0.12722223,"width":0.035416666,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Most played","depth":15,"bounds":{"left":0.59583336,"top":0.12722223,"width":0.054166667,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"bounds":{"left":0.48993057,"top":0.23333333,"width":0.084375,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Veselin Kulov","depth":15,"bounds":{"left":0.45520833,"top":0.25777778,"width":0.16770834,"height":0.022222223},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Veselin Kulov","depth":16,"bounds":{"left":0.47604167,"top":0.26055557,"width":0.12604167,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":16,"bounds":{"left":0.5420139,"top":0.28611112,"width":0.008680556,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"bounds":{"left":0.51944447,"top":0.30666667,"width":0.03888889,"height":0.013888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"bounds":{"left":0.7739583,"top":0.23333333,"width":0.084375,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Veselin Kulov","depth":15,"bounds":{"left":0.7392361,"top":0.25777778,"width":0.16770834,"height":0.022222223},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Veselin Kulov","depth":16,"bounds":{"left":0.76006943,"top":0.26055557,"width":0.12604167,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":16,"bounds":{"left":0.82604164,"top":0.28611112,"width":0.008680556,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"bounds":{"left":0.8034722,"top":0.30666667,"width":0.03888889,"height":0.013888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"bounds":{"left":1.0,"top":0.23333333,"width":-0.05798614,"height":0.017777778},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Processing tickets review","depth":15,"bounds":{"left":1.0,"top":0.25777778,"width":-0.03923607,"height":0.022222223},"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Processing tickets review","depth":16,"bounds":{"left":1.0,"top":0.26055557,"width":-0.06006944,"height":0.016666668},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"bounds":{"left":1.0,"top":0.30666667,"width":-0.087499976,"height":0.013888889},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Planing - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Planing - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Refinement - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Refinement - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Todor Stamatov","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Todor Stamatov","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Stefka / James Weekly","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Stefka / James Weekly","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Robinson Crusoe Cruises Limited","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sprint Review","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sprint Review","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Todor Stamatov at Drun Drun Chiki Chiki Bam Bam","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Test 42","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Test 42","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Robinson Crusoe Cruises Limited","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sprint Review","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sprint Review","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Mihail Mihaylov","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Mihail Mihaylov","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Planing - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Planing - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Stefka / James Weekly","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Stefka / James Weekly","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Kara / James","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Kara / James","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Jiminny SF App","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny SF App","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2026-04-28-jiminny-x-cmbio-at-partnership-kick-off","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2026-04-28-jiminny-x-cmbio-at-partnership-kick-off","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Laura / James","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Laura / James","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Live Feed","depth":13,"bounds":{"left":0.70555556,"top":0.123333335,"width":0.04548611,"height":0.026666667},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Live Feed","depth":14,"bounds":{"left":0.70555556,"top":0.1261111,"width":0.04548611,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Veselin Kulov","depth":18,"bounds":{"left":0.74375,"top":0.19722222,"width":0.059027776,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"bounds":{"left":0.80625,"top":0.19777778,"width":0.05659722,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7 May, 3:47 PM","depth":18,"bounds":{"left":0.74375,"top":0.22,"width":0.060763888,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"bounds":{"left":0.74375,"top":0.26555556,"width":0.030555556,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"bounds":{"left":0.7760417,"top":0.26555556,"width":0.017361112,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"unknown customer","depth":18,"bounds":{"left":0.7951389,"top":0.26555556,"width":0.07777778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"bounds":{"left":0.75972223,"top":0.3188889,"width":0.021527778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7 May, 10:37 AM","depth":18,"bounds":{"left":0.7826389,"top":0.3188889,"width":0.06666667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"bounds":{"left":0.90208334,"top":0.3188889,"width":0.03888889,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4m","depth":18,"bounds":{"left":0.9423611,"top":0.3188889,"width":0.0125,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"bounds":{"left":0.75972223,"top":0.34666666,"width":0.025347222,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"bounds":{"left":0.7864583,"top":0.34666666,"width":0.010416667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Veselin Kulov","depth":18,"bounds":{"left":0.74375,"top":0.41277778,"width":0.059027776,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"bounds":{"left":0.80625,"top":0.41333333,"width":0.05659722,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7 May, 11:12 AM","depth":18,"bounds":{"left":0.74375,"top":0.43555555,"width":0.06666667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"bounds":{"left":0.74375,"top":0.4811111,"width":0.030555556,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"bounds":{"left":0.7760417,"top":0.4811111,"width":0.017361112,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"unknown customer","depth":18,"bounds":{"left":0.7951389,"top":0.4811111,"width":0.07777778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"bounds":{"left":0.75972223,"top":0.53444445,"width":0.021527778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7 May, 10:37 AM","depth":18,"bounds":{"left":0.7826389,"top":0.53444445,"width":0.06666667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"bounds":{"left":0.90208334,"top":0.53444445,"width":0.03888889,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4m","depth":18,"bounds":{"left":0.9423611,"top":0.53444445,"width":0.0125,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"bounds":{"left":0.75972223,"top":0.56222224,"width":0.025347222,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"bounds":{"left":0.7864583,"top":0.56222224,"width":0.010416667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Veselin Kulov","depth":18,"bounds":{"left":0.74375,"top":0.62833333,"width":0.059027776,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"bounds":{"left":0.80625,"top":0.6288889,"width":0.05659722,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"29 Apr, 2:03 PM","depth":18,"bounds":{"left":0.74375,"top":0.6511111,"width":0.063541666,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"bounds":{"left":0.74375,"top":0.69666666,"width":0.030555556,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"bounds":{"left":0.7760417,"top":0.69666666,"width":0.017361112,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"unknown customer","depth":18,"bounds":{"left":0.7951389,"top":0.69666666,"width":0.07777778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"bounds":{"left":0.75972223,"top":0.75,"width":0.021527778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"29 Apr, 1:33 PM","depth":18,"bounds":{"left":0.7826389,"top":0.75,"width":0.063541666,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duration:","depth":18,"bounds":{"left":0.90208334,"top":0.75,"width":0.03888889,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4m","depth":18,"bounds":{"left":0.9423611,"top":0.75,"width":0.0125,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Value:","depth":18,"bounds":{"left":0.75972223,"top":0.7777778,"width":0.025347222,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$0","depth":18,"bounds":{"left":0.7864583,"top":0.7777778,"width":0.010416667,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
1170628216061656742
|
1877042680841726851
|
visual_change
|
accessibility
|
NULL
|
Unnamed Group
Platform Sprint 4 Q2 - Platform Team Unnamed Group
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
Usage | Windsurf
Close tab
Unnamed Group
[SRD-6848] Sidekick SMS issue - Jira
Close tab
[JY-20891] Sidekick SMS issue - Jira
Close tab
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
Close tab
Unnamed Group
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
[JY-20903] Recorded call does not appear on the dashboard - Jira
Close tab
Jiminny
Close tab
Jiminny
Close tab
Jiminny
Close tab
Unnamed Group
[SRD-6853] Moxso - Potential deal stages bug - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Useful commands - Engineering - Confluence
Close tab
Unnamed Group
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
Close tab
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
Close tab
Jiminny
Close tab
New Tab
Close tab
New Tab
Close tab
CloudWatch | us-east-2
Close tab
Companies | All companies
Close tab
Developers | HubSpot
Close tab
Jiminny
Close tab
Feed — jiminny — Sentry
Close tab
Inbox (1,684) - [EMAIL] - Jiminny Mail
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
JY-20493-smart-instant-nudge-pre-filtering ■ 885799
75
75
My Recordings
My Recordings
Everyone's Recordings
Everyone's Recordings
No Recordings
Schedule
Schedule
Invite Notetaker
This Week
This Week
Everyone's Schedule
Everyone's Schedule
Unknown Customer
Backend Chapter
Tomorrow, 10:30 AM
Trending this month
Trending this month
Sort by Sort by: Most played
Sort by
Sort by:
Most played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
2
times played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
1
times played
Unknown Customer
Processing tickets review
Processing tickets review
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Refinement - Processing
Refinement - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Notetaker added by Todor Stamatov
Notetaker added by Todor Stamatov
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Todor Stamatov at Drun Drun Chiki Chiki Bam Bam
Test 42
Test 42
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Unknown Customer
Notetaker added by Mihail Mihaylov
Notetaker added by Mihail Mihaylov
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Kara / James
Kara / James
0
times played
Unknown Customer
Jiminny SF App
Jiminny SF App
0
times played
Unknown Customer
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Laura / James
Laura / James
0
times played
Live Feed
Live Feed
Veselin Kulov
listened to call
7 May, 3:47 PM
activity
with
unknown customer
Held:
7 May, 10:37 AM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
7 May, 11:12 AM
activity
with
unknown customer
Held:
7 May, 10:37 AM
Duration:
4m
Value:
$0
Veselin Kulov
listened to call
29 Apr, 2:03 PM
activity
with
unknown customer
Held:
29 Apr, 1:33 PM
Duration:
4m
Value:
$0...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45149
|
1621
|
7
|
2026-05-14T14:22:34.912942+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768554912_m1.jpg...
|
Firefox
|
Jiminny — Work
|
1
|
app.staging.jiminny.com/dashboard
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Unnamed Group
Platform Sprint 4 Q2 - Platform Team Unnamed Group
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
Usage | Windsurf
Close tab
Unnamed Group
[SRD-6848] Sidekick SMS issue - Jira
Close tab
[JY-20891] Sidekick SMS issue - Jira
Close tab
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
Close tab
Unnamed Group
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
[JY-20903] Recorded call does not appear on the dashboard - Jira
Close tab
Jiminny
Close tab
Jiminny
Close tab
Jiminny
Close tab
Unnamed Group
[SRD-6853] Moxso - Potential deal stages bug - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Useful commands - Engineering - Confluence
Close tab
Unnamed Group
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
Close tab
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
Close tab
Jiminny
Close tab
New Tab
Close tab
New Tab
Close tab
CloudWatch | us-east-2
Close tab
Companies | All companies
Close tab
Developers | HubSpot
Close tab
Jiminny
Close tab
Feed — jiminny — Sentry
Close tab
Inbox (1,684) - [EMAIL] - Jiminny Mail
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
JY-20493-smart-instant-nudge-pre-filtering ■ 885799
75
75
My Recordings
My Recordings
Everyone's Recordings
Everyone's Recordings
No Recordings
Schedule
Schedule
Invite Notetaker
This Week
This Week
Everyone's Schedule
Everyone's Schedule
Unknown Customer
Backend Chapter
Tomorrow, 10:30 AM
Trending this month
Trending this month
Sort by Sort by: Most played
Sort by
Sort by:
Most played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
2
times played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
1
times played
Unknown Customer
Processing tickets review
Processing tickets review
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Refinement - Processing
Refinement - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Notetaker added by Todor Stamatov
Notetaker added by Todor Stamatov
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Todor Stamatov at Drun Drun Chiki Chiki Bam Bam
Test 42
Test 42
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Unknown Customer
Notetaker added by Mihail Mihaylov
Notetaker added by Mihail Mihaylov
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Kara / James
Kara / James
0
times played
Unknown Customer
Jiminny SF App
Jiminny SF App
0
times played
Unknown Customer
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Laura / James
Laura / James
0
times played
Live Feed
Live Feed
Veselin Kulov
listened to call
7 May, 3:47 PM
activity
with
unknown customer
Held:...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Usage | Windsurf","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6848] Sidekick SMS issue - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20891] Sidekick SMS issue - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6849] Recorded call does not appear on the dashboard - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20903] Recorded call does not appear on the dashboard - Jira","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.033680554,"height":0.045555554},"on_screen":false,"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.0,"width":0.010416667,"height":0.016666668},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.008680556,"top":0.0,"width":0.016666668,"height":0.026666667},"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"[SRD-6853] Moxso - Potential deal stages bug - Jira","depth":4,"bounds":{"left":0.0,"top":0.017222222,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.017222222,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.06277778,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.06277778,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Configure SSH access to multiple environment - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.108333334,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.108333334,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Useful commands - Engineering - Confluence","depth":4,"bounds":{"left":0.0,"top":0.1538889,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.1538889,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.008680556,"top":0.20666666,"width":0.016666668,"height":0.026666667},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.23944445,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.23944445,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira","depth":4,"bounds":{"left":0.0,"top":0.285,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.285,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.33055556,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.33055556,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.37611112,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.37611112,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.42166665,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.42166665,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0,"top":0.4672222,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.4672222,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Companies | All companies","depth":4,"bounds":{"left":0.0,"top":0.5127778,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.5127778,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Developers | HubSpot","depth":4,"bounds":{"left":0.0,"top":0.55833334,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.55833334,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.60388887,"width":0.033680554,"height":0.045555554},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.0013888889,"top":0.60388887,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.0,"top":0.64944446,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.64944446,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Inbox (1,684) - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.695,"width":0.033680554,"height":0.045555554},"on_screen":true,"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.695,"width":0.010416667,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.005902778,"top":0.74666667,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.0,"top":0.7977778,"width":0.033680554,"height":0.043333333},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.0,"top":0.8411111,"width":0.033680554,"height":0.038333334},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.0,"top":0.8794444,"width":0.033680554,"height":0.03888889},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.0,"top":0.91833335,"width":0.033680554,"height":0.038333334},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0,"top":0.95666665,"width":0.033680554,"height":0.043333333},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20493-smart-instant-nudge-pre-filtering ■ 885799","depth":9,"bounds":{"left":0.035069443,"top":0.98055553,"width":0.21875,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"75","depth":12,"bounds":{"left":0.03923611,"top":0.88,"width":0.033333335,"height":0.04888889},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"75","depth":14,"bounds":{"left":0.05659722,"top":0.885,"width":0.009722223,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"My Recordings","depth":14,"bounds":{"left":0.14201389,"top":0.1,"width":0.08055556,"height":0.07333333},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"My Recordings","depth":15,"bounds":{"left":0.14618056,"top":0.1261111,"width":0.072222225,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Everyone's Recordings","depth":14,"bounds":{"left":0.22256945,"top":0.1,"width":0.11736111,"height":0.07333333},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Everyone's Recordings","depth":15,"bounds":{"left":0.22673611,"top":0.1261111,"width":0.10902778,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No Recordings","depth":17,"bounds":{"left":0.209375,"top":0.36055556,"width":0.063541666,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Schedule","depth":13,"bounds":{"left":0.396875,"top":0.36333334,"width":0.050347224,"height":0.037777778},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Schedule","depth":14,"bounds":{"left":0.396875,"top":0.37,"width":0.050347224,"height":0.024444444},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Invite Notetaker","depth":14,"bounds":{"left":0.5888889,"top":0.36,"width":0.09236111,"height":0.04},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXComboBox","text":"This Week","depth":14,"bounds":{"left":0.40729168,"top":0.4288889,"width":0.12847222,"height":0.04111111},"on_screen":true,"value":"This Week","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"This Week","depth":17,"bounds":{"left":0.41493055,"top":0.44,"width":0.045833334,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Everyone's Schedule","depth":14,"bounds":{"left":0.54270834,"top":0.4288889,"width":0.128125,"height":0.04111111},"on_screen":true,"value":"Everyone's Schedule","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Everyone's Schedule","depth":17,"bounds":{"left":0.5503472,"top":0.44,"width":0.088194445,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"bounds":{"left":0.44895834,"top":0.51055557,"width":0.08090278,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Backend Chapter","depth":17,"bounds":{"left":0.44895834,"top":0.5377778,"width":0.06979167,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Tomorrow, 10:30 AM","depth":16,"bounds":{"left":0.44895834,"top":0.565,"width":0.08576389,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Trending this month","depth":13,"bounds":{"left":0.40729168,"top":0.123333335,"width":0.096875,"height":0.026666667},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Trending this month","depth":14,"bounds":{"left":0.40729168,"top":0.1261111,"width":0.096875,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Sort by Sort by: Most played","depth":13,"bounds":{"left":0.55277777,"top":0.116111115,"width":0.11805555,"height":0.04111111},"on_screen":true,"value":"Sort by Sort by: Most played","help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Sort by","depth":14,"on_screen":false,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sort by:","depth":15,"bounds":{"left":0.56041664,"top":0.12722223,"width":0.035416666,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Most played","depth":15,"bounds":{"left":0.59583336,"top":0.12722223,"width":0.054166667,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"bounds":{"left":0.48993057,"top":0.23333333,"width":0.084375,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Veselin Kulov","depth":15,"bounds":{"left":0.45520833,"top":0.25777778,"width":0.16770834,"height":0.022222223},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Veselin Kulov","depth":16,"bounds":{"left":0.47604167,"top":0.26055557,"width":0.12604167,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":16,"bounds":{"left":0.5420139,"top":0.28611112,"width":0.008680556,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"bounds":{"left":0.51944447,"top":0.30666667,"width":0.03888889,"height":0.013888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"bounds":{"left":0.7739583,"top":0.23333333,"width":0.084375,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Veselin Kulov","depth":15,"bounds":{"left":0.7392361,"top":0.25777778,"width":0.16770834,"height":0.022222223},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Veselin Kulov","depth":16,"bounds":{"left":0.76006943,"top":0.26055557,"width":0.12604167,"height":0.016666668},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":16,"bounds":{"left":0.82604164,"top":0.28611112,"width":0.008680556,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"bounds":{"left":0.8034722,"top":0.30666667,"width":0.03888889,"height":0.013888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"bounds":{"left":1.0,"top":0.23333333,"width":-0.05798614,"height":0.017777778},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Processing tickets review","depth":15,"bounds":{"left":1.0,"top":0.25777778,"width":-0.03923607,"height":0.022222223},"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Processing tickets review","depth":16,"bounds":{"left":1.0,"top":0.26055557,"width":-0.06006944,"height":0.016666668},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"bounds":{"left":1.0,"top":0.30666667,"width":-0.087499976,"height":0.013888889},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Planing - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Planing - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Refinement - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Refinement - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Todor Stamatov","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Todor Stamatov","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Stefka / James Weekly","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Stefka / James Weekly","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Robinson Crusoe Cruises Limited","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sprint Review","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sprint Review","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Todor Stamatov at Drun Drun Chiki Chiki Bam Bam","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Test 42","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Test 42","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Robinson Crusoe Cruises Limited","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sprint Review","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sprint Review","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notetaker added by Mihail Mihaylov","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notetaker added by Mihail Mihaylov","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Planing - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Planing - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Stefka / James Weekly","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Stefka / James Weekly","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Kara / James","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Kara / James","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Jiminny SF App","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny SF App","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2026-04-28-jiminny-x-cmbio-at-partnership-kick-off","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2026-04-28-jiminny-x-cmbio-at-partnership-kick-off","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Daily - Processing","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Daily - Processing","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unknown Customer","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Laura / James","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Laura / James","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"times played","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Live Feed","depth":13,"bounds":{"left":0.70555556,"top":0.123333335,"width":0.04548611,"height":0.026666667},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Live Feed","depth":14,"bounds":{"left":0.70555556,"top":0.1261111,"width":0.04548611,"height":0.021666666},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Veselin Kulov","depth":18,"bounds":{"left":0.74375,"top":0.19722222,"width":0.059027776,"height":0.018888889},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"listened to call","depth":18,"bounds":{"left":0.80625,"top":0.19777778,"width":0.05659722,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7 May, 3:47 PM","depth":18,"bounds":{"left":0.74375,"top":0.22,"width":0.060763888,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activity","depth":18,"bounds":{"left":0.74375,"top":0.26555556,"width":0.030555556,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with","depth":18,"bounds":{"left":0.7760417,"top":0.26555556,"width":0.017361112,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"unknown customer","depth":18,"bounds":{"left":0.7951389,"top":0.26555556,"width":0.07777778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Held:","depth":18,"bounds":{"left":0.75972223,"top":0.3188889,"width":0.021527778,"height":0.017777778},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
2272302148497823286
|
1895058728585095555
|
visual_change
|
accessibility
|
NULL
|
Unnamed Group
Platform Sprint 4 Q2 - Platform Team Unnamed Group
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
Usage | Windsurf
Close tab
Unnamed Group
[SRD-6848] Sidekick SMS issue - Jira
Close tab
[JY-20891] Sidekick SMS issue - Jira
Close tab
JY-20891 add support for secondary email by LakyLak · Pull Request #12073 · jiminny/app
Close tab
Unnamed Group
[SRD-6849] Recorded call does not appear on the dashboard - Jira
Close tab
[JY-20903] Recorded call does not appear on the dashboard - Jira
Close tab
Jiminny
Close tab
Jiminny
Close tab
Jiminny
Close tab
Unnamed Group
[SRD-6853] Moxso - Potential deal stages bug - Jira
Close tab
JY-20903 Update activity stage when opportunity is changed by LakyLak · Pull Request #12077 · jiminny/app
Close tab
Configure SSH access to multiple environment - Engineering - Confluence
Close tab
Useful commands - Engineering - Confluence
Close tab
Unnamed Group
JY-20904 Fix UpdateActivityElasticSearchDocumentCommand find activity… by LakyLak · Pull Request #12078 · jiminny/app
Close tab
[JY-20904] Fix UpdateActivityElasticSearchDocumentCommand - Jira
Close tab
Jiminny
Close tab
New Tab
Close tab
New Tab
Close tab
CloudWatch | us-east-2
Close tab
Companies | All companies
Close tab
Developers | HubSpot
Close tab
Jiminny
Close tab
Feed — jiminny — Sentry
Close tab
Inbox (1,684) - [EMAIL] - Jiminny Mail
Close tab
New Tab
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Customize sidebar
JY-20493-smart-instant-nudge-pre-filtering ■ 885799
75
75
My Recordings
My Recordings
Everyone's Recordings
Everyone's Recordings
No Recordings
Schedule
Schedule
Invite Notetaker
This Week
This Week
Everyone's Schedule
Everyone's Schedule
Unknown Customer
Backend Chapter
Tomorrow, 10:30 AM
Trending this month
Trending this month
Sort by Sort by: Most played
Sort by
Sort by:
Most played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
2
times played
Unknown Customer
Notetaker added by Veselin Kulov
Notetaker added by Veselin Kulov
1
times played
Unknown Customer
Processing tickets review
Processing tickets review
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Refinement - Processing
Refinement - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Notetaker added by Todor Stamatov
Notetaker added by Todor Stamatov
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Todor Stamatov at Drun Drun Chiki Chiki Bam Bam
Test 42
Test 42
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Robinson Crusoe Cruises Limited
Sprint Review
Sprint Review
0
times played
Unknown Customer
Notetaker added by Mihail Mihaylov
Notetaker added by Mihail Mihaylov
0
times played
Unknown Customer
Planing - Processing
Planing - Processing
0
times played
Unknown Customer
Stefka / James Weekly
Stefka / James Weekly
0
times played
Unknown Customer
Kara / James
Kara / James
0
times played
Unknown Customer
Jiminny SF App
Jiminny SF App
0
times played
Unknown Customer
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
2026-04-28-jiminny-x-cmbio-at-partnership-kick-off
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Daily - Processing
Daily - Processing
0
times played
Unknown Customer
Laura / James
Laura / James
0
times played
Live Feed
Live Feed
Veselin Kulov
listened to call
7 May, 3:47 PM
activity
with
unknown customer
Held:...
|
45148
|
NULL
|
NULL
|
NULL
|
|
45152
|
1622
|
2
|
2026-05-14T14:22:45.653507+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768565653_m2.jpg...
|
PhpStorm
|
faVsco.js – ImportActivityTypes.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
ActivityFilesLaterJiminny... ~@ jiminny-x-integrat ActivityFilesLaterJiminny... ~@ jiminny-x-integrati& platform-inner-team© Channels# ai-chapter# alertsi backend# bugscontusion-cllnia# curiosity_lab# engineering# general# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the people of iimi..• Direct messages8. A...• A3 62P. Galya Dimitrova Mvasil VasilevA. Stefka Stoyanova%: Todor Stamatovf. Mario GeorgievP. Nikolay Ivanov2o James Graham "2. Stoyan Tanev. Steliyan Georgiev. Petko Kashinski*. Lukas Kovali...a: Apps® ToastS lira Gloud6 Huddle with Aneliya Angelova& R. Aneliya Angelova •• Messagest Add canvasur FilesAneliya Angelova 2:30 PMЛукаш, само при Сейлсфорс и Хубспот се синкваха активити типовете, когато се направи плейбук,нали?пои останалите скМі тоябва оъчно ла се въвелат.Lukas Kovalik 2:47 PMтряова да се за всички, някьде не се ли попьлвапри зохо май беше hardcoded но май и там си връщаха две категорииAneliya Angelova @ 4:00 PMЛукаш имаш ли време да се чуем за тестването на https://jiminny.atlassian.net/browse/JY-20725X Preview insackt[HubSpot] Optimise CRM rematctC OpenReady for QA- MediumAA Aneliya AngelovaAs of today at 4:00 PM RetreshOpen in Jira+ SummariseLukas Kovalik 4:02 PMzean-iurwiniareelYou joined the huddle LIVE 4:06 PMAneliva Angelova is here toolAneliya Angelova 4:44 PM11512582Lukas Kovalik O 5:16 PMcurl --location "httns:ani.hubani.com/crm/v3/obiects/contacts/search--header 'Content-Type: application/json'\--header "Authorization: BearerC|Kvma?iMx7OINOM8kOFwrAowAcAkUAhIR24?A05D?xiVrIYMILuP6SA01Kwcmhr=n.v2 hytorDniMvDalYck-CMalovolNOM9kOFwrAUAcrkcaws-ThwRARIRAOF-ATFSAOFRAO-ROIEBAQUBEggBAQEBAYICFIґ7Au8O2Nwal9YW2CbCCxYvwngmSgNldTFSAFoAYABoAHAAeAA'\--data "{ "limit", 13'Message Aneliva Angelova+ AaIđ 0Pm~ (Q SearchDate ModifiedYesterdav at 13.34Yesterday at 13:33Yesterday at 13.32Yesterdav at 13:32Yesterday at 13:31Yesterdav at 13:30Yesterday at 13:30Yesterday at 13.29Yesterday at 13:28resterday at 15.21Yesterdav at 13:27Yesterday at 13:26Yesterday at 13:26Yesterday at 13:23Yesterday at 13:25Yesterday at 13.24Yesterdav at 13:23Yesterday at 13:22Testerady dl ls.2.Yesterday at 13:21Yesterday at 13:20Yesterday at 13.19Yesterdav at 13:19Yesterday at 13:18Yesterday at 13:18Yesterday at 13:17Yesterday at 13:17Yesterday at 13:16Yesterday at 13.16Yecterdav at 12:15Yesterday at 13:15Yesterday at 13-14Yesterdav at 13:14Yesterday at 13:13Yesterday at 13:12Yecterdav at 12:12Yesterday at 13.11Yesterdav at 13:10Yesterday at 13:10Yesterday at 13:09Yoctorday at 12:09Yesterday at 13:08Yesterday at 13.0Yecterdav at 12:07Yesterday at 13:06Yesterdav at 13:0529 K:MPEG-4 movie6 KBMPEG-4 movie12 KBMPEG-4 movieMPEG-4 movie7 KBI8 KB37 KBMPEG-4 movie10 K:7 KBMPEG-4 movie8 KBMPEG-4 movie8 KBMPEG-4 movie72 KB14 KBMPEG-4 movie13 KE9KBMPEG-4 movie18 KB12 KB|10 KRIMPEG-4 movieMPEG-4 movie16 KB6 KBMPEG-4 movie6KEMPEG-4 movie12 KBMPEG-4 movie23 KB8 K:MPEG-4 movie6 KBMPEG-4 movie6 KB11 KBMPEG-4 movie11 KEMPEG-4 movie20 KBMPEG-4 movie34 KB10 K:MPEG-4 movie7 KBMPEG-4 movie5 KB11 KBMPEG-4 movie26KPMPEG-4 movie111 KB MPEG-4 movie102 KB88 KBMPEG-4 movie59 KBMPEG-4 movie98 KB97 KBRAKRMPEG-4 movieMDEG-A movie44 KB93 KBMPEG-4 movie78 K:MPEG-A movie50 KBMPEG-4 movie58 KB27 KB7 KPMPEG-4 movieMPEG-4 movie12 KB32 KBMPEG-4 movie17 Kг19 KBMPEG-4 movie32 KB10KBMPEG-4 movieAl Notes: OffLeave• 0FavouritesE jiminny© Recents* ApplicationsiCloudiCloud Drive228 Sync folderQ DXP4800PLUS-B5FA@ Network_• CRMI• Orange• Red• Yellow• Green• Purple•) All lags..lohlDownloadsNameLoom.pkgAlfred copv.alfredoreterencesB KeychronAssist-1.0.2 (1).dmgA Keychron Assist-1.0.2.dmgmazanoke-images-yWJo.zioPhotos-3-001.zipD Transcript.pdf→mage U.loge1 Orioninstaller.dmaimage.jpg- image (2).1pc• ПО-22221726037035-004-001_ORGES.pdf• %D0%9F%D0%9E-22221726037035-004-001_archive.zipПO-22221726037035-004-001_archive (1).zip• repon 4).XmAltred copy2.altredoreterences05012026_0000000026574472_ SWIFT_OB70501260015890.pdf27022026_0000000026574472_SWIFT_OB72702260049200.pdf= 03042026 [CREDIT_CARD] SWIFT [CREDIT_CARD].001B reporti) xm=pdt.odipdf-1.pdfD pdf-2.pdf-pdf-5.pd1= ndf-1 ndipdf-3.pdfB Rovaix Famly Treo gedbitwarden export 20251031122528.isonKoválik Family Tree.zip*macOS Storage_Cleanup.mdal favicon icofirst_aid_notes_complete.docxrenortl2).esvconfig.ymlIteration run Search HS.postman_collection.json--report(1).csvm licence hettertouchtoalMariusHosting Config.json1ooks-891a6503-bbb7-4b2b-9c3.csv•Alfredmazanoke-images-YWJ6ана Ковалик.jpg•искане даниел Ковалик..pa• Фактура Март Даниел Ковалик.jрс• Фактура Април Даниел Ковалик.jpg• Dhotac 2.001Q SearchKind00,4 MDinstdlle..dckage55.9 MBAlfred...ferences10,1 MBDisk Image10,1 MBDisk ImageIL MBLiP archive6,6 MBZIP archive2,5 MB PDF Document2,5 MBJreo lmage2.2 MBDisk Image2,2 MB PDF Document2 MBJPEG image1,9 MBJPEG imaqe192 KBPDF Document140 KbZIP archive148 KВZIP archivel148 KBZIP archive122 KBXML document111 KBAlfred...ferences94 KBPDF Document92 KBPDr DocumentK:91 KB91 KB30 KB29 KBPDF DocumentXML documentPDF Document28 KBPDF Document28 KR28 KB27 KB14 KB11 KB6 KB6KBPDE DocumentDocumentJSONICSV DocumentZIP archiveMarkdo…..ument5KRWindo...n image4 KB3 KBword ..cumentCSV Document2 KBYAML document1 KBcSV Document928 byteshttlicence183 bytesZero butesJSONAlfred.. ferencesZero bytesFolde1,9 MB1,8 MB17 MB1,7 MBColdorJPEG imageJPEG ImageIPEG imadeJPEG imageColdo1 of 58 selected, 9.64 GB availabld• Inu 14 Mау 1/•22.42Date AddedIs Mdl ZUzo dl 19:4530 Jan 2026 at 12:3617 Mar 2026 at 20:2717 Mar 2026 at 20:26Z3 Aor 2020 al 13:0229 Jan 2026 at 15:2019 Dec 2025 at 10:1619 Dec 2025 at 12:238 Aor 2026 at 20:3519 Dec 2025 at 10:2919 Dec 2025 at 12:1819 Dec 2025 at 12:4026 Mar 2026 at 11:2410 May 2026 at 13:5326 Mar 2026 at 11:2426 Mar 2026 at 11:2426 Mar 2026 at 11:2310 May 2026 at 14:3730 Jan 2026 at 12:3713 Feb 2026 at 11:54Z3 Apr 2026 at 13.0823 Aor 2026 at 13:0810 May 2026 at 13:5410 May 2026 at 14:3710 May 2026 at 13:4910 May 2026 at 13:5010 May 2026 at 13:5110 Mav 2026 at 12:51110 May 2026 at 13:5019 Dec 2025 at 11:3431 Oct 2025 at 12:2525 Nov 2025 at 17:596 Mar 2026 at 11.2224 Anr 2026 at 16:5220 Oct 2025 at 11:0218 Mar 2026 at 15:29• Mav 2026 at 11:09129 Oct 2025 at 19:329 May 2026 at 10:0418 Mar 2026 at 11:5510 May 2026 at 13:5712 Jun 2025 at 19:0430 Jan 2026 at 12:3630 Jan 2026 at 12:3616 Oct 2025 aт 16:0123 Apr 2026 at 13:0223 Apr 2026 at 13:0223 Apr 2026 at 13:0222 Anr 2026 at 12:0223 Apr 2026 at 13:0229 Jan 2026 at 15:20...
|
NULL
|
2030629860205763140
|
NULL
|
app_switch
|
ocr
|
NULL
|
ActivityFilesLaterJiminny... ~@ jiminny-x-integrat ActivityFilesLaterJiminny... ~@ jiminny-x-integrati& platform-inner-team© Channels# ai-chapter# alertsi backend# bugscontusion-cllnia# curiosity_lab# engineering# general# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the people of iimi..• Direct messages8. A...• A3 62P. Galya Dimitrova Mvasil VasilevA. Stefka Stoyanova%: Todor Stamatovf. Mario GeorgievP. Nikolay Ivanov2o James Graham "2. Stoyan Tanev. Steliyan Georgiev. Petko Kashinski*. Lukas Kovali...a: Apps® ToastS lira Gloud6 Huddle with Aneliya Angelova& R. Aneliya Angelova •• Messagest Add canvasur FilesAneliya Angelova 2:30 PMЛукаш, само при Сейлсфорс и Хубспот се синкваха активити типовете, когато се направи плейбук,нали?пои останалите скМі тоябва оъчно ла се въвелат.Lukas Kovalik 2:47 PMтряова да се за всички, някьде не се ли попьлвапри зохо май беше hardcoded но май и там си връщаха две категорииAneliya Angelova @ 4:00 PMЛукаш имаш ли време да се чуем за тестването на https://jiminny.atlassian.net/browse/JY-20725X Preview insackt[HubSpot] Optimise CRM rematctC OpenReady for QA- MediumAA Aneliya AngelovaAs of today at 4:00 PM RetreshOpen in Jira+ SummariseLukas Kovalik 4:02 PMzean-iurwiniareelYou joined the huddle LIVE 4:06 PMAneliva Angelova is here toolAneliya Angelova 4:44 PM11512582Lukas Kovalik O 5:16 PMcurl --location "httns:ani.hubani.com/crm/v3/obiects/contacts/search--header 'Content-Type: application/json'\--header "Authorization: BearerC|Kvma?iMx7OINOM8kOFwrAowAcAkUAhIR24?A05D?xiVrIYMILuP6SA01Kwcmhr=n.v2 hytorDniMvDalYck-CMalovolNOM9kOFwrAUAcrkcaws-ThwRARIRAOF-ATFSAOFRAO-ROIEBAQUBEggBAQEBAYICFIґ7Au8O2Nwal9YW2CbCCxYvwngmSgNldTFSAFoAYABoAHAAeAA'\--data "{ "limit", 13'Message Aneliva Angelova+ AaIđ 0Pm~ (Q SearchDate ModifiedYesterdav at 13.34Yesterday at 13:33Yesterday at 13.32Yesterdav at 13:32Yesterday at 13:31Yesterdav at 13:30Yesterday at 13:30Yesterday at 13.29Yesterday at 13:28resterday at 15.21Yesterdav at 13:27Yesterday at 13:26Yesterday at 13:26Yesterday at 13:23Yesterday at 13:25Yesterday at 13.24Yesterdav at 13:23Yesterday at 13:22Testerady dl ls.2.Yesterday at 13:21Yesterday at 13:20Yesterday at 13.19Yesterdav at 13:19Yesterday at 13:18Yesterday at 13:18Yesterday at 13:17Yesterday at 13:17Yesterday at 13:16Yesterday at 13.16Yecterdav at 12:15Yesterday at 13:15Yesterday at 13-14Yesterdav at 13:14Yesterday at 13:13Yesterday at 13:12Yecterdav at 12:12Yesterday at 13.11Yesterdav at 13:10Yesterday at 13:10Yesterday at 13:09Yoctorday at 12:09Yesterday at 13:08Yesterday at 13.0Yecterdav at 12:07Yesterday at 13:06Yesterdav at 13:0529 K:MPEG-4 movie6 KBMPEG-4 movie12 KBMPEG-4 movieMPEG-4 movie7 KBI8 KB37 KBMPEG-4 movie10 K:7 KBMPEG-4 movie8 KBMPEG-4 movie8 KBMPEG-4 movie72 KB14 KBMPEG-4 movie13 KE9KBMPEG-4 movie18 KB12 KB|10 KRIMPEG-4 movieMPEG-4 movie16 KB6 KBMPEG-4 movie6KEMPEG-4 movie12 KBMPEG-4 movie23 KB8 K:MPEG-4 movie6 KBMPEG-4 movie6 KB11 KBMPEG-4 movie11 KEMPEG-4 movie20 KBMPEG-4 movie34 KB10 K:MPEG-4 movie7 KBMPEG-4 movie5 KB11 KBMPEG-4 movie26KPMPEG-4 movie111 KB MPEG-4 movie102 KB88 KBMPEG-4 movie59 KBMPEG-4 movie98 KB97 KBRAKRMPEG-4 movieMDEG-A movie44 KB93 KBMPEG-4 movie78 K:MPEG-A movie50 KBMPEG-4 movie58 KB27 KB7 KPMPEG-4 movieMPEG-4 movie12 KB32 KBMPEG-4 movie17 Kг19 KBMPEG-4 movie32 KB10KBMPEG-4 movieAl Notes: OffLeave• 0FavouritesE jiminny© Recents* ApplicationsiCloudiCloud Drive228 Sync folderQ DXP4800PLUS-B5FA@ Network_• CRMI• Orange• Red• Yellow• Green• Purple•) All lags..lohlDownloadsNameLoom.pkgAlfred copv.alfredoreterencesB KeychronAssist-1.0.2 (1).dmgA Keychron Assist-1.0.2.dmgmazanoke-images-yWJo.zioPhotos-3-001.zipD Transcript.pdf→mage U.loge1 Orioninstaller.dmaimage.jpg- image (2).1pc• ПО-22221726037035-004-001_ORGES.pdf• %D0%9F%D0%9E-22221726037035-004-001_archive.zipПO-22221726037035-004-001_archive (1).zip• repon 4).XmAltred copy2.altredoreterences05012026_0000000026574472_ SWIFT_OB70501260015890.pdf27022026_0000000026574472_SWIFT_OB72702260049200.pdf= 03042026 [CREDIT_CARD] SWIFT [CREDIT_CARD].001B reporti) xm=pdt.odipdf-1.pdfD pdf-2.pdf-pdf-5.pd1= ndf-1 ndipdf-3.pdfB Rovaix Famly Treo gedbitwarden export 20251031122528.isonKoválik Family Tree.zip*macOS Storage_Cleanup.mdal favicon icofirst_aid_notes_complete.docxrenortl2).esvconfig.ymlIteration run Search HS.postman_collection.json--report(1).csvm licence hettertouchtoalMariusHosting Config.json1ooks-891a6503-bbb7-4b2b-9c3.csv•Alfredmazanoke-images-YWJ6ана Ковалик.jpg•искане даниел Ковалик..pa• Фактура Март Даниел Ковалик.jрс• Фактура Април Даниел Ковалик.jpg• Dhotac 2.001Q SearchKind00,4 MDinstdlle..dckage55.9 MBAlfred...ferences10,1 MBDisk Image10,1 MBDisk ImageIL MBLiP archive6,6 MBZIP archive2,5 MB PDF Document2,5 MBJreo lmage2.2 MBDisk Image2,2 MB PDF Document2 MBJPEG image1,9 MBJPEG imaqe192 KBPDF Document140 KbZIP archive148 KВZIP archivel148 KBZIP archive122 KBXML document111 KBAlfred...ferences94 KBPDF Document92 KBPDr DocumentK:91 KB91 KB30 KB29 KBPDF DocumentXML documentPDF Document28 KBPDF Document28 KR28 KB27 KB14 KB11 KB6 KB6KBPDE DocumentDocumentJSONICSV DocumentZIP archiveMarkdo…..ument5KRWindo...n image4 KB3 KBword ..cumentCSV Document2 KBYAML document1 KBcSV Document928 byteshttlicence183 bytesZero butesJSONAlfred.. ferencesZero bytesFolde1,9 MB1,8 MB17 MB1,7 MBColdorJPEG imageJPEG ImageIPEG imadeJPEG imageColdo1 of 58 selected, 9.64 GB availabld• Inu 14 Mау 1/•22.42Date AddedIs Mdl ZUzo dl 19:4530 Jan 2026 at 12:3617 Mar 2026 at 20:2717 Mar 2026 at 20:26Z3 Aor 2020 al 13:0229 Jan 2026 at 15:2019 Dec 2025 at 10:1619 Dec 2025 at 12:238 Aor 2026 at 20:3519 Dec 2025 at 10:2919 Dec 2025 at 12:1819 Dec 2025 at 12:4026 Mar 2026 at 11:2410 May 2026 at 13:5326 Mar 2026 at 11:2426 Mar 2026 at 11:2426 Mar 2026 at 11:2310 May 2026 at 14:3730 Jan 2026 at 12:3713 Feb 2026 at 11:54Z3 Apr 2026 at 13.0823 Aor 2026 at 13:0810 May 2026 at 13:5410 May 2026 at 14:3710 May 2026 at 13:4910 May 2026 at 13:5010 May 2026 at 13:5110 Mav 2026 at 12:51110 May 2026 at 13:5019 Dec 2025 at 11:3431 Oct 2025 at 12:2525 Nov 2025 at 17:596 Mar 2026 at 11.2224 Anr 2026 at 16:5220 Oct 2025 at 11:0218 Mar 2026 at 15:29• Mav 2026 at 11:09129 Oct 2025 at 19:329 May 2026 at 10:0418 Mar 2026 at 11:5510 May 2026 at 13:5712 Jun 2025 at 19:0430 Jan 2026 at 12:3630 Jan 2026 at 12:3616 Oct 2025 aт 16:0123 Apr 2026 at 13:0223 Apr 2026 at 13:0223 Apr 2026 at 13:0222 Anr 2026 at 12:0223 Apr 2026 at 13:0229 Jan 2026 at 15:20...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45150
|
1621
|
8
|
2026-05-14T14:22:45.653546+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768565653_m1.jpg...
|
PhpStorm
|
faVsco.js – ImportActivityTypes.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorRun PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelpabl100% (C47•FV faVsco.jsv* #12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTestvProject v© ReportController.php© AutomatedReportGenerated.phpTrackAutomatedReportGeneratedEvent.phpPlaybackController.php:8• Thu 14 May 17:22:45* :Q= custom.log› D ProvidersUserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.phpSendReportJob.phpD Sessionsv D Sms• DeleteCrmEntityTrait.phpDeleteAccountJob.phpImportActivityTypes.php X9 AccountController.phpE IntegrationAppTrait.php> O Intercomenv.stagingE.envDetachActivityObject.phpRematchActivityOnCrmObjectDetach.php© MatchActivityCrmData.phpv D PlanhatClient.php18© CreateSmsRecei© CreateSmsSentE© CreateReceivedNotC SmsListener.php> D Softphone© DialerMetricslmported.© ElasticSearchMetricsR© GenerateExportToken:© ImportRemote TrackLis© PlanhatActivityListeneReindexForAccountLisC ReindexForContactList© ReindexForGroupListelC ReindexForLeadListenReindexForOpportunit!© ReindexForUserListent© ResolveProvider.php© SendExportEmail.php© SetupintegrationActior© UserPilotActivityListen> C Authenticationv AutomatedReportsv C UserPilot|© TrackAutomatedRe> • Calendarsv D CrmBootstrapintegrationAl© ImportActivityTypes.pl© ImportMetadata.php© InitProfiles.php © HubspotPaginationService.php© HandleHubspotRateLimit.phpclass ImportActivitxTypes implementsShouldQueuepublic function handle(PlaybookCreated $event): voidTT Jon't run it somenow we atready nave categories.if ($playbook->getCategories()->isNotEmpty()) {return;A1$values = []:try {$crmService = $this->crmResolver->resolveForTeam($playbook->getTeamO):$crmService->syncField($playbook->getActivityField();$values = $crmService->importPicklistValues($playbook->getActivityField());} catch (Throwable $e) {Log::warning( message: '[ImportActivityTypes] CRM API failed, falling back to database values', ['playbook_id' => $playbook->getId),'team_id' => Splaybook->getTeamIdO.'error' => $e->getMessage(),1):if (empty($values)) {$values = $this->fetchActivityFieldValues(Splaybook->getActivityField);Log::info( message: '[ImportActivityTypes] Using database fallback for categories', l'playbook_id' => Splaybook->getId(),'field_values_count' => $values->count(),1):@ I avnitMndifierl isteneWorkspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)W Windsurf Teams=laravel.logSF [jiminny@localhost]A HS_local [jiminny@localho:W4 console [QAI PROD] X4 console [PROD]A console (EU]Go jiminny0841A3 X4 A Vm migrations oi V234m teams wherem crm_layouts !iM crm_layout_eiIM crm_fields Wim features;m team_feature:m opportunitie:m teams;1.id, CASEWHENid FROMsocial,I on u.id= sa.:1..n<->1: on t.L_id = 1052 andIMaccounts wheiactivities49:44UTF-8Co 4 spaces...
|
NULL
|
2832691924500541138
|
NULL
|
app_switch
|
ocr
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorRun PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelpabl100% (C47•FV faVsco.jsv* #12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTestvProject v© ReportController.php© AutomatedReportGenerated.phpTrackAutomatedReportGeneratedEvent.phpPlaybackController.php:8• Thu 14 May 17:22:45* :Q= custom.log› D ProvidersUserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.phpSendReportJob.phpD Sessionsv D Sms• DeleteCrmEntityTrait.phpDeleteAccountJob.phpImportActivityTypes.php X9 AccountController.phpE IntegrationAppTrait.php> O Intercomenv.stagingE.envDetachActivityObject.phpRematchActivityOnCrmObjectDetach.php© MatchActivityCrmData.phpv D PlanhatClient.php18© CreateSmsRecei© CreateSmsSentE© CreateReceivedNotC SmsListener.php> D Softphone© DialerMetricslmported.© ElasticSearchMetricsR© GenerateExportToken:© ImportRemote TrackLis© PlanhatActivityListeneReindexForAccountLisC ReindexForContactList© ReindexForGroupListelC ReindexForLeadListenReindexForOpportunit!© ReindexForUserListent© ResolveProvider.php© SendExportEmail.php© SetupintegrationActior© UserPilotActivityListen> C Authenticationv AutomatedReportsv C UserPilot|© TrackAutomatedRe> • Calendarsv D CrmBootstrapintegrationAl© ImportActivityTypes.pl© ImportMetadata.php© InitProfiles.php © HubspotPaginationService.php© HandleHubspotRateLimit.phpclass ImportActivitxTypes implementsShouldQueuepublic function handle(PlaybookCreated $event): voidTT Jon't run it somenow we atready nave categories.if ($playbook->getCategories()->isNotEmpty()) {return;A1$values = []:try {$crmService = $this->crmResolver->resolveForTeam($playbook->getTeamO):$crmService->syncField($playbook->getActivityField();$values = $crmService->importPicklistValues($playbook->getActivityField());} catch (Throwable $e) {Log::warning( message: '[ImportActivityTypes] CRM API failed, falling back to database values', ['playbook_id' => $playbook->getId),'team_id' => Splaybook->getTeamIdO.'error' => $e->getMessage(),1):if (empty($values)) {$values = $this->fetchActivityFieldValues(Splaybook->getActivityField);Log::info( message: '[ImportActivityTypes] Using database fallback for categories', l'playbook_id' => Splaybook->getId(),'field_values_count' => $values->count(),1):@ I avnitMndifierl isteneWorkspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)W Windsurf Teams=laravel.logSF [jiminny@localhost]A HS_local [jiminny@localho:W4 console [QAI PROD] X4 console [PROD]A console (EU]Go jiminny0841A3 X4 A Vm migrations oi V234m teams wherem crm_layouts !iM crm_layout_eiIM crm_fields Wim features;m team_feature:m opportunitie:m teams;1.id, CASEWHENid FROMsocial,I on u.id= sa.:1..n<->1: on t.L_id = 1052 andIMaccounts wheiactivities49:44UTF-8Co 4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45151
|
1621
|
9
|
2026-05-14T14:22:48.181636+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768568181_m1.jpg...
|
PhpStorm
|
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project Files
Preview
Filter
Open in Find Tool Win Project Files
Preview
Filter
Open in Find Tool Window
Impor
ImportActivityTypes.php .../app/Listeners/Crm/ImportActivityTypes.php, class
ImportBatchJobTrait.php .../app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php
ImportCall.php .../app/Jobs/Activity/Import/ImportCall.php, class
ImportActivityTypesTest.php .../tests/Unit/Listeners/Crm/ImportActivityTypesTest.php, class
ImportAccountBatch.php .../app/Jobs/Crm/Hubspot/ImportAccountBatch.php, class
ImportCallsCommand.php .../app/Console/Commands/Dev/ImportCallsCommand.php, class
ImportCallTest.php .../tests/Unit/Jobs/Activity/Import/ImportCallTest.php, final class
ImportContactBatch.php .../app/Jobs/Crm/Hubspot/ImportContactBatch.php, class
ImportBotRecordingJob.php .../app/Services/RecallAI/Jobs/ImportBotRecordingJob.php, class
ImportAccountBatchTest.php .../tests/Unit/Jobs/Crm/ImportAccountBatchTest.php, class
ImportContactBatchTest.php .../tests/Unit/Jobs/Crm/ImportContactBatchTest.php, class
ImportBotRecordingJobTest.php .../tests/Unit/Services/RecallAI/Jobs/ImportBotRecordingJobTest.php, class
ImportCall.php.html build/coverage/Jobs/Activity/Import
ImportActivityTypes.php.html build/coverage/Listeners/Crm
ImportCallsCommand.php.html build/coverage/Console/Commands/Dev
… more
ImportActivityTypes.php .../app/Listeners/Crm/ImportActivityTypes.php, class
ImportBatchJobTrait.php .../app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php
ImportCall.php .../app/Jobs/Activity/Import/ImportCall.php, class
ImportActivityTypesTest.php .../tests/Unit/Listeners/Crm/ImportActivityTypesTest.php, class
ImportAccountBatch.php .../app/Jobs/Crm/Hubspot/ImportAccountBatch.php, class
ImportCallsCommand.php .../app/Console/Commands/Dev/ImportCallsCommand.php, class
ImportCallTest.php .../tests/Unit/Jobs/Activity/Import/ImportCallTest.php, final class
ImportContactBatch.php .../app/Jobs/Crm/Hubspot/ImportContactBatch.php, class
ImportBotRecordingJob.php .../app/Services/RecallAI/Jobs/ImportBotRecordingJob.php, class
ImportAccountBatchTest.php .../tests/Unit/Jobs/Crm/ImportAccountBatchTest.php, class
ImportContactBatchTest.php .../tests/Unit/Jobs/Crm/ImportContactBatchTest.php, class
ImportBotRecordingJobTest.php .../tests/Unit/Services/RecallAI/Jobs/ImportBotRecordingJobTest.php, class
ImportCall.php.html build/coverage/Jobs/Activity/Import
ImportActivityTypes.php.html build/coverage/Listeners/Crm
ImportCallsCommand.php.html build/coverage/Console/Commands/Dev
… more
Listeners/Crm/ImportActivityTypes.php
Open In Right Split...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project Files","depth":2,"bounds":{"left":0.8298611,"top":0.25,"width":0.07430556,"height":0.026666667},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Preview","depth":2,"bounds":{"left":0.90416664,"top":0.25,"width":0.018055556,"height":0.026666667},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Filter","depth":2,"bounds":{"left":0.9222222,"top":0.25,"width":0.018055556,"height":0.026666667},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open in Find Tool Window","depth":2,"bounds":{"left":0.94027776,"top":0.25,"width":0.018055556,"height":0.026666667},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Impor","depth":1,"bounds":{"left":0.50555557,"top":0.29222223,"width":0.45277777,"height":0.032222223},"on_screen":true,"value":"Impor","role_description":"text field","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"ImportActivityTypes.php .../app/Listeners/Crm/ImportActivityTypes.php, class","depth":2,"bounds":{"left":0.49930555,"top":0.33666667,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportBatchJobTrait.php .../app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php","depth":2,"bounds":{"left":0.49930555,"top":0.3611111,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportCall.php .../app/Jobs/Activity/Import/ImportCall.php, class","depth":2,"bounds":{"left":0.49930555,"top":0.38555557,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportActivityTypesTest.php .../tests/Unit/Listeners/Crm/ImportActivityTypesTest.php, class","depth":2,"bounds":{"left":0.49930555,"top":0.41,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportAccountBatch.php .../app/Jobs/Crm/Hubspot/ImportAccountBatch.php, class","depth":2,"bounds":{"left":0.49930555,"top":0.43444446,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportCallsCommand.php .../app/Console/Commands/Dev/ImportCallsCommand.php, class","depth":2,"bounds":{"left":0.49930555,"top":0.4588889,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportCallTest.php .../tests/Unit/Jobs/Activity/Import/ImportCallTest.php, final class","depth":2,"bounds":{"left":0.49930555,"top":0.48333332,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportContactBatch.php .../app/Jobs/Crm/Hubspot/ImportContactBatch.php, class","depth":2,"bounds":{"left":0.49930555,"top":0.50777775,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportBotRecordingJob.php .../app/Services/RecallAI/Jobs/ImportBotRecordingJob.php, class","depth":2,"bounds":{"left":0.49930555,"top":0.5322222,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportAccountBatchTest.php .../tests/Unit/Jobs/Crm/ImportAccountBatchTest.php, class","depth":2,"bounds":{"left":0.49930555,"top":0.5566667,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportContactBatchTest.php .../tests/Unit/Jobs/Crm/ImportContactBatchTest.php, class","depth":2,"bounds":{"left":0.49930555,"top":0.58111113,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportBotRecordingJobTest.php .../tests/Unit/Services/RecallAI/Jobs/ImportBotRecordingJobTest.php, class","depth":2,"bounds":{"left":0.49930555,"top":0.60555553,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportCall.php.html build/coverage/Jobs/Activity/Import","depth":2,"bounds":{"left":0.49930555,"top":0.63,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportActivityTypes.php.html build/coverage/Listeners/Crm","depth":2,"bounds":{"left":0.49930555,"top":0.65444446,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportCallsCommand.php.html build/coverage/Console/Commands/Dev","depth":2,"bounds":{"left":0.49930555,"top":0.6788889,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"… more","depth":2,"bounds":{"left":0.49930555,"top":0.7033333,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportActivityTypes.php .../app/Listeners/Crm/ImportActivityTypes.php, class","depth":4,"bounds":{"left":0.49930555,"top":0.33666667,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportBatchJobTrait.php .../app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php","depth":4,"bounds":{"left":0.49930555,"top":0.3611111,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportCall.php .../app/Jobs/Activity/Import/ImportCall.php, class","depth":4,"bounds":{"left":0.49930555,"top":0.38555557,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportActivityTypesTest.php .../tests/Unit/Listeners/Crm/ImportActivityTypesTest.php, class","depth":4,"bounds":{"left":0.49930555,"top":0.41,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportAccountBatch.php .../app/Jobs/Crm/Hubspot/ImportAccountBatch.php, class","depth":4,"bounds":{"left":0.49930555,"top":0.43444446,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportCallsCommand.php .../app/Console/Commands/Dev/ImportCallsCommand.php, class","depth":4,"bounds":{"left":0.49930555,"top":0.4588889,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportCallTest.php .../tests/Unit/Jobs/Activity/Import/ImportCallTest.php, final class","depth":4,"bounds":{"left":0.49930555,"top":0.48333332,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportContactBatch.php .../app/Jobs/Crm/Hubspot/ImportContactBatch.php, class","depth":4,"bounds":{"left":0.49930555,"top":0.50777775,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportBotRecordingJob.php .../app/Services/RecallAI/Jobs/ImportBotRecordingJob.php, class","depth":4,"bounds":{"left":0.49930555,"top":0.5322222,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportAccountBatchTest.php .../tests/Unit/Jobs/Crm/ImportAccountBatchTest.php, class","depth":4,"bounds":{"left":0.49930555,"top":0.5566667,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportContactBatchTest.php .../tests/Unit/Jobs/Crm/ImportContactBatchTest.php, class","depth":4,"bounds":{"left":0.49930555,"top":0.58111113,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportBotRecordingJobTest.php .../tests/Unit/Services/RecallAI/Jobs/ImportBotRecordingJobTest.php, class","depth":4,"bounds":{"left":0.49930555,"top":0.60555553,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportCall.php.html build/coverage/Jobs/Activity/Import","depth":4,"bounds":{"left":0.49930555,"top":0.63,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportActivityTypes.php.html build/coverage/Listeners/Crm","depth":4,"bounds":{"left":0.49930555,"top":0.65444446,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ImportCallsCommand.php.html build/coverage/Console/Commands/Dev","depth":4,"bounds":{"left":0.49930555,"top":0.6788889,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"… more","depth":4,"bounds":{"left":0.49930555,"top":0.7033333,"width":0.4652778,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Listeners/Crm/ImportActivityTypes.php","depth":1,"bounds":{"left":0.51319444,"top":0.74777776,"width":0.15416667,"height":0.018888889},"on_screen":true,"help_text":"Listeners/Crm/ImportActivityTypes.php","role_description":"text"},{"role":"AXLink","text":"Open In Right Split","depth":1,"bounds":{"left":0.8673611,"top":0.74777776,"width":0.08055556,"height":0.018888889},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-7492745212563973318
|
-176829527558906079
|
visual_change
|
accessibility
|
NULL
|
Project Files
Preview
Filter
Open in Find Tool Win Project Files
Preview
Filter
Open in Find Tool Window
Impor
ImportActivityTypes.php .../app/Listeners/Crm/ImportActivityTypes.php, class
ImportBatchJobTrait.php .../app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php
ImportCall.php .../app/Jobs/Activity/Import/ImportCall.php, class
ImportActivityTypesTest.php .../tests/Unit/Listeners/Crm/ImportActivityTypesTest.php, class
ImportAccountBatch.php .../app/Jobs/Crm/Hubspot/ImportAccountBatch.php, class
ImportCallsCommand.php .../app/Console/Commands/Dev/ImportCallsCommand.php, class
ImportCallTest.php .../tests/Unit/Jobs/Activity/Import/ImportCallTest.php, final class
ImportContactBatch.php .../app/Jobs/Crm/Hubspot/ImportContactBatch.php, class
ImportBotRecordingJob.php .../app/Services/RecallAI/Jobs/ImportBotRecordingJob.php, class
ImportAccountBatchTest.php .../tests/Unit/Jobs/Crm/ImportAccountBatchTest.php, class
ImportContactBatchTest.php .../tests/Unit/Jobs/Crm/ImportContactBatchTest.php, class
ImportBotRecordingJobTest.php .../tests/Unit/Services/RecallAI/Jobs/ImportBotRecordingJobTest.php, class
ImportCall.php.html build/coverage/Jobs/Activity/Import
ImportActivityTypes.php.html build/coverage/Listeners/Crm
ImportCallsCommand.php.html build/coverage/Console/Commands/Dev
… more
ImportActivityTypes.php .../app/Listeners/Crm/ImportActivityTypes.php, class
ImportBatchJobTrait.php .../app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php
ImportCall.php .../app/Jobs/Activity/Import/ImportCall.php, class
ImportActivityTypesTest.php .../tests/Unit/Listeners/Crm/ImportActivityTypesTest.php, class
ImportAccountBatch.php .../app/Jobs/Crm/Hubspot/ImportAccountBatch.php, class
ImportCallsCommand.php .../app/Console/Commands/Dev/ImportCallsCommand.php, class
ImportCallTest.php .../tests/Unit/Jobs/Activity/Import/ImportCallTest.php, final class
ImportContactBatch.php .../app/Jobs/Crm/Hubspot/ImportContactBatch.php, class
ImportBotRecordingJob.php .../app/Services/RecallAI/Jobs/ImportBotRecordingJob.php, class
ImportAccountBatchTest.php .../tests/Unit/Jobs/Crm/ImportAccountBatchTest.php, class
ImportContactBatchTest.php .../tests/Unit/Jobs/Crm/ImportContactBatchTest.php, class
ImportBotRecordingJobTest.php .../tests/Unit/Services/RecallAI/Jobs/ImportBotRecordingJobTest.php, class
ImportCall.php.html build/coverage/Jobs/Activity/Import
ImportActivityTypes.php.html build/coverage/Listeners/Crm
ImportCallsCommand.php.html build/coverage/Console/Commands/Dev
… more
Listeners/Crm/ImportActivityTypes.php
Open In Right Split...
|
45150
|
NULL
|
NULL
|
NULL
|
|
45153
|
1621
|
10
|
2026-05-14T14:22:54.243568+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768574243_m1.jpg...
|
PhpStorm
|
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
saave
Inherited members (⌘R)
Anonymous Classes (⌘I saave
Inherited members (⌘R)
Anonymous Classes (⌘I)
Lambdas (⌘L)
WriteCrmTrait.php...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"saave","depth":1,"bounds":{"left":0.52152777,"top":0.32,"width":0.06458333,"height":0.031111112},"on_screen":true,"value":"saave","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Inherited members (⌘R)","depth":1,"bounds":{"left":0.5277778,"top":0.35555556,"width":0.10972222,"height":0.031111112},"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Anonymous Classes (⌘I)","depth":1,"bounds":{"left":0.64444447,"top":0.35555556,"width":0.10972222,"height":0.031111112},"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Lambdas (⌘L)","depth":1,"bounds":{"left":0.76111114,"top":0.35555556,"width":0.10972222,"height":0.031111112},"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"WriteCrmTrait.php","depth":1,"bounds":{"left":0.5277778,"top":0.3188889,"width":0.35277778,"height":0.036666665},"on_screen":true,"role_description":"text"}]...
|
-6236990536150617012
|
938158309487346305
|
visual_change
|
hybrid
|
NULL
|
saave
Inherited members (⌘R)
Anonymous Classes (⌘I saave
Inherited members (⌘R)
Anonymous Classes (⌘I)
Lambdas (⌘L)
WriteCrmTrait.php
PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelplhlFV faVsco.jsv#12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTestvProject v100% С8• Thu 14 May 17:22:54Q© ReportController.php© AutomatedReportGenerated.phpTrackAutomatedReportGeneratedEvent.phpPlaybackController.php:=custom.log> D CrmObjectsUserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.phpSendReportJob.php• DecorateActivity› D Dummy• DeleteCrmEntityTrait.phpDeleteAccountJob.php© ImportActivityTypes.phpT WriteCrmTrait.php X© AccountController.php› HelpersT IntegrationAppTrait.php=.env.staging.envDetachActivityObject.phpRematchActivityOnCrmObjectDetach.phpv Hubspot© MatchActivityCrmData.php© Client.php© HubspotPaginationService.phpHandleHubspotRateLimit.php18D AccountSyncStrate› DJ Actions12<? phpA7 . vD ContactSyncStrate!> DDTOdeclare(strict_types=1):› OJ FieldsQsaaveWriteCrmTrait.php› D Journalnamespace Jiminny\Services\Crm\Hubspot\ServiceTraInherited members (28R)Anonymous Classes (81)Lambdas (2L)Metadatav OpportunitySyncStiUse> [ Concerns© HubspotLastMor1819 @20trait WriteCrmTrait© HubspotLastMor{© HubspotLastMor© HubspotLastMor21© HubspotLastMor22© HubspotSingleS:© HubspotSyncStr4 usages2 usages2 usages"saave' not foundprivate const string ACTION_CREATE = 'create'private const string ACTION_UPDATE = 'update'© HubspotWebhocv D Pagination© HubspotPaginati© PaginationConfig© PaginationState.> D ProspectSearchStra> D Redisv D ServiceTraits€ OpportunitySync© SyncCrmEntities+ SyncFieldsTrait./232425262728 € ›5051525354private const string ACTION_DELETE = 'delete'/***/* @inheritdocpublic function saveActivity(Activity $activity): Activity{…..}T WriteCrmTrait.pl/*** @throws HubspotException* Othrows BadRequest*/1 usageD Utils5589private function buildConferencePayload(Activity $activity): Activity{...}D Webhook@ RatchSvneCnllertn'2 usagesWorkspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)=laravel.logSF [jiminny@localhost]A HS_local [jiminny@localho:W4 console [QAI PROD] X4 console (PROD]A console (EU]Go jiminnyA3X4 A Vmmigrations oim teams wherem crm_layouts !iM crm_layout_eiIM crm_fields Wim features;m team_feature:m opportunitie:m teams;1.id, CASEWHEN226227228229230231232233 V234id FROM social,1 on V.id = sa.:1..n<->1: on t.Lid = 1052 andIMaccounts wheiactivitiesW Windsurf Teams1:1UTF-8Co 4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45154
|
1621
|
11
|
2026-05-14T14:22:57.279229+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768577279_m1.jpg...
|
PhpStorm
|
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
s
Inherited members (⌘R)
Anonymous Classes (⌘I)
La s
Inherited members (⌘R)
Anonymous Classes (⌘I)
Lambdas (⌘L)
WriteCrmTrait
ACTION_CREATE: string = 'create', private
ACTION_DELETE: string = 'delete', private
ACTION_UPDATE: string = 'update', private
buildAssociation(id: int, categoryToTypeIds: array): array, private method
buildAssociationType(category: string, typeId: int): array, private method
buildSoftphonePayload(activity: Activity): Activity, private method
buildTextMessagePayload(activity: Activity): Activity, private method
convertActivityMeetingAssociationsV4(activity: Activity): array, private method
createMeetingV3(engagement: array, associations: array, metadata: array): Response, private method
fetchCustomFieldData(activity: Activity, objectType: string): array, private method
getActivityAssociations(activity: Activity): array, private method
getAssociationDifferences(activity: Activity, oldAssociations: array, currentAssociations: array): array, private method
hasEngagementActivityType(engagementId: string): bool, private method
logCrmEngagementManipulation(action: string, engagementData: array, [data: array = [...]], [associations: array = [...]]): void, private method
normalizeIds(ids: array): array, private method
saveActivity(activity: Activity): Activity ↑RemoteEntityManipulationInterface, public method
saveFollowupActivity(activity: Activity, fields: array): null|string ↑RemoteEntityManipulationInterface, public method
shouldIncludeRemovedAssociations(type: string, added: array, activity: Activity): bool, private method
updateActivityAssociations(activity: Activity, oldAssociations: array): void, private method
updateAssociations(activity: Activity, associationChanges: array): void, private method
updateMeetingV3(meetingId: string, engagement: array, metadata: array): Response, private method
updateNonTaskActivity(activity: Activity, engagement: array, metadata: array, oldAssociations: array): void, private method
updateTask(activity: Activity, engagement: array, metadata: array): void, private method
upsertActivity(activity: Activity, engagement: array, metadata: array): Activity, method
ACTION_CREATE: string = 'create', private
ACTION_DELETE: string = 'delete', private...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"s","depth":1,"bounds":{"left":0.52152777,"top":0.32,"width":0.06458333,"height":0.031111112},"on_screen":true,"value":"s","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Inherited members (⌘R)","depth":1,"bounds":{"left":0.5277778,"top":0.35555556,"width":0.10972222,"height":0.031111112},"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Anonymous Classes (⌘I)","depth":1,"bounds":{"left":0.64444447,"top":0.35555556,"width":0.10972222,"height":0.031111112},"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Lambdas (⌘L)","depth":1,"bounds":{"left":0.76111114,"top":0.35555556,"width":0.10972222,"height":0.031111112},"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"WriteCrmTrait","depth":4,"bounds":{"left":0.5395833,"top":0.16777778,"width":0.077083334,"height":0.024444444},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ACTION_CREATE: string = 'create', private","depth":5,"bounds":{"left":0.55277777,"top":0.19222222,"width":0.1736111,"height":0.024444444},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ACTION_DELETE: string = 'delete', private","depth":5,"bounds":{"left":0.55277777,"top":0.21666667,"width":0.17152777,"height":0.024444444},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ACTION_UPDATE: string = 'update', private","depth":5,"bounds":{"left":0.55277777,"top":0.24111111,"width":0.17708333,"height":0.024444444},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"buildAssociation(id: int, categoryToTypeIds: array): array, private method","depth":5,"bounds":{"left":0.55277777,"top":0.26555556,"width":0.27083334,"height":0.024444444},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"buildAssociationType(category: string, typeId: int): array, private method","depth":5,"bounds":{"left":0.55277777,"top":0.29,"width":0.26805556,"height":0.024444444},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"buildSoftphonePayload(activity: Activity): Activity, private method","depth":5,"bounds":{"left":0.55277777,"top":0.31444445,"width":0.23888889,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"buildTextMessagePayload(activity: Activity): Activity, private method","depth":5,"bounds":{"left":0.55277777,"top":0.33888888,"width":0.25277779,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"convertActivityMeetingAssociationsV4(activity: Activity): array, private method","depth":5,"bounds":{"left":0.55277777,"top":0.36333334,"width":0.2951389,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"createMeetingV3(engagement: array, associations: array, metadata: array): Response, private method","depth":5,"bounds":{"left":0.55277777,"top":0.38777778,"width":0.39375,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"fetchCustomFieldData(activity: Activity, objectType: string): array, private method","depth":5,"bounds":{"left":0.55277777,"top":0.41222224,"width":0.30833334,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"getActivityAssociations(activity: Activity): array, private method","depth":5,"bounds":{"left":0.55277777,"top":0.43666667,"width":0.22986111,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"getAssociationDifferences(activity: Activity, oldAssociations: array, currentAssociations: array): array, private method","depth":5,"bounds":{"left":0.55277777,"top":0.4611111,"width":0.44722223,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"hasEngagementActivityType(engagementId: string): bool, private method","depth":5,"bounds":{"left":0.55277777,"top":0.48555556,"width":0.27152777,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"logCrmEngagementManipulation(action: string, engagementData: array, [data: array = [...]], [associations: array = [...]]): void, private method","depth":5,"bounds":{"left":0.55277777,"top":0.51,"width":0.44722223,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"normalizeIds(ids: array): array, private method","depth":5,"bounds":{"left":0.55277777,"top":0.53444445,"width":0.15486111,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"saveActivity(activity: Activity): Activity ↑RemoteEntityManipulationInterface, public method","depth":5,"bounds":{"left":0.55277777,"top":0.5588889,"width":0.3548611,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"saveFollowupActivity(activity: Activity, fields: array): null|string ↑RemoteEntityManipulationInterface, public method","depth":5,"bounds":{"left":0.55277777,"top":0.5833333,"width":0.44722223,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"shouldIncludeRemovedAssociations(type: string, added: array, activity: Activity): bool, private method","depth":5,"bounds":{"left":0.55277777,"top":0.6077778,"width":0.3923611,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"updateActivityAssociations(activity: Activity, oldAssociations: array): void, private method","depth":5,"bounds":{"left":0.55277777,"top":0.63222224,"width":0.34097221,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"updateAssociations(activity: Activity, associationChanges: array): void, private method","depth":5,"bounds":{"left":0.55277777,"top":0.6566667,"width":0.32777777,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"updateMeetingV3(meetingId: string, engagement: array, metadata: array): Response, private method","depth":5,"bounds":{"left":0.55277777,"top":0.6811111,"width":0.38819444,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"updateNonTaskActivity(activity: Activity, engagement: array, metadata: array, oldAssociations: array): void, private method","depth":5,"bounds":{"left":0.55277777,"top":0.70555556,"width":0.44722223,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"updateTask(activity: Activity, engagement: array, metadata: array): void, private method","depth":5,"bounds":{"left":0.55277777,"top":0.73,"width":0.33472222,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"upsertActivity(activity: Activity, engagement: array, metadata: array): Activity, method","depth":5,"bounds":{"left":0.55277777,"top":0.7544444,"width":0.34652779,"height":0.024444444},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ACTION_CREATE: string = 'create', private","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.1736111,"height":0.024444444},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ACTION_DELETE: string = 'delete', private","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.17152777,"height":0.024444444},"on_screen":false,"role_description":"text"}]...
|
7231423580236642576
|
8728989992795289067
|
visual_change
|
accessibility
|
NULL
|
s
Inherited members (⌘R)
Anonymous Classes (⌘I)
La s
Inherited members (⌘R)
Anonymous Classes (⌘I)
Lambdas (⌘L)
WriteCrmTrait
ACTION_CREATE: string = 'create', private
ACTION_DELETE: string = 'delete', private
ACTION_UPDATE: string = 'update', private
buildAssociation(id: int, categoryToTypeIds: array): array, private method
buildAssociationType(category: string, typeId: int): array, private method
buildSoftphonePayload(activity: Activity): Activity, private method
buildTextMessagePayload(activity: Activity): Activity, private method
convertActivityMeetingAssociationsV4(activity: Activity): array, private method
createMeetingV3(engagement: array, associations: array, metadata: array): Response, private method
fetchCustomFieldData(activity: Activity, objectType: string): array, private method
getActivityAssociations(activity: Activity): array, private method
getAssociationDifferences(activity: Activity, oldAssociations: array, currentAssociations: array): array, private method
hasEngagementActivityType(engagementId: string): bool, private method
logCrmEngagementManipulation(action: string, engagementData: array, [data: array = [...]], [associations: array = [...]]): void, private method
normalizeIds(ids: array): array, private method
saveActivity(activity: Activity): Activity ↑RemoteEntityManipulationInterface, public method
saveFollowupActivity(activity: Activity, fields: array): null|string ↑RemoteEntityManipulationInterface, public method
shouldIncludeRemovedAssociations(type: string, added: array, activity: Activity): bool, private method
updateActivityAssociations(activity: Activity, oldAssociations: array): void, private method
updateAssociations(activity: Activity, associationChanges: array): void, private method
updateMeetingV3(meetingId: string, engagement: array, metadata: array): Response, private method
updateNonTaskActivity(activity: Activity, engagement: array, metadata: array, oldAssociations: array): void, private method
updateTask(activity: Activity, engagement: array, metadata: array): void, private method
upsertActivity(activity: Activity, engagement: array, metadata: array): Activity, method
ACTION_CREATE: string = 'create', private
ACTION_DELETE: string = 'delete', private...
|
45153
|
NULL
|
NULL
|
NULL
|
|
45155
|
1621
|
12
|
2026-05-14T14:22:58.920709+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768578920_m1.jpg...
|
PhpStorm
|
faVsco.js – WriteCrmTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorRun PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelpabl100% (C47•FV faVsco.jsv* #12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTest* :Project v8• Thu 14 May 17:22:58Q© ReportController.php© AutomatedReportGenerated.phpTrackAutomatedReportGeneratedEvent.phpPlaybackController.php=custom.log> D CrmObjects© UserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.php• DecorateActivity› D Dummy• DeleteCrmEntityTrait.phpDeleteAccountJob.php© ImportActivityTypes.php› HelpersT IntegrationAppTrait.php.env.staging.envDetachActivityObject.phpv HubspotClient.php18D AccountSyncStrate› DJ Actions1921D ContactSyncStrate!› O DTO› O Fields› D Journal© MatchActivityCrmData.phptrait WriteCrmTraitprivate const string ACTION_CREATE = 'create' :2 usages2 usages© HubspotPaginationService.php22private const string ACTION_UPDATE = 'update' ;23Metadataprivate const string ACTION_DELETE = 'delete';24v OpportunitySyncSti> [ Concerns25© HubspotLastMor26© HubspotLastMor27/***/* @inheritdoc© HubspotLastMor28 €public function saveActivity(Activity $activity): Activity© HubspotLastMor2930© HubspotLastMor31© HubspotSingleS:© HubspotSyncStr32switch ($activity->getCrmType)) {case Activity:: TYPE_CONFERENCE:Sactivity = $this->buildConferencePayload(Sactivity);© HubspotWebhoc33v D Pagination34© HubspotPaginati35© PaginationConfig36© PaginationState.37break;case Activity:: TYPE_SOFTPHONE:case Activity::TYPE_SOFTPHONE_INBOUND:Sactivity = $this->buildSoftphonePayload($activity);> D ProspectSearchStra38› [ Redis39break;v D ServiceTraits40€ OpportunitySync41© SyncCrmEntities42+ SyncFieldsTrait./43case Activity::TYPE_SMS_INBOUND:case Activity: : TYPE_SMS_OUTBOUND:Sactivity = $this->buildTextMessagePayload($activity);T WriteCrmTrait.pl44D Utils45break;D Webhook46@ RatchSvnrCnllertn'47Workspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)SendReportJob.php=laravel.logT WriteCrmTrait.php xAccountController.phpSF [jiminny@localhost]RematchActivityOnCrmObjectDetach.phpA HS_local [jiminny@localho:WHandleHubspotRateLimit.php4 console [QAI PROD] XA7 AV4 console [PROD]A console (EU]DGo jiminny08A1 43M4AY211m migrations oi212-213214215— 216m teams wherem crm_layouts !iM crm_layout_eiIM crm_fields Wi217218219220m features;m team_feature:m opportunitie:221222m teams;223224.id, CASEWHEN=225— 226227228229230,id FROM social,1 on V.id = sa.:1..n<->1: on t.Lid = 1052 and231232233 VIMaccounts wheiactivities234W Windsurf Teams28:21UTF-8Co 4 spaces...
|
NULL
|
-6457976466920580779
|
NULL
|
click
|
ocr
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorRun PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelpabl100% (C47•FV faVsco.jsv* #12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTest* :Project v8• Thu 14 May 17:22:58Q© ReportController.php© AutomatedReportGenerated.phpTrackAutomatedReportGeneratedEvent.phpPlaybackController.php=custom.log> D CrmObjects© UserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.php• DecorateActivity› D Dummy• DeleteCrmEntityTrait.phpDeleteAccountJob.php© ImportActivityTypes.php› HelpersT IntegrationAppTrait.php.env.staging.envDetachActivityObject.phpv HubspotClient.php18D AccountSyncStrate› DJ Actions1921D ContactSyncStrate!› O DTO› O Fields› D Journal© MatchActivityCrmData.phptrait WriteCrmTraitprivate const string ACTION_CREATE = 'create' :2 usages2 usages© HubspotPaginationService.php22private const string ACTION_UPDATE = 'update' ;23Metadataprivate const string ACTION_DELETE = 'delete';24v OpportunitySyncSti> [ Concerns25© HubspotLastMor26© HubspotLastMor27/***/* @inheritdoc© HubspotLastMor28 €public function saveActivity(Activity $activity): Activity© HubspotLastMor2930© HubspotLastMor31© HubspotSingleS:© HubspotSyncStr32switch ($activity->getCrmType)) {case Activity:: TYPE_CONFERENCE:Sactivity = $this->buildConferencePayload(Sactivity);© HubspotWebhoc33v D Pagination34© HubspotPaginati35© PaginationConfig36© PaginationState.37break;case Activity:: TYPE_SOFTPHONE:case Activity::TYPE_SOFTPHONE_INBOUND:Sactivity = $this->buildSoftphonePayload($activity);> D ProspectSearchStra38› [ Redis39break;v D ServiceTraits40€ OpportunitySync41© SyncCrmEntities42+ SyncFieldsTrait./43case Activity::TYPE_SMS_INBOUND:case Activity: : TYPE_SMS_OUTBOUND:Sactivity = $this->buildTextMessagePayload($activity);T WriteCrmTrait.pl44D Utils45break;D Webhook46@ RatchSvnrCnllertn'47Workspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)SendReportJob.php=laravel.logT WriteCrmTrait.php xAccountController.phpSF [jiminny@localhost]RematchActivityOnCrmObjectDetach.phpA HS_local [jiminny@localho:WHandleHubspotRateLimit.php4 console [QAI PROD] XA7 AV4 console [PROD]A console (EU]DGo jiminny08A1 43M4AY211m migrations oi212-213214215— 216m teams wherem crm_layouts !iM crm_layout_eiIM crm_fields Wi217218219220m features;m team_feature:m opportunitie:221222m teams;223224.id, CASEWHEN=225— 226227228229230,id FROM social,1 on V.id = sa.:1..n<->1: on t.Lid = 1052 and231232233 VIMaccounts wheiactivities234W Windsurf Teams28:21UTF-8Co 4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45156
|
1622
|
3
|
2026-05-14T14:22:58.942966+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768578942_m2.jpg...
|
PhpStorm
|
faVsco.js – WriteCrmTrait.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
ActivityFilesLaterJiminny... ~@ jiminny-x-integrat ActivityFilesLaterJiminny... ~@ jiminny-x-integrati& platform-inner-team© Channels# ai-chapter# alertsi backend# bugscontusion-cllnia# curiosity_lab# engineering# general# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the people of iimi..• Direct messages8. A...• A3 62P. Galya Dimitrova Mvasil VasilevA. Stefka Stoyanova%: Todor Stamatovf. Mario GeorgievP. Nikolay Ivanov2o James Graham "2. Stoyan Tanev. Steliyan Georgiev. Petko Kashinski*. Lukas Kovali...a: Apps® ToastS lira Gloud6 Huddle with Aneliya Angelova& R. Aneliya Angelova •• Messagest Add canvasur FilesAneliya Angelova 2:30 PMЛукаш, само при Сейлсфорс и Хубспот се синкваха активити типовете, когато се направи плейбук,нали?пои останалите скМі тоябва оъчно ла се въвелат.Lukas Kovalik 2:47 PMтряова да се за всички, някьде не се ли попьлвапри зохо май беше hardcoded но май и там си връщаха две категорииAneliya Angelova @ 4:00 PMЛукаш имаш ли време да се чуем за тестването на https://jiminny.atlassian.net/browse/JY-20725X Preview insackt[HubSpot] Optimise CRM rematctC OpenReady for QA- MediumAA Aneliya AngelovaAs of today at 4:00 PM RetreshOpen in Jira+ SummariseLukas Kovalik 4:02 PMzean-iurwiniareelYou joined the huddle LIVE 4:06 PMAneliva Angelova is here toolAneliya Angelova 4:44 PM11512582Lukas Kovalik O 5:16 PMcurl --location "httns:ani.hubani.com/crm/v3/obiects/contacts/search--header 'Content-Type: application/json'\--header "Authorization: BearerC|Kvma?iMx7OINOM8kOFwrAowAcAkUAhIR24?A05D?xiVrIYMILuP6SA01Kwcmhr=n.v2 hytorDniMvDalYck-CMalovolNOM9kOFwrAUAcrkcaws-ThwRARIRAOF-ATFSAOFRAO-ROIEBAQUBEggBAQEBAYICFIґ7Au8O2Nwal9YW2CbCCxYvwngmSgNldTFSAFoAYABoAHAAeAA'\--data "{ "limit", 13'Message Aneliva Angelova+ AaIAl Notes: OffLeave~ (Q SearchDate ModifiedYesterday at 13.34Yesterday at 13:34Yesterday at 13.33Yesterdav at 13:32resterday al i5.sYesterdav at 13:31Yesterday at 13:30Yesterday at 13.29Yesterday at 13:29resterday at 15.2oYesterday at 13:27Yesterday at 13:26Yesterday at 13:26Yesterday at 13:25Yesterday at 13.24Yesterdav at 13:24Yesterday at 13:23Yesterday at 13:21Yesterday at 13:21Yesterday at 13.20Yesterdav at 13:19Yesterday at 13:19Yesterday at 13:18Yesterday at 13:18Yesterday at 13:17Yesterday at 13.16Yecterdav at 12:16Yesterday at 13:15Yesterday at 13.10Yesterdav at 13:14Yesterday at 13:14Yesterday at 13:13Yecterdav at 12:12Yesterday at 13.11Yesterdav at 13:11Yesterday at 13:10Yesterday at 13:09Yesterday at 13:09Yesterday at 13:08Yesterday at 13.08Yecterdav at 12:07Yesterday at 13:07Yesterdav at 13:0617K:MPEG-4 movie29 KBMPEG-4 movie6 KB12 Kb9 KBIMPEG-4 movieMPEG-4 movie7 KB8 KBMPEG-4 movie37 K:10 KBMPEG-4 movie7 KBMPEG-4 movie9 KBMPEG-4 movie8 KB72 KBMPEG-4 movie14 K:13 KBMPEG-4 movie9 KB18 KB|MPEG-4 movie12 KBMPEG-4 movie10 KB16 KBMPEG-4 movie6KEMPEG-4 movie6 KB MPEG-4 movie12 KB23 K:MPEG-4 movie8 KBMPEG-4 movie6 KBMPEG-4 movie11 KEMPEG-4 movie11 KBMPEG-4 movie20 KB34 K:MPEG-4 movie10 KBMPEG-4 movie7 KB5KBMPEG-4 movie11 KPMPEG-4 movie26 KB MPEG-4 movie111 KBMPEG-4 movie102 K:MPEG-4 movie88 KBMPEG-4 movie59 KB98 KBMPEG-4 movie07 KPMDEG-A movie66 KBMPEG-4 movie44 KBMPEG-4 movie03 K:IMPEG-A movid78 KB MPEG-4 movie50 KB58 KB27 KPMPEG-4 movieMPEG-4 movie7 KB12 KBMPEG-4 movie22 K:17 KBMPEG-4 movie19 KB32 K8MPEG-4 movieFavouritesE jiminny© Recents* ApplicationsiCloudiCloud Drive228 Sync folderQ DXP4800PLUS-B5FA@ Network|• CRMI• Orange• Red• Yellow• Green• Purple•) All lags..lohlDownloadsNameLoom.pkgAlfred copv.alfredoreterencesB KeychronAssist-1.0.2 (1).dmgA Keychron Assist-1.0.2.dmgmazanoke-images-ywJo.ziPhotos-3-001.zipD Transcript.pdf→mage U.loge1 Orioninstaller.dmaimage.jpg- image (2).1pc• ПО-22221726037035-004-001_ORGES.pdf• %D0%9F%D0%9E-22221726037035-004-001_archive.zipПO-22221726037035-004-001_archive (1).zip• repon 4).XmAltred copy2.altredoreterences05012026_0000000026574472_ SWIFT_OB70501260015890.pdf27022026_0000000026574472_SWIFT_OB72702260049200.pdf= 03042026 [CREDIT_CARD] SWIFT [CREDIT_CARD].001B reporti) xm=pdt.odipdf-1.pdfD pdf-2.pdf-pdf-5.pd1= ndf-1 ndipdf-3.pdfB Rovaix Famly Treo gedbitwarden export 20251031122528.isonKoválik Family Tree.zip*macOS Storage_Cleanup.mdal favicon icofirst_aid_notes_complete.docxrenortl2).esvconfig.ymlIteration run Search HS.postman_collection.json--report(1).csvm licence hettertouchtoalMariusHosting Config.json1ooks-891a6503-bbb7-4b2b-9c3.csv•Alfredmazanoke-images-YWJ6ана Ковалик.jpg•искане даниел Ковалик..pg• Фактура Март Даниел Ковалик.jрс• Фактура Април Даниел Ковалик.jpg• Dhotac 2.001Q SearchKind00,4 MDinstdlle..dckage55.9 MBAlfred...ferences10,1 MBDisk Image10,1 MBDisk ImageIL MBLiP archive6,6 MBZIP archive2,5 MB PDF Document2,5 MBJreo lmage2.2 MBDisk Image2,2 MB PDF Document2 MBJPEG image1,9 MBJPEG imaqe192 KBPDF Document140 KbZIP archive148 KВZIP archivel148 KBZIP archive122 KBXML document111 KBAlfred...ferences94 KBPDF Document92 KBPDr DocumentK:91 KB91 KB30 KB29 KBPDF DocumentXML documentPDF Document28 KBPDF Document28 KR28 KB27 KB14 KB11 KB6 KB6KBPDE DocumentDocumentJSONICSV DocumentZIP archiveMarkdo…..ument5KRWindo...n image4 KB3 KBword ..cumentCSV Document2 KBYAML document1 KBcSV Document928 byteshttlicence183 bytesZero butesJSONAlfred.. ferencesZero bytesFolde1,9 MB1,8 MB17 MB1,7 MBColdorJPEG imageJPEG ImageIPEG imadeJPEG imageColdo1 of 58 selected, 9.63 GB availabld• Inu 14 May 1/.L2:00Date AddedIs Mdl ZUzo dl 19:4530 Jan 2026 at 12:3617 Mar 2026 at 20:2717 Mar 2026 at 20:26Z3 Aor 2020 al 13:0229 Jan 2026 at 15:2019 Dec 2025 at 10:1619 Dec 2025 at 12:238 Aor 2026 at 20:3519 Dec 2025 at 10:2919 Dec 2025 at 12:1819 Dec 2025 at 12:4026 Mar 2026 at 11:2410 May 2026 at 13:5326 Mar 2026 at 11:2426 Mar 2026 at 11:2426 Mar 2026 at 11:2310 May 2026 at 14:3730 Jan 2026 at 12:3713 Feb 2026 at 11:54Z3 Apr 2026 at 13.0823 Aor 2026 at 13:0810 May 2026 at 13:5410 May 2026 at 14:3710 May 2026 at 13:4910 May 2026 at 13:5010 May 2026 at 13:5110 Mav 2026 at 12:51110 May 2026 at 13:5019 Dec 2025 at 11:3431 Oct 2025 at 12:2525 Nov 2025 at 17:596 Mar 2026 at 11.2224 Anr 2026 at 16:5220 Oct 2025 at 11:0218 Mar 2026 at 15:29• Mav 2026 at 11:09129 Oct 2025 at 19:329 May 2026 at 10:0418 Mar 2026 at 11:5510 May 2026 at 13:5712 Jun 2025 at 19:0430 Jan 2026 at 12:3630 Jan 2026 at 12:3616 Oct 2025 aт 16:0123 Apr 2026 at 13:0223 Apr 2026 at 13:0223 Apr 2026 at 13:0222 Anr 2026 at 12:0223 Apr 2026 at 13:0229 Jan 2026 at 15:20...
|
NULL
|
4675722753822127110
|
NULL
|
click
|
ocr
|
NULL
|
ActivityFilesLaterJiminny... ~@ jiminny-x-integrat ActivityFilesLaterJiminny... ~@ jiminny-x-integrati& platform-inner-team© Channels# ai-chapter# alertsi backend# bugscontusion-cllnia# curiosity_lab# engineering# general# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the people of iimi..• Direct messages8. A...• A3 62P. Galya Dimitrova Mvasil VasilevA. Stefka Stoyanova%: Todor Stamatovf. Mario GeorgievP. Nikolay Ivanov2o James Graham "2. Stoyan Tanev. Steliyan Georgiev. Petko Kashinski*. Lukas Kovali...a: Apps® ToastS lira Gloud6 Huddle with Aneliya Angelova& R. Aneliya Angelova •• Messagest Add canvasur FilesAneliya Angelova 2:30 PMЛукаш, само при Сейлсфорс и Хубспот се синкваха активити типовете, когато се направи плейбук,нали?пои останалите скМі тоябва оъчно ла се въвелат.Lukas Kovalik 2:47 PMтряова да се за всички, някьде не се ли попьлвапри зохо май беше hardcoded но май и там си връщаха две категорииAneliya Angelova @ 4:00 PMЛукаш имаш ли време да се чуем за тестването на https://jiminny.atlassian.net/browse/JY-20725X Preview insackt[HubSpot] Optimise CRM rematctC OpenReady for QA- MediumAA Aneliya AngelovaAs of today at 4:00 PM RetreshOpen in Jira+ SummariseLukas Kovalik 4:02 PMzean-iurwiniareelYou joined the huddle LIVE 4:06 PMAneliva Angelova is here toolAneliya Angelova 4:44 PM11512582Lukas Kovalik O 5:16 PMcurl --location "httns:ani.hubani.com/crm/v3/obiects/contacts/search--header 'Content-Type: application/json'\--header "Authorization: BearerC|Kvma?iMx7OINOM8kOFwrAowAcAkUAhIR24?A05D?xiVrIYMILuP6SA01Kwcmhr=n.v2 hytorDniMvDalYck-CMalovolNOM9kOFwrAUAcrkcaws-ThwRARIRAOF-ATFSAOFRAO-ROIEBAQUBEggBAQEBAYICFIґ7Au8O2Nwal9YW2CbCCxYvwngmSgNldTFSAFoAYABoAHAAeAA'\--data "{ "limit", 13'Message Aneliva Angelova+ AaIAl Notes: OffLeave~ (Q SearchDate ModifiedYesterday at 13.34Yesterday at 13:34Yesterday at 13.33Yesterdav at 13:32resterday al i5.sYesterdav at 13:31Yesterday at 13:30Yesterday at 13.29Yesterday at 13:29resterday at 15.2oYesterday at 13:27Yesterday at 13:26Yesterday at 13:26Yesterday at 13:25Yesterday at 13.24Yesterdav at 13:24Yesterday at 13:23Yesterday at 13:21Yesterday at 13:21Yesterday at 13.20Yesterdav at 13:19Yesterday at 13:19Yesterday at 13:18Yesterday at 13:18Yesterday at 13:17Yesterday at 13.16Yecterdav at 12:16Yesterday at 13:15Yesterday at 13.10Yesterdav at 13:14Yesterday at 13:14Yesterday at 13:13Yecterdav at 12:12Yesterday at 13.11Yesterdav at 13:11Yesterday at 13:10Yesterday at 13:09Yesterday at 13:09Yesterday at 13:08Yesterday at 13.08Yecterdav at 12:07Yesterday at 13:07Yesterdav at 13:0617K:MPEG-4 movie29 KBMPEG-4 movie6 KB12 Kb9 KBIMPEG-4 movieMPEG-4 movie7 KB8 KBMPEG-4 movie37 K:10 KBMPEG-4 movie7 KBMPEG-4 movie9 KBMPEG-4 movie8 KB72 KBMPEG-4 movie14 K:13 KBMPEG-4 movie9 KB18 KB|MPEG-4 movie12 KBMPEG-4 movie10 KB16 KBMPEG-4 movie6KEMPEG-4 movie6 KB MPEG-4 movie12 KB23 K:MPEG-4 movie8 KBMPEG-4 movie6 KBMPEG-4 movie11 KEMPEG-4 movie11 KBMPEG-4 movie20 KB34 K:MPEG-4 movie10 KBMPEG-4 movie7 KB5KBMPEG-4 movie11 KPMPEG-4 movie26 KB MPEG-4 movie111 KBMPEG-4 movie102 K:MPEG-4 movie88 KBMPEG-4 movie59 KB98 KBMPEG-4 movie07 KPMDEG-A movie66 KBMPEG-4 movie44 KBMPEG-4 movie03 K:IMPEG-A movid78 KB MPEG-4 movie50 KB58 KB27 KPMPEG-4 movieMPEG-4 movie7 KB12 KBMPEG-4 movie22 K:17 KBMPEG-4 movie19 KB32 K8MPEG-4 movieFavouritesE jiminny© Recents* ApplicationsiCloudiCloud Drive228 Sync folderQ DXP4800PLUS-B5FA@ Network|• CRMI• Orange• Red• Yellow• Green• Purple•) All lags..lohlDownloadsNameLoom.pkgAlfred copv.alfredoreterencesB KeychronAssist-1.0.2 (1).dmgA Keychron Assist-1.0.2.dmgmazanoke-images-ywJo.ziPhotos-3-001.zipD Transcript.pdf→mage U.loge1 Orioninstaller.dmaimage.jpg- image (2).1pc• ПО-22221726037035-004-001_ORGES.pdf• %D0%9F%D0%9E-22221726037035-004-001_archive.zipПO-22221726037035-004-001_archive (1).zip• repon 4).XmAltred copy2.altredoreterences05012026_0000000026574472_ SWIFT_OB70501260015890.pdf27022026_0000000026574472_SWIFT_OB72702260049200.pdf= 03042026 [CREDIT_CARD] SWIFT [CREDIT_CARD].001B reporti) xm=pdt.odipdf-1.pdfD pdf-2.pdf-pdf-5.pd1= ndf-1 ndipdf-3.pdfB Rovaix Famly Treo gedbitwarden export 20251031122528.isonKoválik Family Tree.zip*macOS Storage_Cleanup.mdal favicon icofirst_aid_notes_complete.docxrenortl2).esvconfig.ymlIteration run Search HS.postman_collection.json--report(1).csvm licence hettertouchtoalMariusHosting Config.json1ooks-891a6503-bbb7-4b2b-9c3.csv•Alfredmazanoke-images-YWJ6ана Ковалик.jpg•искане даниел Ковалик..pg• Фактура Март Даниел Ковалик.jрс• Фактура Април Даниел Ковалик.jpg• Dhotac 2.001Q SearchKind00,4 MDinstdlle..dckage55.9 MBAlfred...ferences10,1 MBDisk Image10,1 MBDisk ImageIL MBLiP archive6,6 MBZIP archive2,5 MB PDF Document2,5 MBJreo lmage2.2 MBDisk Image2,2 MB PDF Document2 MBJPEG image1,9 MBJPEG imaqe192 KBPDF Document140 KbZIP archive148 KВZIP archivel148 KBZIP archive122 KBXML document111 KBAlfred...ferences94 KBPDF Document92 KBPDr DocumentK:91 KB91 KB30 KB29 KBPDF DocumentXML documentPDF Document28 KBPDF Document28 KR28 KB27 KB14 KB11 KB6 KB6KBPDE DocumentDocumentJSONICSV DocumentZIP archiveMarkdo…..ument5KRWindo...n image4 KB3 KBword ..cumentCSV Document2 KBYAML document1 KBcSV Document928 byteshttlicence183 bytesZero butesJSONAlfred.. ferencesZero bytesFolde1,9 MB1,8 MB17 MB1,7 MBColdorJPEG imageJPEG ImageIPEG imadeJPEG imageColdo1 of 58 selected, 9.63 GB availabld• Inu 14 May 1/.L2:00Date AddedIs Mdl ZUzo dl 19:4530 Jan 2026 at 12:3617 Mar 2026 at 20:2717 Mar 2026 at 20:26Z3 Aor 2020 al 13:0229 Jan 2026 at 15:2019 Dec 2025 at 10:1619 Dec 2025 at 12:238 Aor 2026 at 20:3519 Dec 2025 at 10:2919 Dec 2025 at 12:1819 Dec 2025 at 12:4026 Mar 2026 at 11:2410 May 2026 at 13:5326 Mar 2026 at 11:2426 Mar 2026 at 11:2426 Mar 2026 at 11:2310 May 2026 at 14:3730 Jan 2026 at 12:3713 Feb 2026 at 11:54Z3 Apr 2026 at 13.0823 Aor 2026 at 13:0810 May 2026 at 13:5410 May 2026 at 14:3710 May 2026 at 13:4910 May 2026 at 13:5010 May 2026 at 13:5110 Mav 2026 at 12:51110 May 2026 at 13:5019 Dec 2025 at 11:3431 Oct 2025 at 12:2525 Nov 2025 at 17:596 Mar 2026 at 11.2224 Anr 2026 at 16:5220 Oct 2025 at 11:0218 Mar 2026 at 15:29• Mav 2026 at 11:09129 Oct 2025 at 19:329 May 2026 at 10:0418 Mar 2026 at 11:5510 May 2026 at 13:5712 Jun 2025 at 19:0430 Jan 2026 at 12:3630 Jan 2026 at 12:3616 Oct 2025 aт 16:0123 Apr 2026 at 13:0223 Apr 2026 at 13:0223 Apr 2026 at 13:0222 Anr 2026 at 12:0223 Apr 2026 at 13:0229 Jan 2026 at 15:20...
|
45152
|
NULL
|
NULL
|
NULL
|
|
45157
|
1621
|
13
|
2026-05-14T14:23:05.379144+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768585379_m1.jpg...
|
PhpStorm
|
faVsco.js – WriteCrmTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorRun PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelpabl100% C•FV faVsco.jsv#12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTest8• Thu 14 May 17:23:05* :QProject v© ReportController.php© AutomatedReportGenerated.phpTrackAutomatedReportGeneratedEvent.phpPlaybackController.php:=custom.log> D CrmObjectsUserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.phpSendReportJob.phpO DecorateActivity› D Dummy• DeleteCrmEntityTrait.phpDeleteAccountJob.php© ImportActivityTypes.phpT WriteCrmTrait.php xAccountController.php› HelpersT IntegrationAppTrait.php.env.staging.envDetachActivityObject.phpRematchActivityOnCrmObjectDetach.phpv Hubspot© Client.php18D AccountSyncStrate› D ActionsD ContactSyncStrate!> DDTO› O Fields› D Journal© MatchActivityCrmData.phptrait WriteCrmTrait* Othrows HubspotException* @throws BadRequest*/1 usageHubspotPaginationService.phpHandleHubspotRateLimit.php19525354A8M6AYMetadata55private function puildConferencePayload(Activity $activity): Activity56v OpportunitySyncSti> [ Concerns57$decorator = $this->getDecorateActivity®:© HubspotLastMor58© HubspotLastMor59© HubspotLastMor68© HubspotLastMor6162© HubspotLastMor© HubspotSingleS:63$engagement = ['active' = true,'ownerid' => Sthis->profile->getCrmProviderId(),'timestamp' => $decorator->formatStartTime($activity),'type' => 'MEETING',© HubspotSyncStr641:© HubspotWebhoc6566v D Pagination© HubspotPaginati67if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType))) {$engagement['activityType'] = $activity->getActivityType()->getName();© PaginationConfig68© PaginationState.6970> D ProspectSearchStra> [ Redis71v D ServiceTraits72$metadata = ['meetingOutcome' => $decorator->convertMeetingOutcome($activity),'internalMeetingNotes' => $decorator-›generateDescription(Sactivity),€ OpportunitySync73© SyncCrmEntities74+ SyncFieldsTrait./75// Only if they are creating a fresh engagement should we update the times, otherwise it will cause a (231T WriteCrmTrait.pl76if (! $activity->hasCrmProviderId)) {D Utils77if (Sactivity->hasActualStartTime() && $activity->hasActualEndTime()) {D Webhook78Smetadata[ 'startTime'] = $decorator->formatStartTime(Sactivity);D211212213214215216217218— 219220221222223224225=226_227228229230232- 233 /234@ RatchSvneCnllertn79Smetadata['endTime'] = Sdecoraton->formatEndTime(Sactivity);Workspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)W Windsurf Teams=laravel.logSF [jiminny@localhost]A HS_local [jiminny@localho:W4 console [QAI PROD] X4 console [PROD]A console (EU]Go jiminny0841A3 X4 A Vm migrations oim teams wherem crm_layouts !M crm_layout_eilIM crm_fields Wim features;m team_feature:m opportunitie:m teams;1.id, CASEWHENidFROMsocial,1 on u.id= sa.:1..n<->1: on t.L_id = 1052 andIMaccounts wheiactivities55:22UTF-8Co 4 spaces...
|
NULL
|
155710469272411052
|
NULL
|
click
|
ocr
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorRun PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelpabl100% C•FV faVsco.jsv#12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTest8• Thu 14 May 17:23:05* :QProject v© ReportController.php© AutomatedReportGenerated.phpTrackAutomatedReportGeneratedEvent.phpPlaybackController.php:=custom.log> D CrmObjectsUserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.phpSendReportJob.phpO DecorateActivity› D Dummy• DeleteCrmEntityTrait.phpDeleteAccountJob.php© ImportActivityTypes.phpT WriteCrmTrait.php xAccountController.php› HelpersT IntegrationAppTrait.php.env.staging.envDetachActivityObject.phpRematchActivityOnCrmObjectDetach.phpv Hubspot© Client.php18D AccountSyncStrate› D ActionsD ContactSyncStrate!> DDTO› O Fields› D Journal© MatchActivityCrmData.phptrait WriteCrmTrait* Othrows HubspotException* @throws BadRequest*/1 usageHubspotPaginationService.phpHandleHubspotRateLimit.php19525354A8M6AYMetadata55private function puildConferencePayload(Activity $activity): Activity56v OpportunitySyncSti> [ Concerns57$decorator = $this->getDecorateActivity®:© HubspotLastMor58© HubspotLastMor59© HubspotLastMor68© HubspotLastMor6162© HubspotLastMor© HubspotSingleS:63$engagement = ['active' = true,'ownerid' => Sthis->profile->getCrmProviderId(),'timestamp' => $decorator->formatStartTime($activity),'type' => 'MEETING',© HubspotSyncStr641:© HubspotWebhoc6566v D Pagination© HubspotPaginati67if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType))) {$engagement['activityType'] = $activity->getActivityType()->getName();© PaginationConfig68© PaginationState.6970> D ProspectSearchStra> [ Redis71v D ServiceTraits72$metadata = ['meetingOutcome' => $decorator->convertMeetingOutcome($activity),'internalMeetingNotes' => $decorator-›generateDescription(Sactivity),€ OpportunitySync73© SyncCrmEntities74+ SyncFieldsTrait./75// Only if they are creating a fresh engagement should we update the times, otherwise it will cause a (231T WriteCrmTrait.pl76if (! $activity->hasCrmProviderId)) {D Utils77if (Sactivity->hasActualStartTime() && $activity->hasActualEndTime()) {D Webhook78Smetadata[ 'startTime'] = $decorator->formatStartTime(Sactivity);D211212213214215216217218— 219220221222223224225=226_227228229230232- 233 /234@ RatchSvneCnllertn79Smetadata['endTime'] = Sdecoraton->formatEndTime(Sactivity);Workspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)W Windsurf Teams=laravel.logSF [jiminny@localhost]A HS_local [jiminny@localho:W4 console [QAI PROD] X4 console [PROD]A console (EU]Go jiminny0841A3 X4 A Vm migrations oim teams wherem crm_layouts !M crm_layout_eilIM crm_fields Wim features;m team_feature:m opportunitie:m teams;1.id, CASEWHENidFROMsocial,1 on u.id= sa.:1..n<->1: on t.L_id = 1052 andIMaccounts wheiactivities55:22UTF-8Co 4 spaces...
|
45155
|
NULL
|
NULL
|
NULL
|
|
45159
|
1622
|
4
|
2026-05-14T14:23:05.452772+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768585452_m2.jpg...
|
PhpStorm
|
faVsco.js – WriteCrmTrait.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.2962101,"top":1.0,"width":0.03856383,"height":-0.019952059},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#12066 on JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.33477393,"top":1.0,"width":0.122340426,"height":-0.019952059},"on_screen":true,"help_text":"Pull request #12066 exists for current branch JY-20725-handle-HS-search-rate-limit, 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.5731383,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HandleHubspotRateLimitTest","depth":6,"bounds":{"left":0.5884308,"top":1.0,"width":0.076130316,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.66456115,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-7759925082980464890
|
-7195274138718106135
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
ActivityFilesLaterJiminny... ~@ jiminny-x-integrati& platform-inner-team© Channels# ai-chapter# alertsi backend# bugscontusion-cllnia# curiosity_lab# engineering# general# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the people of iimi..• Direct messages8. A...• A3 62P. Galya Dimitrova Mvasil VasilevA. Stefka Stoyanova%: Todor Stamatovf. Mario GeorgievP. Nikolay Ivanov2o James Graham "2. Stoyan Tanev. Steliyan Georgiev. Petko Kashinski*. Lukas Kovali...a: Apps® ToastS lira Gloud6 Huddle with Aneliya Angelova& R. Aneliya Angelova •• Messagest Add canvasur FilesAneliya Angelova 2:30 PMЛукаш, само при Сейлсфорс и Хубспот се синкваха активити типовете, когато се направи плейбук,нали?пои останалите скМі тоябва оъчно ла се въвелат.Lukas Kovalik 2:47 PMтряова да се за всички, някьде не се ли попьлвапри зохо май беше hardcoded но май и там си връщаха две категорииAneliya Angelova @ 4:00 PMЛукаш имаш ли време да се чуем за тестването на https://jiminny.atlassian.net/browse/JY-20725X Preview insackt[HubSpot] Optimise CRM rematctC OpenReady for QA- MediumAA Aneliya AngelovaAs of today at 4:00 PM RetreshOpen in Jira+ SummariseLukas Kovalik 4:02 PMzean-iurwiniareelYou joined the huddle LIVE 4:06 PMAneliva Angelova is here toolAneliya Angelova 4:44 PM11512582Lukas Kovalik O 5:16 PMcurl --location "httns:ani.hubani.com/crm/v3/obiects/contacts/search--header 'Content-Type: application/json'\--header "Authorization: BearerC|Kvma?iMx7OINOM8kOFwrAowAcAkUAhIR24?A05D?xiVrIYMILuP6SA01Kwcmhr=n.v2 hytorDniMvDalYck-CMalovolNOM9kOFwrAUAcrkcaws-ThwRARIRAOF-ATFSAOFRAO-ROIEBAQUBEggBAQEBAYICFIґ7Au8O2Nwal9YW2CbCCxYvwngmSgNldTFSAFoAYABoAHAAeAA'\--data "{ "limit", 13'Message Aneliva Angelova+ AaIAl Notes: OffLeave~ (Q SearchDate ModifiedYesterday at 13.34Yesterday at 13:34Yesterday at 13.33Yesterdav at 13:32resterday al i5.sYesterdav at 13:31Yesterday at 13:30Yesterday at 13.29Yesterday at 13:29resterday at 15.2oYesterday at 13:27Yesterday at 13:26Yesterday at 13:26Yesterday at 13:25Yesterday at 13.24Yesterdav at 13:24Yesterday at 13:23Yesterday at 13:21Yesterday at 13:21Yesterday at 13.20Yesterdav at 13:19Yesterday at 13:19Yesterday at 13:18Yesterday at 13:18Yesterday at 13:17Yesterday at 13.16Yecterdav at 12:16Yesterday at 13:15resterday at 13-10Yesterdav at 13:14Yesterday at 13:14Yesterday at 13:13Yecterdav at 12:12Yesterday at 13.11Yesterdav at 13:11Yesterday at 13:10Yesterday at 13:09Yesterday at 13:09Yesterday at 13:08Yesterday at 13.08Yecterdav at 12:07Yesterday at 13:07Yesterdav at 13:0617K:MPEG-4 movie29 KBMPEG-4 movie6 KB12 Kb9 KBIMPEG-4 movieMPEG-4 movie7 KB8 KBMPEG-4 movie37 K:10 KBMPEG-4 movie7 KBMPEG-4 movie9 KBMPEG-4 movie8 KB72 KBMPEG-4 movie14 K:13 KBMPEG-4 movie9 KB18 KB|12 KBIMPEG-4 movieMPEG-4 movie10 KB16 KBMPEG-4 movie6KEMPEG-4 movie6 KB MPEG-4 movie12 KB23 K:MPEG-4 movie8 KBMPEG-4 movie6 KBMPEG-4 movie11 KEMPEG-4 movie11 KBMPEG-4 movie20 KB34 K:MPEG-4 movie10 KBMPEG-4 movie7 KB5KBMPEG-4 movie11 KPMPEG-4 movie26 KB MPEG-4 movie111 KBMPEG-4 movie102 K:MPEG-4 movie88 KBMPEG-4 movie59 KB98 KBMPEG-4 movie07 KPMDEG-A movie66 KBMPEG-4 movie44 KBMPEG-4 movie03 K:IMPEG-A movid78 KB MPEG-4 movie50 KB58 KB27 KPMPEG-4 movieMPEG-4 movie7 KB12 KBMPEG-4 movie22 K:17 KBMPEG-4 movie19 KB32 K8MPEG-4 movieFavouritesE jiminny© Recents* ApplicationsiCloudiCloud Drive228 Sync folderQ DXP4800PLUS-B5FA@ Network|• CRMI• Orange• Red• Yellow• Green• Purple•) All lags..lohlDownloadsNameLoom.pkgAlfred copv.alfredoreterencesB KeychronAssist-1.0.2 (1).dmgA Keychron Assist-1.0.2.dmgmazanoke-images-ywJo.ziPhotos-3-001.zipD Transcript.pdf→mage U.loge1 Orioninstaller.dmaimage.jpg- image (2).1pc• ПО-22221726037035-004-001_ORGES.pdf• %D0%9F%D0%9E-22221726037035-004-001_archive.zipПO-22221726037035-004-001_archive (1).zip• repon 4).XmAltred copy2.altredoreterences05012026_0000000026574472_ SWIFT_OB70501260015890.pdf27022026_0000000026574472_SWIFT_OB72702260049200.pdf= 03042026 [CREDIT_CARD] SWIFT [CREDIT_CARD].001B reporti) xm=pdt.odipdf-1.pdfD pdf-2.pdf-pdf-5.pd1= ndf-1 ndipdf-3.pdfB Rovaix Famly Treo gedbitwarden export 20251031122528.isonKoválik Family Tree.zip*macOS Storage_Cleanup.mdal favicon icofirst_aid_notes_complete.docxrenortl2).esvconfig.ymlIteration run Search HS.postman_collection.json--report(1).csvm licence hettertouchtoalMariusHosting Config.json1ooks-891a6503-bbb7-4b2b-9c3.csv•Alfredmazanoke-images-YWJ6ана Ковалик.jpg•искане даниел Ковалик..pg• Фактура Март Даниел Ковалик.jрс• Фактура Април Даниел Ковалик.jpg• Dhotac 2.001Q SearchKind00,4 MDinstdlle..dckage55.9 MBAlfred...ferences10,1 MBDisk Image10,1 MBDisk ImageIL MBLiP archive6,6 MBZIP archive2,5 MB PDF Document2,5 MBJreo lmage2.2 MBDisk Image2,2 MB PDF Document2 MBJPEG image1,9 MBJPEG imaqe192 KBPDF Document140 KbZIP archive148 KВZIP archivel148 KBZIP archive122 KBXML document111 KBAlfred...ferences94 KBPDF Document92 KBPDr DocumentK:91 KB91 KB30 KB29 KBPDF DocumentXML documentPDF Document28 KBPDF Document28 KR28 KB27 KB14 KB11 KB6 KB6KBPDE DocumentDocumentJSONICSV DocumentZIP archiveMarkdo…..ument5KRWindo...n image4 KB3 KBword ..cumentCSV Document2 KBYAML document1 KBcSV Document928 byteshttlicence183 bytesZero butesJSONAlfred.. ferencesZero bytesFolde1,9 MB1,8 MB17 MB1,7 MBColdorJPEG imageJPEG ImageIPEG imadeJPEG imageColdo1 of 58 selected, 9.63 GB availabld• Inu 14 May 1/-23.02Date AddedIs Mdl ZUzo dl 19:4530 Jan 2026 at 12:3617 Mar 2026 at 20:2717 Mar 2026 at 20:26Z3 Aor 2020 al 13:0229 Jan 2026 at 15:2019 Dec 2025 at 10:1619 Dec 2025 at 12:238 Aor 2026 at 20:3519 Dec 2025 at 10:2919 Dec 2025 at 12:1819 Dec 2025 at 12:4026 Mar 2026 at 11:2410 May 2026 at 13:5326 Mar 2026 at 11:2426 Mar 2026 at 11:2426 Mar 2026 at 11:2310 May 2026 at 14:3730 Jan 2026 at 12:3713 Feb 2026 at 11:54Z3 Apr 2026 at 13.0823 Aor 2026 at 13:0810 May 2026 at 13:5410 May 2026 at 14:3710 May 2026 at 13:4910 May 2026 at 13:5010 May 2026 at 13:5110 Mav 2026 at 12:51110 May 2026 at 13:5019 Dec 2025 at 11:3431 Oct 2025 at 12:2525 Nov 2025 at 17:596 Mar 2026 at 11.2224 Anr 2026 at 16:5220 Oct 2025 at 11:0218 Mar 2026 at 15:29• Mav 2026 at 11:09129 Oct 2025 at 19:329 May 2026 at 10:0418 Mar 2026 at 11:5510 May 2026 at 13:5712 Jun 2025 at 19:0430 Jan 2026 at 12:3630 Jan 2026 at 12:3616 Oct 2025 aт 16:0123 Apr 2026 at 13:0223 Apr 2026 at 13:0223 Apr 2026 at 13:0222 Anr 2026 at 12:0223 Apr 2026 at 13:0229 Jan 2026 at 15:20...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45158
|
1621
|
14
|
2026-05-14T14:23:07.298201+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768587298_m1.jpg...
|
PhpStorm
|
faVsco.js – WriteCrmTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
8
6
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldDataRepository;
use Jiminny\Services\Crm\Hubspot\DecorateActivity;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationCategoryV4;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationTypeIdV4;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Http\Response;
use SevenShores\Hubspot\Exceptions\HubspotException;
use Sentry;
trait WriteCrmTrait
{
private const string ACTION_CREATE = 'create';
private const string ACTION_UPDATE = 'update';
private const string ACTION_DELETE = 'delete';
/**
* @inheritdoc
*/
public function saveActivity(Activity $activity): Activity
{
switch ($activity->getCrmType()) {
case Activity::TYPE_CONFERENCE:
$activity = $this->buildConferencePayload($activity);
break;
case Activity::TYPE_SOFTPHONE:
case Activity::TYPE_SOFTPHONE_INBOUND:
$activity = $this->buildSoftphonePayload($activity);
break;
case Activity::TYPE_SMS_INBOUND:
case Activity::TYPE_SMS_OUTBOUND:
$activity = $this->buildTextMessagePayload($activity);
break;
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildConferencePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatStartTime($activity),
'type' => 'MEETING',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'meetingOutcome' => $decorator->convertMeetingOutcome($activity),
'internalMeetingNotes' => $decorator->generateDescription($activity),
];
// Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.
if (! $activity->hasCrmProviderId()) {
if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {
$metadata['startTime'] = $decorator->formatStartTime($activity);
$metadata['endTime'] = $decorator->formatEndTime($activity);
}
$metadata['title'] = $decorator->generateTitle($activity);
}
// For now, this is just a "meetingOutcome" field for some customers.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function fetchCustomFieldData(Activity $activity, string $objectType): array
{
$payload = [];
$fieldDataRepository = app(FieldDataRepository::class);
$fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);
foreach ($fieldData as $data) {
// Add the field and value to the payload.
$payload += [
$data->getField()->getCrmProviderId() => $data->getValue(),
];
}
return $payload;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity
{
if (! $activity->hasCrmProviderId()) {
$this->createActivity($activity, $engagement, $metadata);
return $activity;
}
try {
$oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());
} catch (HubspotException $e) {
$this->logger->warning('[HubSpot] Engagement not found', [
'activity_id' => $activity->getUuid(),
'crm_provider_id' => $activity->getCrmProviderId(),
'exception' => $e->getMessage(),
]);
// potentially here we can log new activity if it is not found
return $activity;
}
$type = $oldEngagement['engagement']['type'] ?? null;
$bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';
$decorator = $this->getDecorateActivity();
if ($type !== 'TASK') {
$oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldDescription
);
$oldAssociations = $oldEngagement['associations'] ?? [];
$this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);
} else {
$oldTaskBody = $oldEngagement['metadata']['body'] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldTaskBody
);
$this->updateTask($activity, $engagement, $metadata);
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateNonTaskActivity(
Activity $activity,
array $engagement,
array $metadata,
array $oldAssociations
): void {
if ($activity->isTypeConference()) {
$this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);
} else {
$this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);
}
$this->updateActivityAssociations($activity, $oldAssociations);
$this->logCrmEngagementManipulation(
self::ACTION_UPDATE,
['crmId' => $activity->getCrmProviderId()],
$metadata,
);
}
private function updateActivityAssociations(Activity $activity, array $oldAssociations): void
{
$currentAssociations = $this->getActivityAssociations($activity);
$associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);
// No changes - exit
if (empty($associationChanges)) {
return;
}
$this->logger->info('[HubSpot] Updating associations', [
'team' => $this->team->getUuid(),
'activity' => $activity->getUuid(),
'associationChanges' => $associationChanges,
]);
$this->updateAssociations($activity, $associationChanges);
}
private function getAssociationDifferences(
Activity $activity,
array $oldAssociations,
array $currentAssociations
): array {
$objects = ['contactIds', 'companyIds', 'dealIds'];
$changes = [];
foreach ($objects as $object) {
$oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);
$currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);
$added = array_diff($currentIds, $oldIds);
$removed = array_diff($oldIds, $currentIds);
$type = match ($object) {
'contactIds' => 'contact',
'companyIds' => 'company',
'dealIds' => 'deal',
};
$shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);
if (! empty($removed) && ! $shouldIncludeRemoved) {
$this->logger->info('[HubSpot] Skipping association removal', [
'team' => $this->team->getUuid(),
'type' => $type,
'oldIds' => $oldIds,
'currentIds' => $currentIds,
'skippedRemovals' => array_values($removed),
]);
}
if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {
$changes[$type] = [
'added' => array_values($added),
'removed' => array_values($removed),
];
}
}
return $changes;
}
/**
* Determines whether removed associations should be synced to HubSpot.
*
* For contacts and companies, we always sync removals (original behavior).
* For deals, we only sync removals when:
* - There are new deals being added (replacement scenario), OR
* - The activity has an account associated (indicating intentional association management)
*
* This prevents accidentally removing deal associations that exist in HubSpot
* but not in Jiminny's database (e.g., deals manually added by users in HubSpot).
*/
private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool
{
// Contacts and companies: always sync removals
if ($type !== 'deal') {
return true;
}
// Deals: sync removals if we're adding new deals (replacement)
if (! empty($added)) {
return true;
}
// Deals: sync removals only if activity has an account (intentional management)
return $activity->hasAccount();
}
private function normalizeIds(array $ids): array
{
$stringIds = array_map('strval', $ids);
$uniqueIds = array_unique($stringIds);
sort($uniqueIds);
return $uniqueIds;
}
private function updateAssociations(Activity $activity, array $associationChanges): void
{
$objectType = $activity->isTypeConference() ? 'meeting' : 'call';
foreach ($associationChanges as $type => $changes) {
if (! empty($changes['added'])) {
$associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);
$payload = $this->payloadBuilder->buildAddAssociationPayload(
$activity->getCrmProviderId(),
$changes['added'],
$associationType
);
$this->getClient()->addAssociations($objectType, $type, $payload);
}
if (! empty($changes['removed'])) {
$payload = $this->payloadBuilder->buildRemoveAssociationPayload(
$activity->getCrmProviderId(),
$changes['removed']
);
$this->getClient()->removeAssociations($objectType, $type, $payload);
}
}
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response
{
// If already set by us/client, do not override it
if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {
$this->logger->info('[HubSpot] Meeting activity type already set', [
'meetingId' => $meetingId,
'activityType' => $engagement['activityType'],
]);
unset($engagement['activityType']);
}
$payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);
return $this->getClient()->updateMeeting($meetingId, $payload);
}
private function hasEngagementActivityType(string $engagementId): bool
{
try {
$engagementData = $this->getClient()->getEngagementData($engagementId);
if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {
return true;
}
return false;
} catch (\Exception $exception) {
Sentry::captureException($exception);
$this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [
'engagementId' => $engagementId,
'exception' => $exception->getMessage(),
]);
}
return true;
}
private function updateTask(Activity $activity, array $engagement, array $metadata): void
{
$this->getClient()->deleteEngagement($activity->getCrmProviderId());
$this->logCrmEngagementManipulation(
self::ACTION_DELETE,
['crmId' => $activity->getCrmProviderId()],
);
$this->createActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createActivity(Activity $activity, array $engagement, array $metadata): void
{
$associations = $this->getActivityAssociations($activity);
$associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);
if ($activity->isTypeConference()) {
try {
$hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);
$activityId = $hsEngagement->data->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
} else {
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
$activityId = $hsEngagement->data->engagement->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
}
}
private function convertActivityMeetingAssociationsV4(Activity $activity): array
{
$associations = [];
$contactIds = $this->getParticipantsIds($activity);
foreach ($contactIds as $contactId) {
$associations[] = $this->buildAssociation(
(int) $contactId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]
);
}
if ($activity->hasAccount()) {
$crmProviderId = $activity->getAccount()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]
);
}
}
if ($activity->hasOpportunity()) {
$crmProviderId = $activity->getOpportunity()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]
);
}
}
return $associations;
}
private function buildAssociation(int $id, array $categoryToTypeIds): array
{
$types = [];
foreach ($categoryToTypeIds as $category => $typeId) {
$types[] = $this->buildAssociationType($category, $typeId);
}
return [
'to' => ['id' => (string) $id],
'types' => $types,
];
}
private function buildAssociationType(string $category, int $typeId): array
{
return [
'associationCategory' => $category,
'associationTypeId' => $typeId,
];
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createMeetingV3(array $engagement, array $associations, array $metadata): Response
{
$payload = $this->payloadBuilder->getV3MeetingPayload(
engagement: $engagement,
metadata: $metadata,
associations: $associations
);
return $this->getClient()->createMeeting($payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
private function buildSoftphonePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'CALL',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'toNumber' => $decorator->formatCallToParticipant($activity),
'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),
'status' => $decorator->convertCallStatus($activity),
'durationMilliseconds' => (int) $activity->getDuration() * 1000,
'body' => $decorator->generateDescription($activity),
];
// For now, this is just a "disposition" field to store Call Outcome.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildTextMessagePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'NOTE',
];
$metadata = [
'body' => $decorator->generateDescription($activity),
];
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function getActivityAssociations(Activity $activity): array
{
return [
'contactIds' => $this->getParticipantsIds($activity),
'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],
'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],
'ownerIds' => [],
];
}
/**
* Get DecorateActivity instance. Can be overridden in tests for mocking.
*/
protected function getDecorateActivity(): DecorateActivity
{
return new DecorateActivity();
}
public function saveFollowupActivity(Activity $activity, array $fields): ?string
{
if (empty($fields['subject'])) {
return null;
}
$associations = $this->getActivityAssociations($activity);
if (empty($fields['timestamp']) === false) {
$formatTimestamp = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['timestamp']);
$timestamp = $formatTimestamp->getTimestamp();
} else {
$timestamp = strtotime('+1 day');
}
$reminder = null;
if (empty($fields['reminder']) === false) {
$formatReminder = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['reminder']);
$reminder = $formatReminder->getTimestamp() * 1000;
}
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $timestamp * 1000,
'type' => 'TASK',
];
$metadata = [
'subject' => $fields['subject'],
'body' => '',
'status' => 'NOT_STARTED',
'taskType' => 'CALL',
'reminders' => $reminder ? [$reminder] : [],
];
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
} catch (BadRequest $e) {
$this->logger->info('[HubSpot] Failed to create followup task', [
'activity' => $activity->getUuid(),
'engagement' => $engagement,
'metadata' => $metadata,
'error' => $e->getMessage(),
]);
return null;
}
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
// We don't actually create a corresponding activity object on our side yet.
return (string) $hsEngagement->data->engagement->id;
}
private function logCrmEngagementManipulation(
string $action,
array $engagementData,
array $data = [],
array $associations = [],
): void {
$payload = ['team' => $this->team->getUuid()];
if ($action === self::ACTION_CREATE) {
$payload['engagementData'] = $engagementData;
$payload['data'] = $data;
$payload['associations'] = $associations;
}
if ($action === self::ACTION_UPDATE) {
$payload['engagementId'] = $engagementData['crmId'];
$payload['data'] = $data;
}
if ($action === self::ACTION_DELETE) {
$payload['engagementId'] = $engagementData['crmId'];
}
$this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
8
1
3
4
Previous Highlighted Error
Next Highlighted Error
SELECT
# DISTINCT
opp.id as opp_id, opp.uuid, opp.name,
# COUNT(dr.id) AS deal_risk_count,
# dr.id,
# cfd.value,
usr.name AS owner_name,
opp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,
usr.uuid as owner_uuid,
usr.photo_path as owner_photo,
usr.id AS owner_id,
jt.name as owner_job,
opp.stage_id, opp.stage_updated_at,
acc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,
rt.business_process_id AS pipeline_id
FROM opportunities opp
LEFT JOIN record_types rt ON opp.record_type_id = rt.id
LEFT JOIN users usr ON opp.user_id = usr.id
LEFT JOIN accounts acc ON opp.account_id = acc.id
LEFT JOIN job_titles jt ON usr.job_title_id = jt.id
# LEFT JOIN group_deal_risk_types gdrt ON gdrt.group_id = usr.group_id
# LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id AND dr.group_deal_risk_type_id = gdrt.id
LEFT JOIN crm_field_data cfd ON (cfd.object_id = opp.id and cfd.crm_field_id = 66814)
WHERE opp.user_id IS NOT NULL
AND opp.deleted_at IS NULL
AND opp.is_closed = 1
AND opp.is_won = 0
AND opp.close_date >= '2024-01-01 00:00:00'
AND opp.close_date <= '2024-12-31 23:59:59'
AND usr.team_id = 1
# and opp.id = 4823179
GROUP BY opp.id, cfd.value
ORDER BY
# cfd.value
CAST(cfd.value AS UNSIGNED)
# owner_name
# (SELECT COUNT(dr.id)
# FROM deal_risks dr
# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
# WHERE dr.opportunity_id = opp.id
# AND dr.is_active = 1
# AND gdrt.is_enabled = 1)
DESC
LIMIT 25
# OFFSET 0
;
select * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';
select * from crm_layout_entities where crm_layout_id = 1493;
SELECT * FROM opportunities WHERE id = 4584045;
SELECT * FROM crm_field_data WHERE object_id = 4584045;
SELECT * FROM opportunities where team_id = 1 and crm_provider_id = '0061K00000lfb9IQAQ'
SELECT
opp.id as opportunity_id,
opp.name,
COUNT(dr.id) as risk_count
FROM opportunities opp
LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id
WHERE opp.id IN (
6069574,8900938,9507638,9524799,9660490,9662824,9705675,9749758,4158460,4391812,6450439,4448422,5118945,5590675,4584045,7228149,9002408,9165534,9446720,9641778,9665149,9703344,9709280,5747948,4158491,6182565,6263970,5798120,5111315,4536978,6062352,6548383,6072095,6548225,4480986,5011422,6548381,8760540,8917554,9509986,9514392,9569009,9569011,9578490,9713604,5784397,7276612,7288405,8994421,9118219,9608148,9818911,4510535,6479693,5958049,6271674,6550448,4158331,4158483,6126571,6171615,6540943,4897466,5190896,5796182,5932762,8572433,8723698,8892697,9711001,9789264,4549188,6100831,6170064,6260799,6263653,6449936,6530871,6538978,7777651,8059269,8319918,8787049,8901150,9263153,9453207,9514738,9696700,9791062,5752018,6421452,7439134,7878923,9354763,9369285,9514396,9582506,9889949,9890216,4094311,4158495,4158496,6098128,5585661,3872564,6442149,5891604,6164746,6199593,6583474,6519684,9018490,9809006,4496897,5041324,5829430,6198319,6253504,6555763,7242914,7931055,8024125,8797814,8058559,8673347,8892695,8994420,9616219,9714970,9722004,9809439,9818918,6523177,8134147,9002915,9711422,9892713,9901719,9954210,9978435,5800810,6243518,6416114,6222251,6411974,6512456,5791953,6545606,9914780,5805540,6238986,6463838,6547680,9767049,9809437,9810885,9890855,6673493,9902036,4335521,6379871,6503799,6546077,8018765,9907556,9958433,9905855,9916179,9946741,9957877,5563344,6271838,6450815,7641128,7762567,7780592,8684810,8685786,8685787,8685788
)
GROUP BY opp.id;
SELECT COUNT(dr.id)
FROM deal_risks dr
JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
WHERE dr.opportunity_id = 5563344
# AND gdrt.group_id = usr.group_id
EXPLAIN SELECT
opp.id as opp_id, opp.uuid, opp.name,
cfd.value,
cfv.sequence,
cfv.value,
# MAX(cfd.value) AS max_cfd_value,
usr.name AS owner_name,
opp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,
usr.uuid as owner_uuid,
usr.photo_path as owner_photo,
usr.id AS owner_id,
jt.name as owner_job,
opp.stage_id, opp.stage_updated_at,
acc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,
rt.business_process_id AS pipeline_id
FROM opportunities opp
LEFT JOIN record_types rt ON opp.record_type_id = rt.id
LEFT JOIN users usr ON opp.user_id = usr.id
LEFT JOIN accounts acc ON opp.account_id = acc.id
LEFT JOIN job_titles jt ON usr.job_title_id = jt.id
LEFT JOIN crm_field_data cfd ON cfd.id = (SELECT sub_cfd.id
FROM crm_field_data sub_cfd
WHERE sub_cfd.object_id = opp.id
AND sub_cfd.crm_field_id = 66810
ORDER BY sub_cfd.updated_at DESC
LIMIT 1)
LEFT JOIN crm_field_values cfv ON cfv.crm_field_id = 66810 AND cfv.value = cfd.value
WHERE opp.user_id IS NOT NULL
AND opp.deleted_at IS NULL
AND opp.is_closed = 0
# AND opp.is_closed = 1
# AND opp.is_won = 0
AND opp.close_date >= '2024-01-01 00:00:00'
AND opp.close_date <= '2024-12-31 23:59:59'
AND usr.team_id = 1
# and opp.id = 4823179
# GROUP BY opp.id
ORDER BY
cfv.sequence DESC,
# opp.name
# owner_name
cfd.value
# CAST(cfd.value AS UNSIGNED)
# CAST(MAX(cfd.value) AS UNSIGNED)
# (SELECT COUNT(dr.id)
# FROM deal_risks dr
# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
# WHERE dr.opportunity_id = opp.id
# AND dr.is_active = 1 AND gdrt.is_enabled = 1)
DESC
# LIMIT 75, 25
;
SELECT * FROM crm_field_values WHERE crm_field_id = 66810;
SELECT f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value, fd.updated_at, fd.id, fd.created_at
FROM crm_fields f
INNER JOIN crm_field_data fd ON fd.crm_field_id = f.id
WHERE (f.crm_configuration_id = 1)
AND (f.object_type = 'opportunity')
# AND (fd.object_id IN (4158331,4158483,4158495,4158496,6069574,6263970,6171615,6540943,6545606,5829430,6198319,6263653,6548381,7242914,8797814,7228149,7439134,7878923,8134147,9002915,9118219,9165534,9354763,9369285,9446720))
AND (fd.object_id IN (4158460))
AND (f.crm_provider_id IN ('ForecastCategoryName'))
ORDER BY fd.object_id ASC, fd.updated_at DESC;
select * from crm_layouts where crm_configuration_id = 1;
select cf.* from crm_fields cf
join crm_layout_entities cle on cf.id = cle.crm_field_id
where crm_layout_id = 1493;
SELECT * FROM opportunities WHERE id = 4158460;
SELECT * FROM crm_field_data WHERE object_id = 4158460;
select * from users where team_id = 1;
SELECT * FROM users WHERE id = 7160;
select * from role_user where user_id = 3248;
select * from crm_field_values where crm_field_id = 66824;
SELECT * FROM users WHERE id = 23470;
select * from crm_configurations;
# [PASSWORD_DOTS]
select * from teams where id = 1038; # 23521, 966
select * from users where team_id = 1038;
select * from crm_configurations where id = 966;
SELECT * FROM activities WHERE uuid_to_bin('e8dc7c34-31f6-4430-bd81-2d9d8f00dd07') = uuid; # 54965762
SELECT * FROM activities WHERE uuid_to_bin('4b5727c0-69d1-428f-b8d9-b649055166e2') = uuid; # 54965764
SELECT * FROM participants WHERE activity_id = 54965764;
SELECT id, uuid, title, recording_state FROM activities WHERE uuid_to_bin('355f105d-5606-4379-b0c5-d91daf59f4ce') = uuid; # 54964075
SELECT * FROM participants WHERE activity_id = 54964075;
select f.id, slug, title, tf.team_id from team_features tf JOIN features f on tf.feature_id = f.id
where tf.team_id = 1038;
# [PASSWORD_DOTS] PD [PASSWORD_DOTS]
select * from teams where id = 1029;
SELECT * FROM crm_configurations WHERE id = 957;
SELECT * FROM activities WHERE uuid_to_bin('581a33cb-4343-44bd-ac0c-4a5cee71c5ec') = uuid; # 54963423
SELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433
SELECT * FROM activities WHERE uuid_to_bin('8b6bb2f7-e7a7-4aec-8806-fbfea7093ccd') = uuid; # 54963421
# [PASSWORD_DOTS] Close [PASSWORD_DOTS]
select * from teams where id = 1031;
SELECT * FROM crm_configurations WHERE id = 959;
SELECT * FROM activities WHERE uuid_to_bin('59c517e0-96c9-4dad-bfeb-b7bef72f8725') = uuid; # 54963517
SELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433
SELECT * FROM activities WHERE uuid_to_bin('d68311e3-ac34-49bd-bf04-88323a7e5352') = uuid; # 54966009
# [PASSWORD_DOTS] SF [PASSWORD_DOTS]
SELECT * FROM activities WHERE uuid_to_bin('2bd4cfa3-4eb5-4c77-84a7-9ed46a21c988') = uuid;
select * from roles;
select * from permissions;
select * from permission_role where permission_id = 136;
select * from migrations order by id desc;
select * from teams where id IN (1, 1037);
select * from crm_layouts where crm_configuration_id = 1;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 1493;
SELECT * FROM crm_fields WHERE id IN (1652,1661,66799,66814,66821,66836,66843,66846,66864,84752,182306,323580,380378);
select * from features;
select * from team_features where feature_id = 33;
select * from opportunities;
select * from teams;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1052 and sa.provider = 'hubspot';
SELECT * FROM accounts where id = 11512582;
select * from activities where account_id = 11512582;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.054166667,"top":0.027777778,"width":0.08055556,"height":0.035555556},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#12066 on JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.13472222,"top":0.027777778,"width":0.25555557,"height":0.035555556},"on_screen":true,"help_text":"Pull request #12066 exists for current branch JY-20725-handle-HS-search-rate-limit, 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.6326389,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HandleHubspotRateLimitTest","depth":6,"bounds":{"left":0.6645833,"top":0.027777778,"width":0.15902779,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.82361114,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.8472222,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.87083334,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.9291667,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9527778,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9763889,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"8","depth":4,"bounds":{"left":0.78055555,"top":0.24333334,"width":0.016666668,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.80138886,"top":0.24333334,"width":0.016666668,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.8215278,"top":0.24111111,"width":0.015277778,"height":0.025555555},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.8368056,"top":0.24111111,"width":0.014583333,"height":0.025555555},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldDataRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DecorateActivity;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationCategoryV4;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationTypeIdV4;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse Sentry;\n\ntrait WriteCrmTrait\n{\n private const string ACTION_CREATE = 'create';\n private const string ACTION_UPDATE = 'update';\n private const string ACTION_DELETE = 'delete';\n\n /**\n * @inheritdoc\n */\n public function saveActivity(Activity $activity): Activity\n {\n switch ($activity->getCrmType()) {\n case Activity::TYPE_CONFERENCE:\n $activity = $this->buildConferencePayload($activity);\n\n break;\n case Activity::TYPE_SOFTPHONE:\n case Activity::TYPE_SOFTPHONE_INBOUND:\n $activity = $this->buildSoftphonePayload($activity);\n\n break;\n\n case Activity::TYPE_SMS_INBOUND:\n case Activity::TYPE_SMS_OUTBOUND:\n $activity = $this->buildTextMessagePayload($activity);\n\n break;\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildConferencePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatStartTime($activity),\n 'type' => 'MEETING',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'meetingOutcome' => $decorator->convertMeetingOutcome($activity),\n 'internalMeetingNotes' => $decorator->generateDescription($activity),\n ];\n\n // Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.\n if (! $activity->hasCrmProviderId()) {\n if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {\n $metadata['startTime'] = $decorator->formatStartTime($activity);\n $metadata['endTime'] = $decorator->formatEndTime($activity);\n }\n $metadata['title'] = $decorator->generateTitle($activity);\n }\n\n // For now, this is just a \"meetingOutcome\" field for some customers.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function fetchCustomFieldData(Activity $activity, string $objectType): array\n {\n $payload = [];\n\n $fieldDataRepository = app(FieldDataRepository::class);\n $fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);\n\n foreach ($fieldData as $data) {\n // Add the field and value to the payload.\n $payload += [\n $data->getField()->getCrmProviderId() => $data->getValue(),\n ];\n }\n\n return $payload;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity\n {\n if (! $activity->hasCrmProviderId()) {\n $this->createActivity($activity, $engagement, $metadata);\n\n return $activity;\n }\n\n try {\n $oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());\n } catch (HubspotException $e) {\n $this->logger->warning('[HubSpot] Engagement not found', [\n 'activity_id' => $activity->getUuid(),\n 'crm_provider_id' => $activity->getCrmProviderId(),\n 'exception' => $e->getMessage(),\n ]);\n\n // potentially here we can log new activity if it is not found\n\n return $activity;\n }\n\n $type = $oldEngagement['engagement']['type'] ?? null;\n $bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';\n\n $decorator = $this->getDecorateActivity();\n\n if ($type !== 'TASK') {\n $oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldDescription\n );\n\n $oldAssociations = $oldEngagement['associations'] ?? [];\n $this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);\n } else {\n $oldTaskBody = $oldEngagement['metadata']['body'] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldTaskBody\n );\n $this->updateTask($activity, $engagement, $metadata);\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateNonTaskActivity(\n Activity $activity,\n array $engagement,\n array $metadata,\n array $oldAssociations\n ): void {\n if ($activity->isTypeConference()) {\n $this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);\n } else {\n $this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);\n }\n\n $this->updateActivityAssociations($activity, $oldAssociations);\n\n $this->logCrmEngagementManipulation(\n self::ACTION_UPDATE,\n ['crmId' => $activity->getCrmProviderId()],\n $metadata,\n );\n }\n\n private function updateActivityAssociations(Activity $activity, array $oldAssociations): void\n {\n $currentAssociations = $this->getActivityAssociations($activity);\n $associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);\n\n // No changes - exit\n if (empty($associationChanges)) {\n return;\n }\n\n $this->logger->info('[HubSpot] Updating associations', [\n 'team' => $this->team->getUuid(),\n 'activity' => $activity->getUuid(),\n 'associationChanges' => $associationChanges,\n ]);\n\n $this->updateAssociations($activity, $associationChanges);\n }\n\n private function getAssociationDifferences(\n Activity $activity,\n array $oldAssociations,\n array $currentAssociations\n ): array {\n $objects = ['contactIds', 'companyIds', 'dealIds'];\n $changes = [];\n\n foreach ($objects as $object) {\n $oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);\n $currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);\n\n $added = array_diff($currentIds, $oldIds);\n $removed = array_diff($oldIds, $currentIds);\n\n $type = match ($object) {\n 'contactIds' => 'contact',\n 'companyIds' => 'company',\n 'dealIds' => 'deal',\n };\n\n $shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);\n\n if (! empty($removed) && ! $shouldIncludeRemoved) {\n $this->logger->info('[HubSpot] Skipping association removal', [\n 'team' => $this->team->getUuid(),\n 'type' => $type,\n 'oldIds' => $oldIds,\n 'currentIds' => $currentIds,\n 'skippedRemovals' => array_values($removed),\n ]);\n }\n\n if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {\n $changes[$type] = [\n 'added' => array_values($added),\n 'removed' => array_values($removed),\n ];\n }\n }\n\n return $changes;\n }\n\n /**\n * Determines whether removed associations should be synced to HubSpot.\n *\n * For contacts and companies, we always sync removals (original behavior).\n * For deals, we only sync removals when:\n * - There are new deals being added (replacement scenario), OR\n * - The activity has an account associated (indicating intentional association management)\n *\n * This prevents accidentally removing deal associations that exist in HubSpot\n * but not in Jiminny's database (e.g., deals manually added by users in HubSpot).\n */\n private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool\n {\n // Contacts and companies: always sync removals\n if ($type !== 'deal') {\n return true;\n }\n\n // Deals: sync removals if we're adding new deals (replacement)\n if (! empty($added)) {\n return true;\n }\n\n // Deals: sync removals only if activity has an account (intentional management)\n return $activity->hasAccount();\n }\n\n private function normalizeIds(array $ids): array\n {\n $stringIds = array_map('strval', $ids);\n $uniqueIds = array_unique($stringIds);\n sort($uniqueIds);\n\n return $uniqueIds;\n }\n\n private function updateAssociations(Activity $activity, array $associationChanges): void\n {\n $objectType = $activity->isTypeConference() ? 'meeting' : 'call';\n\n foreach ($associationChanges as $type => $changes) {\n if (! empty($changes['added'])) {\n $associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);\n $payload = $this->payloadBuilder->buildAddAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['added'],\n $associationType\n );\n $this->getClient()->addAssociations($objectType, $type, $payload);\n }\n\n if (! empty($changes['removed'])) {\n $payload = $this->payloadBuilder->buildRemoveAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['removed']\n );\n $this->getClient()->removeAssociations($objectType, $type, $payload);\n }\n }\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response\n {\n // If already set by us/client, do not override it\n if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {\n $this->logger->info('[HubSpot] Meeting activity type already set', [\n 'meetingId' => $meetingId,\n 'activityType' => $engagement['activityType'],\n ]);\n\n unset($engagement['activityType']);\n }\n\n $payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);\n\n return $this->getClient()->updateMeeting($meetingId, $payload);\n }\n\n private function hasEngagementActivityType(string $engagementId): bool\n {\n try {\n $engagementData = $this->getClient()->getEngagementData($engagementId);\n if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {\n return true;\n }\n\n return false;\n } catch (\\Exception $exception) {\n Sentry::captureException($exception);\n\n $this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [\n 'engagementId' => $engagementId,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n return true;\n }\n\n private function updateTask(Activity $activity, array $engagement, array $metadata): void\n {\n $this->getClient()->deleteEngagement($activity->getCrmProviderId());\n\n $this->logCrmEngagementManipulation(\n self::ACTION_DELETE,\n ['crmId' => $activity->getCrmProviderId()],\n );\n\n $this->createActivity($activity, $engagement, $metadata);\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createActivity(Activity $activity, array $engagement, array $metadata): void\n {\n $associations = $this->getActivityAssociations($activity);\n $associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);\n\n if ($activity->isTypeConference()) {\n try {\n $hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);\n $activityId = $hsEngagement->data->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n } else {\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n $activityId = $hsEngagement->data->engagement->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n }\n }\n\n private function convertActivityMeetingAssociationsV4(Activity $activity): array\n {\n $associations = [];\n\n $contactIds = $this->getParticipantsIds($activity);\n\n foreach ($contactIds as $contactId) {\n $associations[] = $this->buildAssociation(\n (int) $contactId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]\n );\n }\n\n if ($activity->hasAccount()) {\n $crmProviderId = $activity->getAccount()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]\n );\n }\n }\n\n if ($activity->hasOpportunity()) {\n $crmProviderId = $activity->getOpportunity()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]\n );\n }\n }\n\n return $associations;\n }\n\n private function buildAssociation(int $id, array $categoryToTypeIds): array\n {\n $types = [];\n\n foreach ($categoryToTypeIds as $category => $typeId) {\n $types[] = $this->buildAssociationType($category, $typeId);\n }\n\n return [\n 'to' => ['id' => (string) $id],\n 'types' => $types,\n ];\n }\n\n private function buildAssociationType(string $category, int $typeId): array\n {\n return [\n 'associationCategory' => $category,\n 'associationTypeId' => $typeId,\n ];\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createMeetingV3(array $engagement, array $associations, array $metadata): Response\n {\n $payload = $this->payloadBuilder->getV3MeetingPayload(\n engagement: $engagement,\n metadata: $metadata,\n associations: $associations\n );\n\n return $this->getClient()->createMeeting($payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n private function buildSoftphonePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'CALL',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'toNumber' => $decorator->formatCallToParticipant($activity),\n 'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),\n 'status' => $decorator->convertCallStatus($activity),\n 'durationMilliseconds' => (int) $activity->getDuration() * 1000,\n 'body' => $decorator->generateDescription($activity),\n ];\n\n // For now, this is just a \"disposition\" field to store Call Outcome.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildTextMessagePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'NOTE',\n ];\n\n $metadata = [\n 'body' => $decorator->generateDescription($activity),\n ];\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function getActivityAssociations(Activity $activity): array\n {\n return [\n 'contactIds' => $this->getParticipantsIds($activity),\n 'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],\n 'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],\n 'ownerIds' => [],\n ];\n }\n\n /**\n * Get DecorateActivity instance. Can be overridden in tests for mocking.\n */\n protected function getDecorateActivity(): DecorateActivity\n {\n return new DecorateActivity();\n }\n\n public function saveFollowupActivity(Activity $activity, array $fields): ?string\n {\n if (empty($fields['subject'])) {\n return null;\n }\n\n $associations = $this->getActivityAssociations($activity);\n\n if (empty($fields['timestamp']) === false) {\n $formatTimestamp = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['timestamp']);\n $timestamp = $formatTimestamp->getTimestamp();\n } else {\n $timestamp = strtotime('+1 day');\n }\n\n $reminder = null;\n if (empty($fields['reminder']) === false) {\n $formatReminder = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['reminder']);\n $reminder = $formatReminder->getTimestamp() * 1000;\n }\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $timestamp * 1000,\n 'type' => 'TASK',\n ];\n\n $metadata = [\n 'subject' => $fields['subject'],\n 'body' => '',\n 'status' => 'NOT_STARTED',\n 'taskType' => 'CALL',\n 'reminders' => $reminder ? [$reminder] : [],\n ];\n\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n } catch (BadRequest $e) {\n $this->logger->info('[HubSpot] Failed to create followup task', [\n 'activity' => $activity->getUuid(),\n 'engagement' => $engagement,\n 'metadata' => $metadata,\n 'error' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n // We don't actually create a corresponding activity object on our side yet.\n return (string) $hsEngagement->data->engagement->id;\n }\n\n private function logCrmEngagementManipulation(\n string $action,\n array $engagementData,\n array $data = [],\n array $associations = [],\n ): void {\n $payload = ['team' => $this->team->getUuid()];\n\n if ($action === self::ACTION_CREATE) {\n $payload['engagementData'] = $engagementData;\n $payload['data'] = $data;\n $payload['associations'] = $associations;\n }\n\n if ($action === self::ACTION_UPDATE) {\n $payload['engagementId'] = $engagementData['crmId'];\n $payload['data'] = $data;\n }\n\n if ($action === self::ACTION_DELETE) {\n $payload['engagementId'] = $engagementData['crmId'];\n }\n\n $this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldDataRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DecorateActivity;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationCategoryV4;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationTypeIdV4;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse Sentry;\n\ntrait WriteCrmTrait\n{\n private const string ACTION_CREATE = 'create';\n private const string ACTION_UPDATE = 'update';\n private const string ACTION_DELETE = 'delete';\n\n /**\n * @inheritdoc\n */\n public function saveActivity(Activity $activity): Activity\n {\n switch ($activity->getCrmType()) {\n case Activity::TYPE_CONFERENCE:\n $activity = $this->buildConferencePayload($activity);\n\n break;\n case Activity::TYPE_SOFTPHONE:\n case Activity::TYPE_SOFTPHONE_INBOUND:\n $activity = $this->buildSoftphonePayload($activity);\n\n break;\n\n case Activity::TYPE_SMS_INBOUND:\n case Activity::TYPE_SMS_OUTBOUND:\n $activity = $this->buildTextMessagePayload($activity);\n\n break;\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildConferencePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatStartTime($activity),\n 'type' => 'MEETING',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'meetingOutcome' => $decorator->convertMeetingOutcome($activity),\n 'internalMeetingNotes' => $decorator->generateDescription($activity),\n ];\n\n // Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.\n if (! $activity->hasCrmProviderId()) {\n if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {\n $metadata['startTime'] = $decorator->formatStartTime($activity);\n $metadata['endTime'] = $decorator->formatEndTime($activity);\n }\n $metadata['title'] = $decorator->generateTitle($activity);\n }\n\n // For now, this is just a \"meetingOutcome\" field for some customers.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function fetchCustomFieldData(Activity $activity, string $objectType): array\n {\n $payload = [];\n\n $fieldDataRepository = app(FieldDataRepository::class);\n $fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);\n\n foreach ($fieldData as $data) {\n // Add the field and value to the payload.\n $payload += [\n $data->getField()->getCrmProviderId() => $data->getValue(),\n ];\n }\n\n return $payload;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity\n {\n if (! $activity->hasCrmProviderId()) {\n $this->createActivity($activity, $engagement, $metadata);\n\n return $activity;\n }\n\n try {\n $oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());\n } catch (HubspotException $e) {\n $this->logger->warning('[HubSpot] Engagement not found', [\n 'activity_id' => $activity->getUuid(),\n 'crm_provider_id' => $activity->getCrmProviderId(),\n 'exception' => $e->getMessage(),\n ]);\n\n // potentially here we can log new activity if it is not found\n\n return $activity;\n }\n\n $type = $oldEngagement['engagement']['type'] ?? null;\n $bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';\n\n $decorator = $this->getDecorateActivity();\n\n if ($type !== 'TASK') {\n $oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldDescription\n );\n\n $oldAssociations = $oldEngagement['associations'] ?? [];\n $this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);\n } else {\n $oldTaskBody = $oldEngagement['metadata']['body'] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldTaskBody\n );\n $this->updateTask($activity, $engagement, $metadata);\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateNonTaskActivity(\n Activity $activity,\n array $engagement,\n array $metadata,\n array $oldAssociations\n ): void {\n if ($activity->isTypeConference()) {\n $this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);\n } else {\n $this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);\n }\n\n $this->updateActivityAssociations($activity, $oldAssociations);\n\n $this->logCrmEngagementManipulation(\n self::ACTION_UPDATE,\n ['crmId' => $activity->getCrmProviderId()],\n $metadata,\n );\n }\n\n private function updateActivityAssociations(Activity $activity, array $oldAssociations): void\n {\n $currentAssociations = $this->getActivityAssociations($activity);\n $associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);\n\n // No changes - exit\n if (empty($associationChanges)) {\n return;\n }\n\n $this->logger->info('[HubSpot] Updating associations', [\n 'team' => $this->team->getUuid(),\n 'activity' => $activity->getUuid(),\n 'associationChanges' => $associationChanges,\n ]);\n\n $this->updateAssociations($activity, $associationChanges);\n }\n\n private function getAssociationDifferences(\n Activity $activity,\n array $oldAssociations,\n array $currentAssociations\n ): array {\n $objects = ['contactIds', 'companyIds', 'dealIds'];\n $changes = [];\n\n foreach ($objects as $object) {\n $oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);\n $currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);\n\n $added = array_diff($currentIds, $oldIds);\n $removed = array_diff($oldIds, $currentIds);\n\n $type = match ($object) {\n 'contactIds' => 'contact',\n 'companyIds' => 'company',\n 'dealIds' => 'deal',\n };\n\n $shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);\n\n if (! empty($removed) && ! $shouldIncludeRemoved) {\n $this->logger->info('[HubSpot] Skipping association removal', [\n 'team' => $this->team->getUuid(),\n 'type' => $type,\n 'oldIds' => $oldIds,\n 'currentIds' => $currentIds,\n 'skippedRemovals' => array_values($removed),\n ]);\n }\n\n if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {\n $changes[$type] = [\n 'added' => array_values($added),\n 'removed' => array_values($removed),\n ];\n }\n }\n\n return $changes;\n }\n\n /**\n * Determines whether removed associations should be synced to HubSpot.\n *\n * For contacts and companies, we always sync removals (original behavior).\n * For deals, we only sync removals when:\n * - There are new deals being added (replacement scenario), OR\n * - The activity has an account associated (indicating intentional association management)\n *\n * This prevents accidentally removing deal associations that exist in HubSpot\n * but not in Jiminny's database (e.g., deals manually added by users in HubSpot).\n */\n private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool\n {\n // Contacts and companies: always sync removals\n if ($type !== 'deal') {\n return true;\n }\n\n // Deals: sync removals if we're adding new deals (replacement)\n if (! empty($added)) {\n return true;\n }\n\n // Deals: sync removals only if activity has an account (intentional management)\n return $activity->hasAccount();\n }\n\n private function normalizeIds(array $ids): array\n {\n $stringIds = array_map('strval', $ids);\n $uniqueIds = array_unique($stringIds);\n sort($uniqueIds);\n\n return $uniqueIds;\n }\n\n private function updateAssociations(Activity $activity, array $associationChanges): void\n {\n $objectType = $activity->isTypeConference() ? 'meeting' : 'call';\n\n foreach ($associationChanges as $type => $changes) {\n if (! empty($changes['added'])) {\n $associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);\n $payload = $this->payloadBuilder->buildAddAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['added'],\n $associationType\n );\n $this->getClient()->addAssociations($objectType, $type, $payload);\n }\n\n if (! empty($changes['removed'])) {\n $payload = $this->payloadBuilder->buildRemoveAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['removed']\n );\n $this->getClient()->removeAssociations($objectType, $type, $payload);\n }\n }\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response\n {\n // If already set by us/client, do not override it\n if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {\n $this->logger->info('[HubSpot] Meeting activity type already set', [\n 'meetingId' => $meetingId,\n 'activityType' => $engagement['activityType'],\n ]);\n\n unset($engagement['activityType']);\n }\n\n $payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);\n\n return $this->getClient()->updateMeeting($meetingId, $payload);\n }\n\n private function hasEngagementActivityType(string $engagementId): bool\n {\n try {\n $engagementData = $this->getClient()->getEngagementData($engagementId);\n if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {\n return true;\n }\n\n return false;\n } catch (\\Exception $exception) {\n Sentry::captureException($exception);\n\n $this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [\n 'engagementId' => $engagementId,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n return true;\n }\n\n private function updateTask(Activity $activity, array $engagement, array $metadata): void\n {\n $this->getClient()->deleteEngagement($activity->getCrmProviderId());\n\n $this->logCrmEngagementManipulation(\n self::ACTION_DELETE,\n ['crmId' => $activity->getCrmProviderId()],\n );\n\n $this->createActivity($activity, $engagement, $metadata);\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createActivity(Activity $activity, array $engagement, array $metadata): void\n {\n $associations = $this->getActivityAssociations($activity);\n $associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);\n\n if ($activity->isTypeConference()) {\n try {\n $hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);\n $activityId = $hsEngagement->data->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n } else {\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n $activityId = $hsEngagement->data->engagement->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n }\n }\n\n private function convertActivityMeetingAssociationsV4(Activity $activity): array\n {\n $associations = [];\n\n $contactIds = $this->getParticipantsIds($activity);\n\n foreach ($contactIds as $contactId) {\n $associations[] = $this->buildAssociation(\n (int) $contactId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]\n );\n }\n\n if ($activity->hasAccount()) {\n $crmProviderId = $activity->getAccount()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]\n );\n }\n }\n\n if ($activity->hasOpportunity()) {\n $crmProviderId = $activity->getOpportunity()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]\n );\n }\n }\n\n return $associations;\n }\n\n private function buildAssociation(int $id, array $categoryToTypeIds): array\n {\n $types = [];\n\n foreach ($categoryToTypeIds as $category => $typeId) {\n $types[] = $this->buildAssociationType($category, $typeId);\n }\n\n return [\n 'to' => ['id' => (string) $id],\n 'types' => $types,\n ];\n }\n\n private function buildAssociationType(string $category, int $typeId): array\n {\n return [\n 'associationCategory' => $category,\n 'associationTypeId' => $typeId,\n ];\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createMeetingV3(array $engagement, array $associations, array $metadata): Response\n {\n $payload = $this->payloadBuilder->getV3MeetingPayload(\n engagement: $engagement,\n metadata: $metadata,\n associations: $associations\n );\n\n return $this->getClient()->createMeeting($payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n private function buildSoftphonePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'CALL',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'toNumber' => $decorator->formatCallToParticipant($activity),\n 'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),\n 'status' => $decorator->convertCallStatus($activity),\n 'durationMilliseconds' => (int) $activity->getDuration() * 1000,\n 'body' => $decorator->generateDescription($activity),\n ];\n\n // For now, this is just a \"disposition\" field to store Call Outcome.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildTextMessagePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'NOTE',\n ];\n\n $metadata = [\n 'body' => $decorator->generateDescription($activity),\n ];\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function getActivityAssociations(Activity $activity): array\n {\n return [\n 'contactIds' => $this->getParticipantsIds($activity),\n 'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],\n 'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],\n 'ownerIds' => [],\n ];\n }\n\n /**\n * Get DecorateActivity instance. Can be overridden in tests for mocking.\n */\n protected function getDecorateActivity(): DecorateActivity\n {\n return new DecorateActivity();\n }\n\n public function saveFollowupActivity(Activity $activity, array $fields): ?string\n {\n if (empty($fields['subject'])) {\n return null;\n }\n\n $associations = $this->getActivityAssociations($activity);\n\n if (empty($fields['timestamp']) === false) {\n $formatTimestamp = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['timestamp']);\n $timestamp = $formatTimestamp->getTimestamp();\n } else {\n $timestamp = strtotime('+1 day');\n }\n\n $reminder = null;\n if (empty($fields['reminder']) === false) {\n $formatReminder = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['reminder']);\n $reminder = $formatReminder->getTimestamp() * 1000;\n }\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $timestamp * 1000,\n 'type' => 'TASK',\n ];\n\n $metadata = [\n 'subject' => $fields['subject'],\n 'body' => '',\n 'status' => 'NOT_STARTED',\n 'taskType' => 'CALL',\n 'reminders' => $reminder ? [$reminder] : [],\n ];\n\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n } catch (BadRequest $e) {\n $this->logger->info('[HubSpot] Failed to create followup task', [\n 'activity' => $activity->getUuid(),\n 'engagement' => $engagement,\n 'metadata' => $metadata,\n 'error' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n // We don't actually create a corresponding activity object on our side yet.\n return (string) $hsEngagement->data->engagement->id;\n }\n\n private function logCrmEngagementManipulation(\n string $action,\n array $engagementData,\n array $data = [],\n array $associations = [],\n ): void {\n $payload = ['team' => $this->team->getUuid()];\n\n if ($action === self::ACTION_CREATE) {\n $payload['engagementData'] = $engagementData;\n $payload['data'] = $data;\n $payload['associations'] = $associations;\n }\n\n if ($action === self::ACTION_UPDATE) {\n $payload['engagementId'] = $engagementData['crmId'];\n $payload['data'] = $data;\n }\n\n if ($action === self::ACTION_DELETE) {\n $payload['engagementId'] = $engagementData['crmId'];\n }\n\n $this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.85486114,"top":0.31,"width":0.018055556,"height":0.026666667},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"bounds":{"left":0.87291664,"top":0.31,"width":0.018055556,"height":0.026666667},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"bounds":{"left":0.9138889,"top":0.31,"width":0.059027776,"height":0.026666667},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"8","depth":4,"bounds":{"left":0.8645833,"top":0.34444445,"width":0.016666668,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.8854167,"top":0.34444445,"width":0.015277778,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.9048611,"top":0.34444445,"width":0.016666668,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"4","depth":4,"bounds":{"left":0.92569447,"top":0.34444445,"width":0.016666668,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.9458333,"top":0.3422222,"width":0.015277778,"height":0.025555555},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.9611111,"top":0.3422222,"width":0.014583333,"height":0.025555555},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"SELECT\n# DISTINCT\nopp.id as opp_id, opp.uuid, opp.name,\n# COUNT(dr.id) AS deal_risk_count,\n\n# dr.id,\n# cfd.value,\nusr.name AS owner_name,\nopp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,\nusr.uuid as owner_uuid,\nusr.photo_path as owner_photo,\nusr.id AS owner_id,\njt.name as owner_job,\nopp.stage_id, opp.stage_updated_at,\nacc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,\nrt.business_process_id AS pipeline_id\nFROM opportunities opp\nLEFT JOIN record_types rt ON opp.record_type_id = rt.id\nLEFT JOIN users usr ON opp.user_id = usr.id\nLEFT JOIN accounts acc ON opp.account_id = acc.id\nLEFT JOIN job_titles jt ON usr.job_title_id = jt.id\n\n# LEFT JOIN group_deal_risk_types gdrt ON gdrt.group_id = usr.group_id\n# LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id AND dr.group_deal_risk_type_id = gdrt.id\n\nLEFT JOIN crm_field_data cfd ON (cfd.object_id = opp.id and cfd.crm_field_id = 66814)\n\nWHERE opp.user_id IS NOT NULL\nAND opp.deleted_at IS NULL\nAND opp.is_closed = 1\nAND opp.is_won = 0\nAND opp.close_date >= '2024-01-01 00:00:00'\nAND opp.close_date <= '2024-12-31 23:59:59'\nAND usr.team_id = 1\n\n# and opp.id = 4823179\n\nGROUP BY opp.id, cfd.value\nORDER BY\n# cfd.value\n CAST(cfd.value AS UNSIGNED)\n# owner_name\n# (SELECT COUNT(dr.id)\n# FROM deal_risks dr\n# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n# WHERE dr.opportunity_id = opp.id\n# AND dr.is_active = 1\n# AND gdrt.is_enabled = 1)\nDESC\nLIMIT 25\n# OFFSET 0\n;\n\nselect * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';\nselect * from crm_layout_entities where crm_layout_id = 1493;\n\nSELECT * FROM opportunities WHERE id = 4584045;\nSELECT * FROM crm_field_data WHERE object_id = 4584045;\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id = '0061K00000lfb9IQAQ'\n\nSELECT\n opp.id as opportunity_id,\n opp.name,\n COUNT(dr.id) as risk_count\nFROM opportunities opp\nLEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id\nWHERE opp.id IN (\n 6069574,8900938,9507638,9524799,9660490,9662824,9705675,9749758,4158460,4391812,6450439,4448422,5118945,5590675,4584045,7228149,9002408,9165534,9446720,9641778,9665149,9703344,9709280,5747948,4158491,6182565,6263970,5798120,5111315,4536978,6062352,6548383,6072095,6548225,4480986,5011422,6548381,8760540,8917554,9509986,9514392,9569009,9569011,9578490,9713604,5784397,7276612,7288405,8994421,9118219,9608148,9818911,4510535,6479693,5958049,6271674,6550448,4158331,4158483,6126571,6171615,6540943,4897466,5190896,5796182,5932762,8572433,8723698,8892697,9711001,9789264,4549188,6100831,6170064,6260799,6263653,6449936,6530871,6538978,7777651,8059269,8319918,8787049,8901150,9263153,9453207,9514738,9696700,9791062,5752018,6421452,7439134,7878923,9354763,9369285,9514396,9582506,9889949,9890216,4094311,4158495,4158496,6098128,5585661,3872564,6442149,5891604,6164746,6199593,6583474,6519684,9018490,9809006,4496897,5041324,5829430,6198319,6253504,6555763,7242914,7931055,8024125,8797814,8058559,8673347,8892695,8994420,9616219,9714970,9722004,9809439,9818918,6523177,8134147,9002915,9711422,9892713,9901719,9954210,9978435,5800810,6243518,6416114,6222251,6411974,6512456,5791953,6545606,9914780,5805540,6238986,6463838,6547680,9767049,9809437,9810885,9890855,6673493,9902036,4335521,6379871,6503799,6546077,8018765,9907556,9958433,9905855,9916179,9946741,9957877,5563344,6271838,6450815,7641128,7762567,7780592,8684810,8685786,8685787,8685788\n)\nGROUP BY opp.id;\n\nSELECT COUNT(dr.id)\n FROM deal_risks dr\n JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n WHERE dr.opportunity_id = 5563344\n# AND gdrt.group_id = usr.group_id\n\n\n\nEXPLAIN SELECT\n opp.id as opp_id, opp.uuid, opp.name,\n\ncfd.value,\ncfv.sequence,\ncfv.value,\n# MAX(cfd.value) AS max_cfd_value,\nusr.name AS owner_name,\nopp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,\nusr.uuid as owner_uuid,\nusr.photo_path as owner_photo,\nusr.id AS owner_id,\njt.name as owner_job,\nopp.stage_id, opp.stage_updated_at,\nacc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,\nrt.business_process_id AS pipeline_id\nFROM opportunities opp\n\nLEFT JOIN record_types rt ON opp.record_type_id = rt.id\nLEFT JOIN users usr ON opp.user_id = usr.id\nLEFT JOIN accounts acc ON opp.account_id = acc.id\nLEFT JOIN job_titles jt ON usr.job_title_id = jt.id\n\nLEFT JOIN crm_field_data cfd ON cfd.id = (SELECT sub_cfd.id\n FROM crm_field_data sub_cfd\n WHERE sub_cfd.object_id = opp.id\n AND sub_cfd.crm_field_id = 66810\n ORDER BY sub_cfd.updated_at DESC\n LIMIT 1)\n\nLEFT JOIN crm_field_values cfv ON cfv.crm_field_id = 66810 AND cfv.value = cfd.value\n\nWHERE opp.user_id IS NOT NULL\nAND opp.deleted_at IS NULL\nAND opp.is_closed = 0\n# AND opp.is_closed = 1\n# AND opp.is_won = 0\nAND opp.close_date >= '2024-01-01 00:00:00'\nAND opp.close_date <= '2024-12-31 23:59:59'\nAND usr.team_id = 1\n\n# and opp.id = 4823179\n\n# GROUP BY opp.id\nORDER BY\n cfv.sequence DESC,\n# opp.name\n# owner_name\n cfd.value\n# CAST(cfd.value AS UNSIGNED)\n# CAST(MAX(cfd.value) AS UNSIGNED)\n# (SELECT COUNT(dr.id)\n# FROM deal_risks dr\n# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n# WHERE dr.opportunity_id = opp.id\n# AND dr.is_active = 1 AND gdrt.is_enabled = 1)\nDESC\n# LIMIT 75, 25\n;\n\n\n\nSELECT * FROM crm_field_values WHERE crm_field_id = 66810;\n\nSELECT f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value, fd.updated_at, fd.id, fd.created_at\nFROM crm_fields f\nINNER JOIN crm_field_data fd ON fd.crm_field_id = f.id\n WHERE (f.crm_configuration_id = 1)\n AND (f.object_type = 'opportunity')\n# AND (fd.object_id IN (4158331,4158483,4158495,4158496,6069574,6263970,6171615,6540943,6545606,5829430,6198319,6263653,6548381,7242914,8797814,7228149,7439134,7878923,8134147,9002915,9118219,9165534,9354763,9369285,9446720))\n AND (fd.object_id IN (4158460))\n AND (f.crm_provider_id IN ('ForecastCategoryName'))\n ORDER BY fd.object_id ASC, fd.updated_at DESC;\n\nselect * from crm_layouts where crm_configuration_id = 1;\nselect cf.* from crm_fields cf\njoin crm_layout_entities cle on cf.id = cle.crm_field_id\nwhere crm_layout_id = 1493;\n\nSELECT * FROM opportunities WHERE id = 4158460;\nSELECT * FROM crm_field_data WHERE object_id = 4158460;\n\nselect * from users where team_id = 1;\nSELECT * FROM users WHERE id = 7160;\nselect * from role_user where user_id = 3248;\n\nselect * from crm_field_values where crm_field_id = 66824;\n\nSELECT * FROM users WHERE id = 23470;\nselect * from crm_configurations;\n\n# ******************************************\nselect * from teams where id = 1038; # 23521, 966\nselect * from users where team_id = 1038;\nselect * from crm_configurations where id = 966;\nSELECT * FROM activities WHERE uuid_to_bin('e8dc7c34-31f6-4430-bd81-2d9d8f00dd07') = uuid; # 54965762\nSELECT * FROM activities WHERE uuid_to_bin('4b5727c0-69d1-428f-b8d9-b649055166e2') = uuid; # 54965764\nSELECT * FROM participants WHERE activity_id = 54965764;\n\nSELECT id, uuid, title, recording_state FROM activities WHERE uuid_to_bin('355f105d-5606-4379-b0c5-d91daf59f4ce') = uuid; # 54964075\nSELECT * FROM participants WHERE activity_id = 54964075;\nselect f.id, slug, title, tf.team_id from team_features tf JOIN features f on tf.feature_id = f.id\n where tf.team_id = 1038;\n\n\n# ****************************************** PD *************************\nselect * from teams where id = 1029;\nSELECT * FROM crm_configurations WHERE id = 957;\nSELECT * FROM activities WHERE uuid_to_bin('581a33cb-4343-44bd-ac0c-4a5cee71c5ec') = uuid; # 54963423\nSELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433\n\nSELECT * FROM activities WHERE uuid_to_bin('8b6bb2f7-e7a7-4aec-8806-fbfea7093ccd') = uuid; # 54963421\n\n# ****************************************** Close *************************\nselect * from teams where id = 1031;\nSELECT * FROM crm_configurations WHERE id = 959;\nSELECT * FROM activities WHERE uuid_to_bin('59c517e0-96c9-4dad-bfeb-b7bef72f8725') = uuid; # 54963517\n\n\nSELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433\n\nSELECT * FROM activities WHERE uuid_to_bin('d68311e3-ac34-49bd-bf04-88323a7e5352') = uuid; # 54966009\n\n# ****************************************** SF *************************\nSELECT * FROM activities WHERE uuid_to_bin('2bd4cfa3-4eb5-4c77-84a7-9ed46a21c988') = uuid;\n\nselect * from roles;\nselect * from permissions;\nselect * from permission_role where permission_id = 136;\n\nselect * from migrations order by id desc;\n\nselect * from teams where id IN (1, 1037);\nselect * from crm_layouts where crm_configuration_id = 1;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1493;\nSELECT * FROM crm_fields WHERE id IN (1652,1661,66799,66814,66821,66836,66843,66846,66864,84752,182306,323580,380378);\n\nselect * from features;\nselect * from team_features where feature_id = 33;\nselect * from opportunities;\n\nselect * from teams;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1052 and sa.provider = 'hubspot';\n\nSELECT * FROM accounts where id = 11512582;\nselect * from activities where account_id = 11512582;","depth":4,"on_screen":true,"value":"SELECT\n# DISTINCT\nopp.id as opp_id, opp.uuid, opp.name,\n# COUNT(dr.id) AS deal_risk_count,\n\n# dr.id,\n# cfd.value,\nusr.name AS owner_name,\nopp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,\nusr.uuid as owner_uuid,\nusr.photo_path as owner_photo,\nusr.id AS owner_id,\njt.name as owner_job,\nopp.stage_id, opp.stage_updated_at,\nacc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,\nrt.business_process_id AS pipeline_id\nFROM opportunities opp\nLEFT JOIN record_types rt ON opp.record_type_id = rt.id\nLEFT JOIN users usr ON opp.user_id = usr.id\nLEFT JOIN accounts acc ON opp.account_id = acc.id\nLEFT JOIN job_titles jt ON usr.job_title_id = jt.id\n\n# LEFT JOIN group_deal_risk_types gdrt ON gdrt.group_id = usr.group_id\n# LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id AND dr.group_deal_risk_type_id = gdrt.id\n\nLEFT JOIN crm_field_data cfd ON (cfd.object_id = opp.id and cfd.crm_field_id = 66814)\n\nWHERE opp.user_id IS NOT NULL\nAND opp.deleted_at IS NULL\nAND opp.is_closed = 1\nAND opp.is_won = 0\nAND opp.close_date >= '2024-01-01 00:00:00'\nAND opp.close_date <= '2024-12-31 23:59:59'\nAND usr.team_id = 1\n\n# and opp.id = 4823179\n\nGROUP BY opp.id, cfd.value\nORDER BY\n# cfd.value\n CAST(cfd.value AS UNSIGNED)\n# owner_name\n# (SELECT COUNT(dr.id)\n# FROM deal_risks dr\n# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n# WHERE dr.opportunity_id = opp.id\n# AND dr.is_active = 1\n# AND gdrt.is_enabled = 1)\nDESC\nLIMIT 25\n# OFFSET 0\n;\n\nselect * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';\nselect * from crm_layout_entities where crm_layout_id = 1493;\n\nSELECT * FROM opportunities WHERE id = 4584045;\nSELECT * FROM crm_field_data WHERE object_id = 4584045;\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id = '0061K00000lfb9IQAQ'\n\nSELECT\n opp.id as opportunity_id,\n opp.name,\n COUNT(dr.id) as risk_count\nFROM opportunities opp\nLEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id\nWHERE opp.id IN (\n 6069574,8900938,9507638,9524799,9660490,9662824,9705675,9749758,4158460,4391812,6450439,4448422,5118945,5590675,4584045,7228149,9002408,9165534,9446720,9641778,9665149,9703344,9709280,5747948,4158491,6182565,6263970,5798120,5111315,4536978,6062352,6548383,6072095,6548225,4480986,5011422,6548381,8760540,8917554,9509986,9514392,9569009,9569011,9578490,9713604,5784397,7276612,7288405,8994421,9118219,9608148,9818911,4510535,6479693,5958049,6271674,6550448,4158331,4158483,6126571,6171615,6540943,4897466,5190896,5796182,5932762,8572433,8723698,8892697,9711001,9789264,4549188,6100831,6170064,6260799,6263653,6449936,6530871,6538978,7777651,8059269,8319918,8787049,8901150,9263153,9453207,9514738,9696700,9791062,5752018,6421452,7439134,7878923,9354763,9369285,9514396,9582506,9889949,9890216,4094311,4158495,4158496,6098128,5585661,3872564,6442149,5891604,6164746,6199593,6583474,6519684,9018490,9809006,4496897,5041324,5829430,6198319,6253504,6555763,7242914,7931055,8024125,8797814,8058559,8673347,8892695,8994420,9616219,9714970,9722004,9809439,9818918,6523177,8134147,9002915,9711422,9892713,9901719,9954210,9978435,5800810,6243518,6416114,6222251,6411974,6512456,5791953,6545606,9914780,5805540,6238986,6463838,6547680,9767049,9809437,9810885,9890855,6673493,9902036,4335521,6379871,6503799,6546077,8018765,9907556,9958433,9905855,9916179,9946741,9957877,5563344,6271838,6450815,7641128,7762567,7780592,8684810,8685786,8685787,8685788\n)\nGROUP BY opp.id;\n\nSELECT COUNT(dr.id)\n FROM deal_risks dr\n JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n WHERE dr.opportunity_id = 5563344\n# AND gdrt.group_id = usr.group_id\n\n\n\nEXPLAIN SELECT\n opp.id as opp_id, opp.uuid, opp.name,\n\ncfd.value,\ncfv.sequence,\ncfv.value,\n# MAX(cfd.value) AS max_cfd_value,\nusr.name AS owner_name,\nopp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,\nusr.uuid as owner_uuid,\nusr.photo_path as owner_photo,\nusr.id AS owner_id,\njt.name as owner_job,\nopp.stage_id, opp.stage_updated_at,\nacc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,\nrt.business_process_id AS pipeline_id\nFROM opportunities opp\n\nLEFT JOIN record_types rt ON opp.record_type_id = rt.id\nLEFT JOIN users usr ON opp.user_id = usr.id\nLEFT JOIN accounts acc ON opp.account_id = acc.id\nLEFT JOIN job_titles jt ON usr.job_title_id = jt.id\n\nLEFT JOIN crm_field_data cfd ON cfd.id = (SELECT sub_cfd.id\n FROM crm_field_data sub_cfd\n WHERE sub_cfd.object_id = opp.id\n AND sub_cfd.crm_field_id = 66810\n ORDER BY sub_cfd.updated_at DESC\n LIMIT 1)\n\nLEFT JOIN crm_field_values cfv ON cfv.crm_field_id = 66810 AND cfv.value = cfd.value\n\nWHERE opp.user_id IS NOT NULL\nAND opp.deleted_at IS NULL\nAND opp.is_closed = 0\n# AND opp.is_closed = 1\n# AND opp.is_won = 0\nAND opp.close_date >= '2024-01-01 00:00:00'\nAND opp.close_date <= '2024-12-31 23:59:59'\nAND usr.team_id = 1\n\n# and opp.id = 4823179\n\n# GROUP BY opp.id\nORDER BY\n cfv.sequence DESC,\n# opp.name\n# owner_name\n cfd.value\n# CAST(cfd.value AS UNSIGNED)\n# CAST(MAX(cfd.value) AS UNSIGNED)\n# (SELECT COUNT(dr.id)\n# FROM deal_risks dr\n# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n# WHERE dr.opportunity_id = opp.id\n# AND dr.is_active = 1 AND gdrt.is_enabled = 1)\nDESC\n# LIMIT 75, 25\n;\n\n\n\nSELECT * FROM crm_field_values WHERE crm_field_id = 66810;\n\nSELECT f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value, fd.updated_at, fd.id, fd.created_at\nFROM crm_fields f\nINNER JOIN crm_field_data fd ON fd.crm_field_id = f.id\n WHERE (f.crm_configuration_id = 1)\n AND (f.object_type = 'opportunity')\n# AND (fd.object_id IN (4158331,4158483,4158495,4158496,6069574,6263970,6171615,6540943,6545606,5829430,6198319,6263653,6548381,7242914,8797814,7228149,7439134,7878923,8134147,9002915,9118219,9165534,9354763,9369285,9446720))\n AND (fd.object_id IN (4158460))\n AND (f.crm_provider_id IN ('ForecastCategoryName'))\n ORDER BY fd.object_id ASC, fd.updated_at DESC;\n\nselect * from crm_layouts where crm_configuration_id = 1;\nselect cf.* from crm_fields cf\njoin crm_layout_entities cle on cf.id = cle.crm_field_id\nwhere crm_layout_id = 1493;\n\nSELECT * FROM opportunities WHERE id = 4158460;\nSELECT * FROM crm_field_data WHERE object_id = 4158460;\n\nselect * from users where team_id = 1;\nSELECT * FROM users WHERE id = 7160;\nselect * from role_user where user_id = 3248;\n\nselect * from crm_field_values where crm_field_id = 66824;\n\nSELECT * FROM users WHERE id = 23470;\nselect * from crm_configurations;\n\n# ******************************************\nselect * from teams where id = 1038; # 23521, 966\nselect * from users where team_id = 1038;\nselect * from crm_configurations where id = 966;\nSELECT * FROM activities WHERE uuid_to_bin('e8dc7c34-31f6-4430-bd81-2d9d8f00dd07') = uuid; # 54965762\nSELECT * FROM activities WHERE uuid_to_bin('4b5727c0-69d1-428f-b8d9-b649055166e2') = uuid; # 54965764\nSELECT * FROM participants WHERE activity_id = 54965764;\n\nSELECT id, uuid, title, recording_state FROM activities WHERE uuid_to_bin('355f105d-5606-4379-b0c5-d91daf59f4ce') = uuid; # 54964075\nSELECT * FROM participants WHERE activity_id = 54964075;\nselect f.id, slug, title, tf.team_id from team_features tf JOIN features f on tf.feature_id = f.id\n where tf.team_id = 1038;\n\n\n# ****************************************** PD *************************\nselect * from teams where id = 1029;\nSELECT * FROM crm_configurations WHERE id = 957;\nSELECT * FROM activities WHERE uuid_to_bin('581a33cb-4343-44bd-ac0c-4a5cee71c5ec') = uuid; # 54963423\nSELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433\n\nSELECT * FROM activities WHERE uuid_to_bin('8b6bb2f7-e7a7-4aec-8806-fbfea7093ccd') = uuid; # 54963421\n\n# ****************************************** Close *************************\nselect * from teams where id = 1031;\nSELECT * FROM crm_configurations WHERE id = 959;\nSELECT * FROM activities WHERE uuid_to_bin('59c517e0-96c9-4dad-bfeb-b7bef72f8725') = uuid; # 54963517\n\n\nSELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433\n\nSELECT * FROM activities WHERE uuid_to_bin('d68311e3-ac34-49bd-bf04-88323a7e5352') = uuid; # 54966009\n\n# ****************************************** SF *************************\nSELECT * FROM activities WHERE uuid_to_bin('2bd4cfa3-4eb5-4c77-84a7-9ed46a21c988') = uuid;\n\nselect * from roles;\nselect * from permissions;\nselect * from permission_role where permission_id = 136;\n\nselect * from migrations order by id desc;\n\nselect * from teams where id IN (1, 1037);\nselect * from crm_layouts where crm_configuration_id = 1;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1493;\nSELECT * FROM crm_fields WHERE id IN (1652,1661,66799,66814,66821,66836,66843,66846,66864,84752,182306,323580,380378);\n\nselect * from features;\nselect * from team_features where feature_id = 33;\nselect * from opportunities;\n\nselect * from teams;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1052 and sa.provider = 'hubspot';\n\nSELECT * FROM accounts where id = 11512582;\nselect * from activities where account_id = 11512582;","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.025,"top":0.06666667,"width":0.050694443,"height":0.034444444},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3382584868951134281
|
6551498163460912845
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
8
6
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldDataRepository;
use Jiminny\Services\Crm\Hubspot\DecorateActivity;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationCategoryV4;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationTypeIdV4;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Http\Response;
use SevenShores\Hubspot\Exceptions\HubspotException;
use Sentry;
trait WriteCrmTrait
{
private const string ACTION_CREATE = 'create';
private const string ACTION_UPDATE = 'update';
private const string ACTION_DELETE = 'delete';
/**
* @inheritdoc
*/
public function saveActivity(Activity $activity): Activity
{
switch ($activity->getCrmType()) {
case Activity::TYPE_CONFERENCE:
$activity = $this->buildConferencePayload($activity);
break;
case Activity::TYPE_SOFTPHONE:
case Activity::TYPE_SOFTPHONE_INBOUND:
$activity = $this->buildSoftphonePayload($activity);
break;
case Activity::TYPE_SMS_INBOUND:
case Activity::TYPE_SMS_OUTBOUND:
$activity = $this->buildTextMessagePayload($activity);
break;
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildConferencePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatStartTime($activity),
'type' => 'MEETING',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'meetingOutcome' => $decorator->convertMeetingOutcome($activity),
'internalMeetingNotes' => $decorator->generateDescription($activity),
];
// Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.
if (! $activity->hasCrmProviderId()) {
if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {
$metadata['startTime'] = $decorator->formatStartTime($activity);
$metadata['endTime'] = $decorator->formatEndTime($activity);
}
$metadata['title'] = $decorator->generateTitle($activity);
}
// For now, this is just a "meetingOutcome" field for some customers.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function fetchCustomFieldData(Activity $activity, string $objectType): array
{
$payload = [];
$fieldDataRepository = app(FieldDataRepository::class);
$fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);
foreach ($fieldData as $data) {
// Add the field and value to the payload.
$payload += [
$data->getField()->getCrmProviderId() => $data->getValue(),
];
}
return $payload;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity
{
if (! $activity->hasCrmProviderId()) {
$this->createActivity($activity, $engagement, $metadata);
return $activity;
}
try {
$oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());
} catch (HubspotException $e) {
$this->logger->warning('[HubSpot] Engagement not found', [
'activity_id' => $activity->getUuid(),
'crm_provider_id' => $activity->getCrmProviderId(),
'exception' => $e->getMessage(),
]);
// potentially here we can log new activity if it is not found
return $activity;
}
$type = $oldEngagement['engagement']['type'] ?? null;
$bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';
$decorator = $this->getDecorateActivity();
if ($type !== 'TASK') {
$oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldDescription
);
$oldAssociations = $oldEngagement['associations'] ?? [];
$this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);
} else {
$oldTaskBody = $oldEngagement['metadata']['body'] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldTaskBody
);
$this->updateTask($activity, $engagement, $metadata);
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateNonTaskActivity(
Activity $activity,
array $engagement,
array $metadata,
array $oldAssociations
): void {
if ($activity->isTypeConference()) {
$this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);
} else {
$this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);
}
$this->updateActivityAssociations($activity, $oldAssociations);
$this->logCrmEngagementManipulation(
self::ACTION_UPDATE,
['crmId' => $activity->getCrmProviderId()],
$metadata,
);
}
private function updateActivityAssociations(Activity $activity, array $oldAssociations): void
{
$currentAssociations = $this->getActivityAssociations($activity);
$associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);
// No changes - exit
if (empty($associationChanges)) {
return;
}
$this->logger->info('[HubSpot] Updating associations', [
'team' => $this->team->getUuid(),
'activity' => $activity->getUuid(),
'associationChanges' => $associationChanges,
]);
$this->updateAssociations($activity, $associationChanges);
}
private function getAssociationDifferences(
Activity $activity,
array $oldAssociations,
array $currentAssociations
): array {
$objects = ['contactIds', 'companyIds', 'dealIds'];
$changes = [];
foreach ($objects as $object) {
$oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);
$currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);
$added = array_diff($currentIds, $oldIds);
$removed = array_diff($oldIds, $currentIds);
$type = match ($object) {
'contactIds' => 'contact',
'companyIds' => 'company',
'dealIds' => 'deal',
};
$shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);
if (! empty($removed) && ! $shouldIncludeRemoved) {
$this->logger->info('[HubSpot] Skipping association removal', [
'team' => $this->team->getUuid(),
'type' => $type,
'oldIds' => $oldIds,
'currentIds' => $currentIds,
'skippedRemovals' => array_values($removed),
]);
}
if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {
$changes[$type] = [
'added' => array_values($added),
'removed' => array_values($removed),
];
}
}
return $changes;
}
/**
* Determines whether removed associations should be synced to HubSpot.
*
* For contacts and companies, we always sync removals (original behavior).
* For deals, we only sync removals when:
* - There are new deals being added (replacement scenario), OR
* - The activity has an account associated (indicating intentional association management)
*
* This prevents accidentally removing deal associations that exist in HubSpot
* but not in Jiminny's database (e.g., deals manually added by users in HubSpot).
*/
private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool
{
// Contacts and companies: always sync removals
if ($type !== 'deal') {
return true;
}
// Deals: sync removals if we're adding new deals (replacement)
if (! empty($added)) {
return true;
}
// Deals: sync removals only if activity has an account (intentional management)
return $activity->hasAccount();
}
private function normalizeIds(array $ids): array
{
$stringIds = array_map('strval', $ids);
$uniqueIds = array_unique($stringIds);
sort($uniqueIds);
return $uniqueIds;
}
private function updateAssociations(Activity $activity, array $associationChanges): void
{
$objectType = $activity->isTypeConference() ? 'meeting' : 'call';
foreach ($associationChanges as $type => $changes) {
if (! empty($changes['added'])) {
$associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);
$payload = $this->payloadBuilder->buildAddAssociationPayload(
$activity->getCrmProviderId(),
$changes['added'],
$associationType
);
$this->getClient()->addAssociations($objectType, $type, $payload);
}
if (! empty($changes['removed'])) {
$payload = $this->payloadBuilder->buildRemoveAssociationPayload(
$activity->getCrmProviderId(),
$changes['removed']
);
$this->getClient()->removeAssociations($objectType, $type, $payload);
}
}
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response
{
// If already set by us/client, do not override it
if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {
$this->logger->info('[HubSpot] Meeting activity type already set', [
'meetingId' => $meetingId,
'activityType' => $engagement['activityType'],
]);
unset($engagement['activityType']);
}
$payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);
return $this->getClient()->updateMeeting($meetingId, $payload);
}
private function hasEngagementActivityType(string $engagementId): bool
{
try {
$engagementData = $this->getClient()->getEngagementData($engagementId);
if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {
return true;
}
return false;
} catch (\Exception $exception) {
Sentry::captureException($exception);
$this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [
'engagementId' => $engagementId,
'exception' => $exception->getMessage(),
]);
}
return true;
}
private function updateTask(Activity $activity, array $engagement, array $metadata): void
{
$this->getClient()->deleteEngagement($activity->getCrmProviderId());
$this->logCrmEngagementManipulation(
self::ACTION_DELETE,
['crmId' => $activity->getCrmProviderId()],
);
$this->createActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createActivity(Activity $activity, array $engagement, array $metadata): void
{
$associations = $this->getActivityAssociations($activity);
$associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);
if ($activity->isTypeConference()) {
try {
$hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);
$activityId = $hsEngagement->data->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
} else {
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
$activityId = $hsEngagement->data->engagement->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
}
}
private function convertActivityMeetingAssociationsV4(Activity $activity): array
{
$associations = [];
$contactIds = $this->getParticipantsIds($activity);
foreach ($contactIds as $contactId) {
$associations[] = $this->buildAssociation(
(int) $contactId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]
);
}
if ($activity->hasAccount()) {
$crmProviderId = $activity->getAccount()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]
);
}
}
if ($activity->hasOpportunity()) {
$crmProviderId = $activity->getOpportunity()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]
);
}
}
return $associations;
}
private function buildAssociation(int $id, array $categoryToTypeIds): array
{
$types = [];
foreach ($categoryToTypeIds as $category => $typeId) {
$types[] = $this->buildAssociationType($category, $typeId);
}
return [
'to' => ['id' => (string) $id],
'types' => $types,
];
}
private function buildAssociationType(string $category, int $typeId): array
{
return [
'associationCategory' => $category,
'associationTypeId' => $typeId,
];
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createMeetingV3(array $engagement, array $associations, array $metadata): Response
{
$payload = $this->payloadBuilder->getV3MeetingPayload(
engagement: $engagement,
metadata: $metadata,
associations: $associations
);
return $this->getClient()->createMeeting($payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
private function buildSoftphonePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'CALL',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'toNumber' => $decorator->formatCallToParticipant($activity),
'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),
'status' => $decorator->convertCallStatus($activity),
'durationMilliseconds' => (int) $activity->getDuration() * 1000,
'body' => $decorator->generateDescription($activity),
];
// For now, this is just a "disposition" field to store Call Outcome.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildTextMessagePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'NOTE',
];
$metadata = [
'body' => $decorator->generateDescription($activity),
];
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function getActivityAssociations(Activity $activity): array
{
return [
'contactIds' => $this->getParticipantsIds($activity),
'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],
'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],
'ownerIds' => [],
];
}
/**
* Get DecorateActivity instance. Can be overridden in tests for mocking.
*/
protected function getDecorateActivity(): DecorateActivity
{
return new DecorateActivity();
}
public function saveFollowupActivity(Activity $activity, array $fields): ?string
{
if (empty($fields['subject'])) {
return null;
}
$associations = $this->getActivityAssociations($activity);
if (empty($fields['timestamp']) === false) {
$formatTimestamp = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['timestamp']);
$timestamp = $formatTimestamp->getTimestamp();
} else {
$timestamp = strtotime('+1 day');
}
$reminder = null;
if (empty($fields['reminder']) === false) {
$formatReminder = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['reminder']);
$reminder = $formatReminder->getTimestamp() * 1000;
}
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $timestamp * 1000,
'type' => 'TASK',
];
$metadata = [
'subject' => $fields['subject'],
'body' => '',
'status' => 'NOT_STARTED',
'taskType' => 'CALL',
'reminders' => $reminder ? [$reminder] : [],
];
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
} catch (BadRequest $e) {
$this->logger->info('[HubSpot] Failed to create followup task', [
'activity' => $activity->getUuid(),
'engagement' => $engagement,
'metadata' => $metadata,
'error' => $e->getMessage(),
]);
return null;
}
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
// We don't actually create a corresponding activity object on our side yet.
return (string) $hsEngagement->data->engagement->id;
}
private function logCrmEngagementManipulation(
string $action,
array $engagementData,
array $data = [],
array $associations = [],
): void {
$payload = ['team' => $this->team->getUuid()];
if ($action === self::ACTION_CREATE) {
$payload['engagementData'] = $engagementData;
$payload['data'] = $data;
$payload['associations'] = $associations;
}
if ($action === self::ACTION_UPDATE) {
$payload['engagementId'] = $engagementData['crmId'];
$payload['data'] = $data;
}
if ($action === self::ACTION_DELETE) {
$payload['engagementId'] = $engagementData['crmId'];
}
$this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
8
1
3
4
Previous Highlighted Error
Next Highlighted Error
SELECT
# DISTINCT
opp.id as opp_id, opp.uuid, opp.name,
# COUNT(dr.id) AS deal_risk_count,
# dr.id,
# cfd.value,
usr.name AS owner_name,
opp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,
usr.uuid as owner_uuid,
usr.photo_path as owner_photo,
usr.id AS owner_id,
jt.name as owner_job,
opp.stage_id, opp.stage_updated_at,
acc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,
rt.business_process_id AS pipeline_id
FROM opportunities opp
LEFT JOIN record_types rt ON opp.record_type_id = rt.id
LEFT JOIN users usr ON opp.user_id = usr.id
LEFT JOIN accounts acc ON opp.account_id = acc.id
LEFT JOIN job_titles jt ON usr.job_title_id = jt.id
# LEFT JOIN group_deal_risk_types gdrt ON gdrt.group_id = usr.group_id
# LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id AND dr.group_deal_risk_type_id = gdrt.id
LEFT JOIN crm_field_data cfd ON (cfd.object_id = opp.id and cfd.crm_field_id = 66814)
WHERE opp.user_id IS NOT NULL
AND opp.deleted_at IS NULL
AND opp.is_closed = 1
AND opp.is_won = 0
AND opp.close_date >= '2024-01-01 00:00:00'
AND opp.close_date <= '2024-12-31 23:59:59'
AND usr.team_id = 1
# and opp.id = 4823179
GROUP BY opp.id, cfd.value
ORDER BY
# cfd.value
CAST(cfd.value AS UNSIGNED)
# owner_name
# (SELECT COUNT(dr.id)
# FROM deal_risks dr
# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
# WHERE dr.opportunity_id = opp.id
# AND dr.is_active = 1
# AND gdrt.is_enabled = 1)
DESC
LIMIT 25
# OFFSET 0
;
select * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';
select * from crm_layout_entities where crm_layout_id = 1493;
SELECT * FROM opportunities WHERE id = 4584045;
SELECT * FROM crm_field_data WHERE object_id = 4584045;
SELECT * FROM opportunities where team_id = 1 and crm_provider_id = '0061K00000lfb9IQAQ'
SELECT
opp.id as opportunity_id,
opp.name,
COUNT(dr.id) as risk_count
FROM opportunities opp
LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id
WHERE opp.id IN (
6069574,8900938,9507638,9524799,9660490,9662824,9705675,9749758,4158460,4391812,6450439,4448422,5118945,5590675,4584045,7228149,9002408,9165534,9446720,9641778,9665149,9703344,9709280,5747948,4158491,6182565,6263970,5798120,5111315,4536978,6062352,6548383,6072095,6548225,4480986,5011422,6548381,8760540,8917554,9509986,9514392,9569009,9569011,9578490,9713604,5784397,7276612,7288405,8994421,9118219,9608148,9818911,4510535,6479693,5958049,6271674,6550448,4158331,4158483,6126571,6171615,6540943,4897466,5190896,5796182,5932762,8572433,8723698,8892697,9711001,9789264,4549188,6100831,6170064,6260799,6263653,6449936,6530871,6538978,7777651,8059269,8319918,8787049,8901150,9263153,9453207,9514738,9696700,9791062,5752018,6421452,7439134,7878923,9354763,9369285,9514396,9582506,9889949,9890216,4094311,4158495,4158496,6098128,5585661,3872564,6442149,5891604,6164746,6199593,6583474,6519684,9018490,9809006,4496897,5041324,5829430,6198319,6253504,6555763,7242914,7931055,8024125,8797814,8058559,8673347,8892695,8994420,9616219,9714970,9722004,9809439,9818918,6523177,8134147,9002915,9711422,9892713,9901719,9954210,9978435,5800810,6243518,6416114,6222251,6411974,6512456,5791953,6545606,9914780,5805540,6238986,6463838,6547680,9767049,9809437,9810885,9890855,6673493,9902036,4335521,6379871,6503799,6546077,8018765,9907556,9958433,9905855,9916179,9946741,9957877,5563344,6271838,6450815,7641128,7762567,7780592,8684810,8685786,8685787,8685788
)
GROUP BY opp.id;
SELECT COUNT(dr.id)
FROM deal_risks dr
JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
WHERE dr.opportunity_id = 5563344
# AND gdrt.group_id = usr.group_id
EXPLAIN SELECT
opp.id as opp_id, opp.uuid, opp.name,
cfd.value,
cfv.sequence,
cfv.value,
# MAX(cfd.value) AS max_cfd_value,
usr.name AS owner_name,
opp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,
usr.uuid as owner_uuid,
usr.photo_path as owner_photo,
usr.id AS owner_id,
jt.name as owner_job,
opp.stage_id, opp.stage_updated_at,
acc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,
rt.business_process_id AS pipeline_id
FROM opportunities opp
LEFT JOIN record_types rt ON opp.record_type_id = rt.id
LEFT JOIN users usr ON opp.user_id = usr.id
LEFT JOIN accounts acc ON opp.account_id = acc.id
LEFT JOIN job_titles jt ON usr.job_title_id = jt.id
LEFT JOIN crm_field_data cfd ON cfd.id = (SELECT sub_cfd.id
FROM crm_field_data sub_cfd
WHERE sub_cfd.object_id = opp.id
AND sub_cfd.crm_field_id = 66810
ORDER BY sub_cfd.updated_at DESC
LIMIT 1)
LEFT JOIN crm_field_values cfv ON cfv.crm_field_id = 66810 AND cfv.value = cfd.value
WHERE opp.user_id IS NOT NULL
AND opp.deleted_at IS NULL
AND opp.is_closed = 0
# AND opp.is_closed = 1
# AND opp.is_won = 0
AND opp.close_date >= '2024-01-01 00:00:00'
AND opp.close_date <= '2024-12-31 23:59:59'
AND usr.team_id = 1
# and opp.id = 4823179
# GROUP BY opp.id
ORDER BY
cfv.sequence DESC,
# opp.name
# owner_name
cfd.value
# CAST(cfd.value AS UNSIGNED)
# CAST(MAX(cfd.value) AS UNSIGNED)
# (SELECT COUNT(dr.id)
# FROM deal_risks dr
# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
# WHERE dr.opportunity_id = opp.id
# AND dr.is_active = 1 AND gdrt.is_enabled = 1)
DESC
# LIMIT 75, 25
;
SELECT * FROM crm_field_values WHERE crm_field_id = 66810;
SELECT f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value, fd.updated_at, fd.id, fd.created_at
FROM crm_fields f
INNER JOIN crm_field_data fd ON fd.crm_field_id = f.id
WHERE (f.crm_configuration_id = 1)
AND (f.object_type = 'opportunity')
# AND (fd.object_id IN (4158331,4158483,4158495,4158496,6069574,6263970,6171615,6540943,6545606,5829430,6198319,6263653,6548381,7242914,8797814,7228149,7439134,7878923,8134147,9002915,9118219,9165534,9354763,9369285,9446720))
AND (fd.object_id IN (4158460))
AND (f.crm_provider_id IN ('ForecastCategoryName'))
ORDER BY fd.object_id ASC, fd.updated_at DESC;
select * from crm_layouts where crm_configuration_id = 1;
select cf.* from crm_fields cf
join crm_layout_entities cle on cf.id = cle.crm_field_id
where crm_layout_id = 1493;
SELECT * FROM opportunities WHERE id = 4158460;
SELECT * FROM crm_field_data WHERE object_id = 4158460;
select * from users where team_id = 1;
SELECT * FROM users WHERE id = 7160;
select * from role_user where user_id = 3248;
select * from crm_field_values where crm_field_id = 66824;
SELECT * FROM users WHERE id = 23470;
select * from crm_configurations;
# [PASSWORD_DOTS]
select * from teams where id = 1038; # 23521, 966
select * from users where team_id = 1038;
select * from crm_configurations where id = 966;
SELECT * FROM activities WHERE uuid_to_bin('e8dc7c34-31f6-4430-bd81-2d9d8f00dd07') = uuid; # 54965762
SELECT * FROM activities WHERE uuid_to_bin('4b5727c0-69d1-428f-b8d9-b649055166e2') = uuid; # 54965764
SELECT * FROM participants WHERE activity_id = 54965764;
SELECT id, uuid, title, recording_state FROM activities WHERE uuid_to_bin('355f105d-5606-4379-b0c5-d91daf59f4ce') = uuid; # 54964075
SELECT * FROM participants WHERE activity_id = 54964075;
select f.id, slug, title, tf.team_id from team_features tf JOIN features f on tf.feature_id = f.id
where tf.team_id = 1038;
# [PASSWORD_DOTS] PD [PASSWORD_DOTS]
select * from teams where id = 1029;
SELECT * FROM crm_configurations WHERE id = 957;
SELECT * FROM activities WHERE uuid_to_bin('581a33cb-4343-44bd-ac0c-4a5cee71c5ec') = uuid; # 54963423
SELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433
SELECT * FROM activities WHERE uuid_to_bin('8b6bb2f7-e7a7-4aec-8806-fbfea7093ccd') = uuid; # 54963421
# [PASSWORD_DOTS] Close [PASSWORD_DOTS]
select * from teams where id = 1031;
SELECT * FROM crm_configurations WHERE id = 959;
SELECT * FROM activities WHERE uuid_to_bin('59c517e0-96c9-4dad-bfeb-b7bef72f8725') = uuid; # 54963517
SELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433
SELECT * FROM activities WHERE uuid_to_bin('d68311e3-ac34-49bd-bf04-88323a7e5352') = uuid; # 54966009
# [PASSWORD_DOTS] SF [PASSWORD_DOTS]
SELECT * FROM activities WHERE uuid_to_bin('2bd4cfa3-4eb5-4c77-84a7-9ed46a21c988') = uuid;
select * from roles;
select * from permissions;
select * from permission_role where permission_id = 136;
select * from migrations order by id desc;
select * from teams where id IN (1, 1037);
select * from crm_layouts where crm_configuration_id = 1;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 1493;
SELECT * FROM crm_fields WHERE id IN (1652,1661,66799,66814,66821,66836,66843,66846,66864,84752,182306,323580,380378);
select * from features;
select * from team_features where feature_id = 33;
select * from opportunities;
select * from teams;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1052 and sa.provider = 'hubspot';
SELECT * FROM accounts where id = 11512582;
select * from activities where account_id = 11512582;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45160
|
1622
|
5
|
2026-05-14T14:23:08.543772+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768588543_m2.jpg...
|
PhpStorm
|
faVsco.js – WriteCrmTrait.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
8
6
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldDataRepository;
use Jiminny\Services\Crm\Hubspot\DecorateActivity;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationCategoryV4;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationTypeIdV4;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Http\Response;
use SevenShores\Hubspot\Exceptions\HubspotException;
use Sentry;
trait WriteCrmTrait
{
private const string ACTION_CREATE = 'create';
private const string ACTION_UPDATE = 'update';
private const string ACTION_DELETE = 'delete';
/**
* @inheritdoc
*/
public function saveActivity(Activity $activity): Activity
{
switch ($activity->getCrmType()) {
case Activity::TYPE_CONFERENCE:
$activity = $this->buildConferencePayload($activity);
break;
case Activity::TYPE_SOFTPHONE:
case Activity::TYPE_SOFTPHONE_INBOUND:
$activity = $this->buildSoftphonePayload($activity);
break;
case Activity::TYPE_SMS_INBOUND:
case Activity::TYPE_SMS_OUTBOUND:
$activity = $this->buildTextMessagePayload($activity);
break;
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildConferencePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatStartTime($activity),
'type' => 'MEETING',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'meetingOutcome' => $decorator->convertMeetingOutcome($activity),
'internalMeetingNotes' => $decorator->generateDescription($activity),
];
// Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.
if (! $activity->hasCrmProviderId()) {
if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {
$metadata['startTime'] = $decorator->formatStartTime($activity);
$metadata['endTime'] = $decorator->formatEndTime($activity);
}
$metadata['title'] = $decorator->generateTitle($activity);
}
// For now, this is just a "meetingOutcome" field for some customers.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function fetchCustomFieldData(Activity $activity, string $objectType): array
{
$payload = [];
$fieldDataRepository = app(FieldDataRepository::class);
$fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);
foreach ($fieldData as $data) {
// Add the field and value to the payload.
$payload += [
$data->getField()->getCrmProviderId() => $data->getValue(),
];
}
return $payload;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity
{
if (! $activity->hasCrmProviderId()) {
$this->createActivity($activity, $engagement, $metadata);
return $activity;
}
try {
$oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());
} catch (HubspotException $e) {
$this->logger->warning('[HubSpot] Engagement not found', [
'activity_id' => $activity->getUuid(),
'crm_provider_id' => $activity->getCrmProviderId(),
'exception' => $e->getMessage(),
]);
// potentially here we can log new activity if it is not found
return $activity;
}
$type = $oldEngagement['engagement']['type'] ?? null;
$bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';
$decorator = $this->getDecorateActivity();
if ($type !== 'TASK') {
$oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldDescription
);
$oldAssociations = $oldEngagement['associations'] ?? [];
$this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);
} else {
$oldTaskBody = $oldEngagement['metadata']['body'] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldTaskBody
);
$this->updateTask($activity, $engagement, $metadata);
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateNonTaskActivity(
Activity $activity,
array $engagement,
array $metadata,
array $oldAssociations
): void {
if ($activity->isTypeConference()) {
$this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);
} else {
$this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);
}
$this->updateActivityAssociations($activity, $oldAssociations);
$this->logCrmEngagementManipulation(
self::ACTION_UPDATE,
['crmId' => $activity->getCrmProviderId()],
$metadata,
);
}
private function updateActivityAssociations(Activity $activity, array $oldAssociations): void
{
$currentAssociations = $this->getActivityAssociations($activity);
$associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);
// No changes - exit
if (empty($associationChanges)) {
return;
}
$this->logger->info('[HubSpot] Updating associations', [
'team' => $this->team->getUuid(),
'activity' => $activity->getUuid(),
'associationChanges' => $associationChanges,
]);
$this->updateAssociations($activity, $associationChanges);
}
private function getAssociationDifferences(
Activity $activity,
array $oldAssociations,
array $currentAssociations
): array {
$objects = ['contactIds', 'companyIds', 'dealIds'];
$changes = [];
foreach ($objects as $object) {
$oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);
$currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);
$added = array_diff($currentIds, $oldIds);
$removed = array_diff($oldIds, $currentIds);
$type = match ($object) {
'contactIds' => 'contact',
'companyIds' => 'company',
'dealIds' => 'deal',
};
$shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);
if (! empty($removed) && ! $shouldIncludeRemoved) {
$this->logger->info('[HubSpot] Skipping association removal', [
'team' => $this->team->getUuid(),
'type' => $type,
'oldIds' => $oldIds,
'currentIds' => $currentIds,
'skippedRemovals' => array_values($removed),
]);
}
if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {
$changes[$type] = [
'added' => array_values($added),
'removed' => array_values($removed),
];
}
}
return $changes;
}
/**
* Determines whether removed associations should be synced to HubSpot.
*
* For contacts and companies, we always sync removals (original behavior).
* For deals, we only sync removals when:
* - There are new deals being added (replacement scenario), OR
* - The activity has an account associated (indicating intentional association management)
*
* This prevents accidentally removing deal associations that exist in HubSpot
* but not in Jiminny's database (e.g., deals manually added by users in HubSpot).
*/
private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool
{
// Contacts and companies: always sync removals
if ($type !== 'deal') {
return true;
}
// Deals: sync removals if we're adding new deals (replacement)
if (! empty($added)) {
return true;
}
// Deals: sync removals only if activity has an account (intentional management)
return $activity->hasAccount();
}
private function normalizeIds(array $ids): array
{
$stringIds = array_map('strval', $ids);
$uniqueIds = array_unique($stringIds);
sort($uniqueIds);
return $uniqueIds;
}
private function updateAssociations(Activity $activity, array $associationChanges): void
{
$objectType = $activity->isTypeConference() ? 'meeting' : 'call';
foreach ($associationChanges as $type => $changes) {
if (! empty($changes['added'])) {
$associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);
$payload = $this->payloadBuilder->buildAddAssociationPayload(
$activity->getCrmProviderId(),
$changes['added'],
$associationType
);
$this->getClient()->addAssociations($objectType, $type, $payload);
}
if (! empty($changes['removed'])) {
$payload = $this->payloadBuilder->buildRemoveAssociationPayload(
$activity->getCrmProviderId(),
$changes['removed']
);
$this->getClient()->removeAssociations($objectType, $type, $payload);
}
}
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response
{
// If already set by us/client, do not override it
if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {
$this->logger->info('[HubSpot] Meeting activity type already set', [
'meetingId' => $meetingId,
'activityType' => $engagement['activityType'],
]);
unset($engagement['activityType']);
}
$payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);
return $this->getClient()->updateMeeting($meetingId, $payload);
}
private function hasEngagementActivityType(string $engagementId): bool
{
try {
$engagementData = $this->getClient()->getEngagementData($engagementId);
if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {
return true;
}
return false;
} catch (\Exception $exception) {
Sentry::captureException($exception);
$this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [
'engagementId' => $engagementId,
'exception' => $exception->getMessage(),
]);
}
return true;
}
private function updateTask(Activity $activity, array $engagement, array $metadata): void
{
$this->getClient()->deleteEngagement($activity->getCrmProviderId());
$this->logCrmEngagementManipulation(
self::ACTION_DELETE,
['crmId' => $activity->getCrmProviderId()],
);
$this->createActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createActivity(Activity $activity, array $engagement, array $metadata): void
{
$associations = $this->getActivityAssociations($activity);
$associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);
if ($activity->isTypeConference()) {
try {
$hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);
$activityId = $hsEngagement->data->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
} else {
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
$activityId = $hsEngagement->data->engagement->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
}
}
private function convertActivityMeetingAssociationsV4(Activity $activity): array
{
$associations = [];
$contactIds = $this->getParticipantsIds($activity);
foreach ($contactIds as $contactId) {
$associations[] = $this->buildAssociation(
(int) $contactId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]
);
}
if ($activity->hasAccount()) {
$crmProviderId = $activity->getAccount()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]
);
}
}
if ($activity->hasOpportunity()) {
$crmProviderId = $activity->getOpportunity()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]
);
}
}
return $associations;
}
private function buildAssociation(int $id, array $categoryToTypeIds): array
{
$types = [];
foreach ($categoryToTypeIds as $category => $typeId) {
$types[] = $this->buildAssociationType($category, $typeId);
}
return [
'to' => ['id' => (string) $id],
'types' => $types,
];
}
private function buildAssociationType(string $category, int $typeId): array
{
return [
'associationCategory' => $category,
'associationTypeId' => $typeId,
];
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createMeetingV3(array $engagement, array $associations, array $metadata): Response
{
$payload = $this->payloadBuilder->getV3MeetingPayload(
engagement: $engagement,
metadata: $metadata,
associations: $associations
);
return $this->getClient()->createMeeting($payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
private function buildSoftphonePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'CALL',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'toNumber' => $decorator->formatCallToParticipant($activity),
'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),
'status' => $decorator->convertCallStatus($activity),
'durationMilliseconds' => (int) $activity->getDuration() * 1000,
'body' => $decorator->generateDescription($activity),
];
// For now, this is just a "disposition" field to store Call Outcome.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildTextMessagePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'NOTE',
];
$metadata = [
'body' => $decorator->generateDescription($activity),
];
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function getActivityAssociations(Activity $activity): array
{
return [
'contactIds' => $this->getParticipantsIds($activity),
'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],
'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],
'ownerIds' => [],
];
}
/**
* Get DecorateActivity instance. Can be overridden in tests for mocking.
*/
protected function getDecorateActivity(): DecorateActivity
{
return new DecorateActivity();
}
public function saveFollowupActivity(Activity $activity, array $fields): ?string
{
if (empty($fields['subject'])) {
return null;
}
$associations = $this->getActivityAssociations($activity);
if (empty($fields['timestamp']) === false) {
$formatTimestamp = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['timestamp']);
$timestamp = $formatTimestamp->getTimestamp();
} else {
$timestamp = strtotime('+1 day');
}
$reminder = null;
if (empty($fields['reminder']) === false) {
$formatReminder = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['reminder']);
$reminder = $formatReminder->getTimestamp() * 1000;
}
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $timestamp * 1000,
'type' => 'TASK',
];
$metadata = [
'subject' => $fields['subject'],
'body' => '',
'status' => 'NOT_STARTED',
'taskType' => 'CALL',
'reminders' => $reminder ? [$reminder] : [],
];
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
} catch (BadRequest $e) {
$this->logger->info('[HubSpot] Failed to create followup task', [
'activity' => $activity->getUuid(),
'engagement' => $engagement,
'metadata' => $metadata,
'error' => $e->getMessage(),
]);
return null;
}
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
// We don't actually create a corresponding activity object on our side yet.
return (string) $hsEngagement->data->engagement->id;
}
private function logCrmEngagementManipulation(
string $action,
array $engagementData,
array $data = [],
array $associations = [],
): void {
$payload = ['team' => $this->team->getUuid()];
if ($action === self::ACTION_CREATE) {
$payload['engagementData'] = $engagementData;
$payload['data'] = $data;
$payload['associations'] = $associations;
}
if ($action === self::ACTION_UPDATE) {
$payload['engagementId'] = $engagementData['crmId'];
$payload['data'] = $data;
}
if ($action === self::ACTION_DELETE) {
$payload['engagementId'] = $engagementData['crmId'];
}
$this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
8
1
3
4
Previous Highlighted Error
Next Highlighted Error
SELECT
# DISTINCT
opp.id as opp_id, opp.uuid, opp.name,
# COUNT(dr.id) AS deal_risk_count,
# dr.id,
# cfd.value,
usr.name AS owner_name,
opp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,
usr.uuid as owner_uuid,
usr.photo_path as owner_photo,
usr.id AS owner_id,
jt.name as owner_job,
opp.stage_id, opp.stage_updated_at,
acc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,
rt.business_process_id AS pipeline_id
FROM opportunities opp
LEFT JOIN record_types rt ON opp.record_type_id = rt.id
LEFT JOIN users usr ON opp.user_id = usr.id
LEFT JOIN accounts acc ON opp.account_id = acc.id
LEFT JOIN job_titles jt ON usr.job_title_id = jt.id
# LEFT JOIN group_deal_risk_types gdrt ON gdrt.group_id = usr.group_id
# LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id AND dr.group_deal_risk_type_id = gdrt.id
LEFT JOIN crm_field_data cfd ON (cfd.object_id = opp.id and cfd.crm_field_id = 66814)
WHERE opp.user_id IS NOT NULL
AND opp.deleted_at IS NULL
AND opp.is_closed = 1
AND opp.is_won = 0
AND opp.close_date >= '2024-01-01 00:00:00'
AND opp.close_date <= '2024-12-31 23:59:59'
AND usr.team_id = 1
# and opp.id = 4823179
GROUP BY opp.id, cfd.value
ORDER BY
# cfd.value
CAST(cfd.value AS UNSIGNED)
# owner_name
# (SELECT COUNT(dr.id)
# FROM deal_risks dr
# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
# WHERE dr.opportunity_id = opp.id
# AND dr.is_active = 1
# AND gdrt.is_enabled = 1)
DESC
LIMIT 25
# OFFSET 0
;
select * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';
select * from crm_layout_entities where crm_layout_id = 1493;
SELECT * FROM opportunities WHERE id = 4584045;
SELECT * FROM crm_field_data WHERE object_id = 4584045;
SELECT * FROM opportunities where team_id = 1 and crm_provider_id = '0061K00000lfb9IQAQ'
SELECT
opp.id as opportunity_id,
opp.name,
COUNT(dr.id) as risk_count
FROM opportunities opp
LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id
WHERE opp.id IN (
6069574,8900938,9507638,9524799,9660490,9662824,9705675,9749758,4158460,4391812,6450439,4448422,5118945,5590675,4584045,7228149,9002408,9165534,9446720,9641778,9665149,9703344,9709280,5747948,4158491,6182565,6263970,5798120,5111315,4536978,6062352,6548383,6072095,6548225,4480986,5011422,6548381,8760540,8917554,9509986,9514392,9569009,9569011,9578490,9713604,5784397,7276612,7288405,8994421,9118219,9608148,9818911,4510535,6479693,5958049,6271674,6550448,4158331,4158483,6126571,6171615,6540943,4897466,5190896,5796182,5932762,8572433,8723698,8892697,9711001,9789264,4549188,6100831,6170064,6260799,6263653,6449936,6530871,6538978,7777651,8059269,8319918,8787049,8901150,9263153,9453207,9514738,9696700,9791062,5752018,6421452,7439134,7878923,9354763,9369285,9514396,9582506,9889949,9890216,4094311,4158495,4158496,6098128,5585661,3872564,6442149,5891604,6164746,6199593,6583474,6519684,9018490,9809006,4496897,5041324,5829430,6198319,6253504,6555763,7242914,7931055,8024125,8797814,8058559,8673347,8892695,8994420,9616219,9714970,9722004,9809439,9818918,6523177,8134147,9002915,9711422,9892713,9901719,9954210,9978435,5800810,6243518,6416114,6222251,6411974,6512456,5791953,6545606,9914780,5805540,6238986,6463838,6547680,9767049,9809437,9810885,9890855,6673493,9902036,4335521,6379871,6503799,6546077,8018765,9907556,9958433,9905855,9916179,9946741,9957877,5563344,6271838,6450815,7641128,7762567,7780592,8684810,8685786,8685787,8685788
)
GROUP BY opp.id;
SELECT COUNT(dr.id)
FROM deal_risks dr
JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
WHERE dr.opportunity_id = 5563344
# AND gdrt.group_id = usr.group_id
EXPLAIN SELECT
opp.id as opp_id, opp.uuid, opp.name,
cfd.value,
cfv.sequence,
cfv.value,
# MAX(cfd.value) AS max_cfd_value,
usr.name AS owner_name,
opp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,
usr.uuid as owner_uuid,
usr.photo_path as owner_photo,
usr.id AS owner_id,
jt.name as owner_job,
opp.stage_id, opp.stage_updated_at,
acc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,
rt.business_process_id AS pipeline_id
FROM opportunities opp
LEFT JOIN record_types rt ON opp.record_type_id = rt.id
LEFT JOIN users usr ON opp.user_id = usr.id
LEFT JOIN accounts acc ON opp.account_id = acc.id
LEFT JOIN job_titles jt ON usr.job_title_id = jt.id
LEFT JOIN crm_field_data cfd ON cfd.id = (SELECT sub_cfd.id
FROM crm_field_data sub_cfd
WHERE sub_cfd.object_id = opp.id
AND sub_cfd.crm_field_id = 66810
ORDER BY sub_cfd.updated_at DESC
LIMIT 1)
LEFT JOIN crm_field_values cfv ON cfv.crm_field_id = 66810 AND cfv.value = cfd.value
WHERE opp.user_id IS NOT NULL
AND opp.deleted_at IS NULL
AND opp.is_closed = 0
# AND opp.is_closed = 1
# AND opp.is_won = 0
AND opp.close_date >= '2024-01-01 00:00:00'
AND opp.close_date <= '2024-12-31 23:59:59'
AND usr.team_id = 1
# and opp.id = 4823179
# GROUP BY opp.id
ORDER BY
cfv.sequence DESC,
# opp.name
# owner_name
cfd.value
# CAST(cfd.value AS UNSIGNED)
# CAST(MAX(cfd.value) AS UNSIGNED)
# (SELECT COUNT(dr.id)
# FROM deal_risks dr
# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
# WHERE dr.opportunity_id = opp.id
# AND dr.is_active = 1 AND gdrt.is_enabled = 1)
DESC
# LIMIT 75, 25
;
SELECT * FROM crm_field_values WHERE crm_field_id = 66810;
SELECT f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value, fd.updated_at, fd.id, fd.created_at
FROM crm_fields f
INNER JOIN crm_field_data fd ON fd.crm_field_id = f.id
WHERE (f.crm_configuration_id = 1)
AND (f.object_type = 'opportunity')
# AND (fd.object_id IN (4158331,4158483,4158495,4158496,6069574,6263970,6171615,6540943,6545606,5829430,6198319,6263653,6548381,7242914,8797814,7228149,7439134,7878923,8134147,9002915,9118219,9165534,9354763,9369285,9446720))
AND (fd.object_id IN (4158460))
AND (f.crm_provider_id IN ('ForecastCategoryName'))
ORDER BY fd.object_id ASC, fd.updated_at DESC;
select * from crm_layouts where crm_configuration_id = 1;
select cf.* from crm_fields cf
join crm_layout_entities cle on cf.id = cle.crm_field_id
where crm_layout_id = 1493;
SELECT * FROM opportunities WHERE id = 4158460;
SELECT * FROM crm_field_data WHERE object_id = 4158460;
select * from users where team_id = 1;
SELECT * FROM users WHERE id = 7160;
select * from role_user where user_id = 3248;
select * from crm_field_values where crm_field_id = 66824;
SELECT * FROM users WHERE id = 23470;
select * from crm_configurations;
# [PASSWORD_DOTS]
select * from teams where id = 1038; # 23521, 966
select * from users where team_id = 1038;
select * from crm_configurations where id = 966;
SELECT * FROM activities WHERE uuid_to_bin('e8dc7c34-31f6-4430-bd81-2d9d8f00dd07') = uuid; # 54965762
SELECT * FROM activities WHERE uuid_to_bin('4b5727c0-69d1-428f-b8d9-b649055166e2') = uuid; # 54965764
SELECT * FROM participants WHERE activity_id = 54965764;
SELECT id, uuid, title, recording_state FROM activities WHERE uuid_to_bin('355f105d-5606-4379-b0c5-d91daf59f4ce') = uuid; # 54964075
SELECT * FROM participants WHERE activity_id = 54964075;
select f.id, slug, title, tf.team_id from team_features tf JOIN features f on tf.feature_id = f.id
where tf.team_id = 1038;
# [PASSWORD_DOTS] PD [PASSWORD_DOTS]
select * from teams where id = 1029;
SELECT * FROM crm_configurations WHERE id = 957;
SELECT * FROM activities WHERE uuid_to_bin('581a33cb-4343-44bd-ac0c-4a5cee71c5ec') = uuid; # 54963423
SELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433
SELECT * FROM activities WHERE uuid_to_bin('8b6bb2f7-e7a7-4aec-8806-fbfea7093ccd') = uuid; # 54963421
# [PASSWORD_DOTS] Close [PASSWORD_DOTS]
select * from teams where id = 1031;
SELECT * FROM crm_configurations WHERE id = 959;
SELECT * FROM activities WHERE uuid_to_bin('59c517e0-96c9-4dad-bfeb-b7bef72f8725') = uuid; # 54963517
SELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433
SELECT * FROM activities WHERE uuid_to_bin('d68311e3-ac34-49bd-bf04-88323a7e5352') = uuid; # 54966009
# [PASSWORD_DOTS] SF [PASSWORD_DOTS]
SELECT * FROM activities WHERE uuid_to_bin('2bd4cfa3-4eb5-4c77-84a7-9ed46a21c988') = uuid;
select * from roles;
select * from permissions;
select * from permission_role where permission_id = 136;
select * from migrations order by id desc;
select * from teams where id IN (1, 1037);
select * from crm_layouts where crm_configuration_id = 1;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 1493;
SELECT * FROM crm_fields WHERE id IN (1652,1661,66799,66814,66821,66836,66843,66846,66864,84752,182306,323580,380378);
select * from features;
select * from team_features where feature_id = 33;
select * from opportunities;
select * from teams;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1052 and sa.provider = 'hubspot';
SELECT * FROM accounts where id = 11512582;
select * from activities where account_id = 11512582;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.2962101,"top":1.0,"width":0.03856383,"height":-0.019952059},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#12066 on JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.33477393,"top":1.0,"width":0.122340426,"height":-0.019952059},"on_screen":true,"help_text":"Pull request #12066 exists for current branch JY-20725-handle-HS-search-rate-limit, 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.5731383,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HandleHubspotRateLimitTest","depth":6,"bounds":{"left":0.5884308,"top":1.0,"width":0.076130316,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.66456115,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.67586434,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.6871675,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.7150931,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.72639626,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.73769945,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"8","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldDataRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DecorateActivity;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationCategoryV4;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationTypeIdV4;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse Sentry;\n\ntrait WriteCrmTrait\n{\n private const string ACTION_CREATE = 'create';\n private const string ACTION_UPDATE = 'update';\n private const string ACTION_DELETE = 'delete';\n\n /**\n * @inheritdoc\n */\n public function saveActivity(Activity $activity): Activity\n {\n switch ($activity->getCrmType()) {\n case Activity::TYPE_CONFERENCE:\n $activity = $this->buildConferencePayload($activity);\n\n break;\n case Activity::TYPE_SOFTPHONE:\n case Activity::TYPE_SOFTPHONE_INBOUND:\n $activity = $this->buildSoftphonePayload($activity);\n\n break;\n\n case Activity::TYPE_SMS_INBOUND:\n case Activity::TYPE_SMS_OUTBOUND:\n $activity = $this->buildTextMessagePayload($activity);\n\n break;\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildConferencePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatStartTime($activity),\n 'type' => 'MEETING',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'meetingOutcome' => $decorator->convertMeetingOutcome($activity),\n 'internalMeetingNotes' => $decorator->generateDescription($activity),\n ];\n\n // Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.\n if (! $activity->hasCrmProviderId()) {\n if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {\n $metadata['startTime'] = $decorator->formatStartTime($activity);\n $metadata['endTime'] = $decorator->formatEndTime($activity);\n }\n $metadata['title'] = $decorator->generateTitle($activity);\n }\n\n // For now, this is just a \"meetingOutcome\" field for some customers.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function fetchCustomFieldData(Activity $activity, string $objectType): array\n {\n $payload = [];\n\n $fieldDataRepository = app(FieldDataRepository::class);\n $fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);\n\n foreach ($fieldData as $data) {\n // Add the field and value to the payload.\n $payload += [\n $data->getField()->getCrmProviderId() => $data->getValue(),\n ];\n }\n\n return $payload;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity\n {\n if (! $activity->hasCrmProviderId()) {\n $this->createActivity($activity, $engagement, $metadata);\n\n return $activity;\n }\n\n try {\n $oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());\n } catch (HubspotException $e) {\n $this->logger->warning('[HubSpot] Engagement not found', [\n 'activity_id' => $activity->getUuid(),\n 'crm_provider_id' => $activity->getCrmProviderId(),\n 'exception' => $e->getMessage(),\n ]);\n\n // potentially here we can log new activity if it is not found\n\n return $activity;\n }\n\n $type = $oldEngagement['engagement']['type'] ?? null;\n $bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';\n\n $decorator = $this->getDecorateActivity();\n\n if ($type !== 'TASK') {\n $oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldDescription\n );\n\n $oldAssociations = $oldEngagement['associations'] ?? [];\n $this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);\n } else {\n $oldTaskBody = $oldEngagement['metadata']['body'] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldTaskBody\n );\n $this->updateTask($activity, $engagement, $metadata);\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateNonTaskActivity(\n Activity $activity,\n array $engagement,\n array $metadata,\n array $oldAssociations\n ): void {\n if ($activity->isTypeConference()) {\n $this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);\n } else {\n $this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);\n }\n\n $this->updateActivityAssociations($activity, $oldAssociations);\n\n $this->logCrmEngagementManipulation(\n self::ACTION_UPDATE,\n ['crmId' => $activity->getCrmProviderId()],\n $metadata,\n );\n }\n\n private function updateActivityAssociations(Activity $activity, array $oldAssociations): void\n {\n $currentAssociations = $this->getActivityAssociations($activity);\n $associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);\n\n // No changes - exit\n if (empty($associationChanges)) {\n return;\n }\n\n $this->logger->info('[HubSpot] Updating associations', [\n 'team' => $this->team->getUuid(),\n 'activity' => $activity->getUuid(),\n 'associationChanges' => $associationChanges,\n ]);\n\n $this->updateAssociations($activity, $associationChanges);\n }\n\n private function getAssociationDifferences(\n Activity $activity,\n array $oldAssociations,\n array $currentAssociations\n ): array {\n $objects = ['contactIds', 'companyIds', 'dealIds'];\n $changes = [];\n\n foreach ($objects as $object) {\n $oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);\n $currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);\n\n $added = array_diff($currentIds, $oldIds);\n $removed = array_diff($oldIds, $currentIds);\n\n $type = match ($object) {\n 'contactIds' => 'contact',\n 'companyIds' => 'company',\n 'dealIds' => 'deal',\n };\n\n $shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);\n\n if (! empty($removed) && ! $shouldIncludeRemoved) {\n $this->logger->info('[HubSpot] Skipping association removal', [\n 'team' => $this->team->getUuid(),\n 'type' => $type,\n 'oldIds' => $oldIds,\n 'currentIds' => $currentIds,\n 'skippedRemovals' => array_values($removed),\n ]);\n }\n\n if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {\n $changes[$type] = [\n 'added' => array_values($added),\n 'removed' => array_values($removed),\n ];\n }\n }\n\n return $changes;\n }\n\n /**\n * Determines whether removed associations should be synced to HubSpot.\n *\n * For contacts and companies, we always sync removals (original behavior).\n * For deals, we only sync removals when:\n * - There are new deals being added (replacement scenario), OR\n * - The activity has an account associated (indicating intentional association management)\n *\n * This prevents accidentally removing deal associations that exist in HubSpot\n * but not in Jiminny's database (e.g., deals manually added by users in HubSpot).\n */\n private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool\n {\n // Contacts and companies: always sync removals\n if ($type !== 'deal') {\n return true;\n }\n\n // Deals: sync removals if we're adding new deals (replacement)\n if (! empty($added)) {\n return true;\n }\n\n // Deals: sync removals only if activity has an account (intentional management)\n return $activity->hasAccount();\n }\n\n private function normalizeIds(array $ids): array\n {\n $stringIds = array_map('strval', $ids);\n $uniqueIds = array_unique($stringIds);\n sort($uniqueIds);\n\n return $uniqueIds;\n }\n\n private function updateAssociations(Activity $activity, array $associationChanges): void\n {\n $objectType = $activity->isTypeConference() ? 'meeting' : 'call';\n\n foreach ($associationChanges as $type => $changes) {\n if (! empty($changes['added'])) {\n $associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);\n $payload = $this->payloadBuilder->buildAddAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['added'],\n $associationType\n );\n $this->getClient()->addAssociations($objectType, $type, $payload);\n }\n\n if (! empty($changes['removed'])) {\n $payload = $this->payloadBuilder->buildRemoveAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['removed']\n );\n $this->getClient()->removeAssociations($objectType, $type, $payload);\n }\n }\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response\n {\n // If already set by us/client, do not override it\n if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {\n $this->logger->info('[HubSpot] Meeting activity type already set', [\n 'meetingId' => $meetingId,\n 'activityType' => $engagement['activityType'],\n ]);\n\n unset($engagement['activityType']);\n }\n\n $payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);\n\n return $this->getClient()->updateMeeting($meetingId, $payload);\n }\n\n private function hasEngagementActivityType(string $engagementId): bool\n {\n try {\n $engagementData = $this->getClient()->getEngagementData($engagementId);\n if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {\n return true;\n }\n\n return false;\n } catch (\\Exception $exception) {\n Sentry::captureException($exception);\n\n $this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [\n 'engagementId' => $engagementId,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n return true;\n }\n\n private function updateTask(Activity $activity, array $engagement, array $metadata): void\n {\n $this->getClient()->deleteEngagement($activity->getCrmProviderId());\n\n $this->logCrmEngagementManipulation(\n self::ACTION_DELETE,\n ['crmId' => $activity->getCrmProviderId()],\n );\n\n $this->createActivity($activity, $engagement, $metadata);\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createActivity(Activity $activity, array $engagement, array $metadata): void\n {\n $associations = $this->getActivityAssociations($activity);\n $associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);\n\n if ($activity->isTypeConference()) {\n try {\n $hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);\n $activityId = $hsEngagement->data->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n } else {\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n $activityId = $hsEngagement->data->engagement->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n }\n }\n\n private function convertActivityMeetingAssociationsV4(Activity $activity): array\n {\n $associations = [];\n\n $contactIds = $this->getParticipantsIds($activity);\n\n foreach ($contactIds as $contactId) {\n $associations[] = $this->buildAssociation(\n (int) $contactId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]\n );\n }\n\n if ($activity->hasAccount()) {\n $crmProviderId = $activity->getAccount()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]\n );\n }\n }\n\n if ($activity->hasOpportunity()) {\n $crmProviderId = $activity->getOpportunity()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]\n );\n }\n }\n\n return $associations;\n }\n\n private function buildAssociation(int $id, array $categoryToTypeIds): array\n {\n $types = [];\n\n foreach ($categoryToTypeIds as $category => $typeId) {\n $types[] = $this->buildAssociationType($category, $typeId);\n }\n\n return [\n 'to' => ['id' => (string) $id],\n 'types' => $types,\n ];\n }\n\n private function buildAssociationType(string $category, int $typeId): array\n {\n return [\n 'associationCategory' => $category,\n 'associationTypeId' => $typeId,\n ];\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createMeetingV3(array $engagement, array $associations, array $metadata): Response\n {\n $payload = $this->payloadBuilder->getV3MeetingPayload(\n engagement: $engagement,\n metadata: $metadata,\n associations: $associations\n );\n\n return $this->getClient()->createMeeting($payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n private function buildSoftphonePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'CALL',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'toNumber' => $decorator->formatCallToParticipant($activity),\n 'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),\n 'status' => $decorator->convertCallStatus($activity),\n 'durationMilliseconds' => (int) $activity->getDuration() * 1000,\n 'body' => $decorator->generateDescription($activity),\n ];\n\n // For now, this is just a \"disposition\" field to store Call Outcome.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildTextMessagePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'NOTE',\n ];\n\n $metadata = [\n 'body' => $decorator->generateDescription($activity),\n ];\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function getActivityAssociations(Activity $activity): array\n {\n return [\n 'contactIds' => $this->getParticipantsIds($activity),\n 'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],\n 'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],\n 'ownerIds' => [],\n ];\n }\n\n /**\n * Get DecorateActivity instance. Can be overridden in tests for mocking.\n */\n protected function getDecorateActivity(): DecorateActivity\n {\n return new DecorateActivity();\n }\n\n public function saveFollowupActivity(Activity $activity, array $fields): ?string\n {\n if (empty($fields['subject'])) {\n return null;\n }\n\n $associations = $this->getActivityAssociations($activity);\n\n if (empty($fields['timestamp']) === false) {\n $formatTimestamp = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['timestamp']);\n $timestamp = $formatTimestamp->getTimestamp();\n } else {\n $timestamp = strtotime('+1 day');\n }\n\n $reminder = null;\n if (empty($fields['reminder']) === false) {\n $formatReminder = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['reminder']);\n $reminder = $formatReminder->getTimestamp() * 1000;\n }\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $timestamp * 1000,\n 'type' => 'TASK',\n ];\n\n $metadata = [\n 'subject' => $fields['subject'],\n 'body' => '',\n 'status' => 'NOT_STARTED',\n 'taskType' => 'CALL',\n 'reminders' => $reminder ? [$reminder] : [],\n ];\n\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n } catch (BadRequest $e) {\n $this->logger->info('[HubSpot] Failed to create followup task', [\n 'activity' => $activity->getUuid(),\n 'engagement' => $engagement,\n 'metadata' => $metadata,\n 'error' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n // We don't actually create a corresponding activity object on our side yet.\n return (string) $hsEngagement->data->engagement->id;\n }\n\n private function logCrmEngagementManipulation(\n string $action,\n array $engagementData,\n array $data = [],\n array $associations = [],\n ): void {\n $payload = ['team' => $this->team->getUuid()];\n\n if ($action === self::ACTION_CREATE) {\n $payload['engagementData'] = $engagementData;\n $payload['data'] = $data;\n $payload['associations'] = $associations;\n }\n\n if ($action === self::ACTION_UPDATE) {\n $payload['engagementId'] = $engagementData['crmId'];\n $payload['data'] = $data;\n }\n\n if ($action === self::ACTION_DELETE) {\n $payload['engagementId'] = $engagementData['crmId'];\n }\n\n $this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);\n }\n}","depth":4,"bounds":{"left":0.39162233,"top":0.41340783,"width":0.31615692,"height":0.5865922},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldDataRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DecorateActivity;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationCategoryV4;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationTypeIdV4;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse Sentry;\n\ntrait WriteCrmTrait\n{\n private const string ACTION_CREATE = 'create';\n private const string ACTION_UPDATE = 'update';\n private const string ACTION_DELETE = 'delete';\n\n /**\n * @inheritdoc\n */\n public function saveActivity(Activity $activity): Activity\n {\n switch ($activity->getCrmType()) {\n case Activity::TYPE_CONFERENCE:\n $activity = $this->buildConferencePayload($activity);\n\n break;\n case Activity::TYPE_SOFTPHONE:\n case Activity::TYPE_SOFTPHONE_INBOUND:\n $activity = $this->buildSoftphonePayload($activity);\n\n break;\n\n case Activity::TYPE_SMS_INBOUND:\n case Activity::TYPE_SMS_OUTBOUND:\n $activity = $this->buildTextMessagePayload($activity);\n\n break;\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildConferencePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatStartTime($activity),\n 'type' => 'MEETING',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'meetingOutcome' => $decorator->convertMeetingOutcome($activity),\n 'internalMeetingNotes' => $decorator->generateDescription($activity),\n ];\n\n // Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.\n if (! $activity->hasCrmProviderId()) {\n if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {\n $metadata['startTime'] = $decorator->formatStartTime($activity);\n $metadata['endTime'] = $decorator->formatEndTime($activity);\n }\n $metadata['title'] = $decorator->generateTitle($activity);\n }\n\n // For now, this is just a \"meetingOutcome\" field for some customers.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function fetchCustomFieldData(Activity $activity, string $objectType): array\n {\n $payload = [];\n\n $fieldDataRepository = app(FieldDataRepository::class);\n $fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);\n\n foreach ($fieldData as $data) {\n // Add the field and value to the payload.\n $payload += [\n $data->getField()->getCrmProviderId() => $data->getValue(),\n ];\n }\n\n return $payload;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity\n {\n if (! $activity->hasCrmProviderId()) {\n $this->createActivity($activity, $engagement, $metadata);\n\n return $activity;\n }\n\n try {\n $oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());\n } catch (HubspotException $e) {\n $this->logger->warning('[HubSpot] Engagement not found', [\n 'activity_id' => $activity->getUuid(),\n 'crm_provider_id' => $activity->getCrmProviderId(),\n 'exception' => $e->getMessage(),\n ]);\n\n // potentially here we can log new activity if it is not found\n\n return $activity;\n }\n\n $type = $oldEngagement['engagement']['type'] ?? null;\n $bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';\n\n $decorator = $this->getDecorateActivity();\n\n if ($type !== 'TASK') {\n $oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldDescription\n );\n\n $oldAssociations = $oldEngagement['associations'] ?? [];\n $this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);\n } else {\n $oldTaskBody = $oldEngagement['metadata']['body'] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldTaskBody\n );\n $this->updateTask($activity, $engagement, $metadata);\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateNonTaskActivity(\n Activity $activity,\n array $engagement,\n array $metadata,\n array $oldAssociations\n ): void {\n if ($activity->isTypeConference()) {\n $this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);\n } else {\n $this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);\n }\n\n $this->updateActivityAssociations($activity, $oldAssociations);\n\n $this->logCrmEngagementManipulation(\n self::ACTION_UPDATE,\n ['crmId' => $activity->getCrmProviderId()],\n $metadata,\n );\n }\n\n private function updateActivityAssociations(Activity $activity, array $oldAssociations): void\n {\n $currentAssociations = $this->getActivityAssociations($activity);\n $associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);\n\n // No changes - exit\n if (empty($associationChanges)) {\n return;\n }\n\n $this->logger->info('[HubSpot] Updating associations', [\n 'team' => $this->team->getUuid(),\n 'activity' => $activity->getUuid(),\n 'associationChanges' => $associationChanges,\n ]);\n\n $this->updateAssociations($activity, $associationChanges);\n }\n\n private function getAssociationDifferences(\n Activity $activity,\n array $oldAssociations,\n array $currentAssociations\n ): array {\n $objects = ['contactIds', 'companyIds', 'dealIds'];\n $changes = [];\n\n foreach ($objects as $object) {\n $oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);\n $currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);\n\n $added = array_diff($currentIds, $oldIds);\n $removed = array_diff($oldIds, $currentIds);\n\n $type = match ($object) {\n 'contactIds' => 'contact',\n 'companyIds' => 'company',\n 'dealIds' => 'deal',\n };\n\n $shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);\n\n if (! empty($removed) && ! $shouldIncludeRemoved) {\n $this->logger->info('[HubSpot] Skipping association removal', [\n 'team' => $this->team->getUuid(),\n 'type' => $type,\n 'oldIds' => $oldIds,\n 'currentIds' => $currentIds,\n 'skippedRemovals' => array_values($removed),\n ]);\n }\n\n if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {\n $changes[$type] = [\n 'added' => array_values($added),\n 'removed' => array_values($removed),\n ];\n }\n }\n\n return $changes;\n }\n\n /**\n * Determines whether removed associations should be synced to HubSpot.\n *\n * For contacts and companies, we always sync removals (original behavior).\n * For deals, we only sync removals when:\n * - There are new deals being added (replacement scenario), OR\n * - The activity has an account associated (indicating intentional association management)\n *\n * This prevents accidentally removing deal associations that exist in HubSpot\n * but not in Jiminny's database (e.g., deals manually added by users in HubSpot).\n */\n private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool\n {\n // Contacts and companies: always sync removals\n if ($type !== 'deal') {\n return true;\n }\n\n // Deals: sync removals if we're adding new deals (replacement)\n if (! empty($added)) {\n return true;\n }\n\n // Deals: sync removals only if activity has an account (intentional management)\n return $activity->hasAccount();\n }\n\n private function normalizeIds(array $ids): array\n {\n $stringIds = array_map('strval', $ids);\n $uniqueIds = array_unique($stringIds);\n sort($uniqueIds);\n\n return $uniqueIds;\n }\n\n private function updateAssociations(Activity $activity, array $associationChanges): void\n {\n $objectType = $activity->isTypeConference() ? 'meeting' : 'call';\n\n foreach ($associationChanges as $type => $changes) {\n if (! empty($changes['added'])) {\n $associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);\n $payload = $this->payloadBuilder->buildAddAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['added'],\n $associationType\n );\n $this->getClient()->addAssociations($objectType, $type, $payload);\n }\n\n if (! empty($changes['removed'])) {\n $payload = $this->payloadBuilder->buildRemoveAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['removed']\n );\n $this->getClient()->removeAssociations($objectType, $type, $payload);\n }\n }\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response\n {\n // If already set by us/client, do not override it\n if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {\n $this->logger->info('[HubSpot] Meeting activity type already set', [\n 'meetingId' => $meetingId,\n 'activityType' => $engagement['activityType'],\n ]);\n\n unset($engagement['activityType']);\n }\n\n $payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);\n\n return $this->getClient()->updateMeeting($meetingId, $payload);\n }\n\n private function hasEngagementActivityType(string $engagementId): bool\n {\n try {\n $engagementData = $this->getClient()->getEngagementData($engagementId);\n if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {\n return true;\n }\n\n return false;\n } catch (\\Exception $exception) {\n Sentry::captureException($exception);\n\n $this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [\n 'engagementId' => $engagementId,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n return true;\n }\n\n private function updateTask(Activity $activity, array $engagement, array $metadata): void\n {\n $this->getClient()->deleteEngagement($activity->getCrmProviderId());\n\n $this->logCrmEngagementManipulation(\n self::ACTION_DELETE,\n ['crmId' => $activity->getCrmProviderId()],\n );\n\n $this->createActivity($activity, $engagement, $metadata);\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createActivity(Activity $activity, array $engagement, array $metadata): void\n {\n $associations = $this->getActivityAssociations($activity);\n $associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);\n\n if ($activity->isTypeConference()) {\n try {\n $hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);\n $activityId = $hsEngagement->data->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n } else {\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n $activityId = $hsEngagement->data->engagement->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n }\n }\n\n private function convertActivityMeetingAssociationsV4(Activity $activity): array\n {\n $associations = [];\n\n $contactIds = $this->getParticipantsIds($activity);\n\n foreach ($contactIds as $contactId) {\n $associations[] = $this->buildAssociation(\n (int) $contactId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]\n );\n }\n\n if ($activity->hasAccount()) {\n $crmProviderId = $activity->getAccount()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]\n );\n }\n }\n\n if ($activity->hasOpportunity()) {\n $crmProviderId = $activity->getOpportunity()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]\n );\n }\n }\n\n return $associations;\n }\n\n private function buildAssociation(int $id, array $categoryToTypeIds): array\n {\n $types = [];\n\n foreach ($categoryToTypeIds as $category => $typeId) {\n $types[] = $this->buildAssociationType($category, $typeId);\n }\n\n return [\n 'to' => ['id' => (string) $id],\n 'types' => $types,\n ];\n }\n\n private function buildAssociationType(string $category, int $typeId): array\n {\n return [\n 'associationCategory' => $category,\n 'associationTypeId' => $typeId,\n ];\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createMeetingV3(array $engagement, array $associations, array $metadata): Response\n {\n $payload = $this->payloadBuilder->getV3MeetingPayload(\n engagement: $engagement,\n metadata: $metadata,\n associations: $associations\n );\n\n return $this->getClient()->createMeeting($payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n private function buildSoftphonePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'CALL',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'toNumber' => $decorator->formatCallToParticipant($activity),\n 'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),\n 'status' => $decorator->convertCallStatus($activity),\n 'durationMilliseconds' => (int) $activity->getDuration() * 1000,\n 'body' => $decorator->generateDescription($activity),\n ];\n\n // For now, this is just a \"disposition\" field to store Call Outcome.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildTextMessagePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'NOTE',\n ];\n\n $metadata = [\n 'body' => $decorator->generateDescription($activity),\n ];\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function getActivityAssociations(Activity $activity): array\n {\n return [\n 'contactIds' => $this->getParticipantsIds($activity),\n 'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],\n 'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],\n 'ownerIds' => [],\n ];\n }\n\n /**\n * Get DecorateActivity instance. Can be overridden in tests for mocking.\n */\n protected function getDecorateActivity(): DecorateActivity\n {\n return new DecorateActivity();\n }\n\n public function saveFollowupActivity(Activity $activity, array $fields): ?string\n {\n if (empty($fields['subject'])) {\n return null;\n }\n\n $associations = $this->getActivityAssociations($activity);\n\n if (empty($fields['timestamp']) === false) {\n $formatTimestamp = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['timestamp']);\n $timestamp = $formatTimestamp->getTimestamp();\n } else {\n $timestamp = strtotime('+1 day');\n }\n\n $reminder = null;\n if (empty($fields['reminder']) === false) {\n $formatReminder = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['reminder']);\n $reminder = $formatReminder->getTimestamp() * 1000;\n }\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $timestamp * 1000,\n 'type' => 'TASK',\n ];\n\n $metadata = [\n 'subject' => $fields['subject'],\n 'body' => '',\n 'status' => 'NOT_STARTED',\n 'taskType' => 'CALL',\n 'reminders' => $reminder ? [$reminder] : [],\n ];\n\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n } catch (BadRequest $e) {\n $this->logger->info('[HubSpot] Failed to create followup task', [\n 'activity' => $activity->getUuid(),\n 'engagement' => $engagement,\n 'metadata' => $metadata,\n 'error' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n // We don't actually create a corresponding activity object on our side yet.\n return (string) $hsEngagement->data->engagement->id;\n }\n\n private function logCrmEngagementManipulation(\n string $action,\n array $engagementData,\n array $data = [],\n array $associations = [],\n ): void {\n $payload = ['team' => $this->team->getUuid()];\n\n if ($action === self::ACTION_CREATE) {\n $payload['engagementData'] = $engagementData;\n $payload['data'] = $data;\n $payload['associations'] = $associations;\n }\n\n if ($action === self::ACTION_UPDATE) {\n $payload['engagementId'] = $engagementData['crmId'];\n $payload['data'] = $data;\n }\n\n if ($action === self::ACTION_DELETE) {\n $payload['engagementId'] = $engagementData['crmId'];\n }\n\n $this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"8","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"4","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"SELECT\n# DISTINCT\nopp.id as opp_id, opp.uuid, opp.name,\n# COUNT(dr.id) AS deal_risk_count,\n\n# dr.id,\n# cfd.value,\nusr.name AS owner_name,\nopp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,\nusr.uuid as owner_uuid,\nusr.photo_path as owner_photo,\nusr.id AS owner_id,\njt.name as owner_job,\nopp.stage_id, opp.stage_updated_at,\nacc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,\nrt.business_process_id AS pipeline_id\nFROM opportunities opp\nLEFT JOIN record_types rt ON opp.record_type_id = rt.id\nLEFT JOIN users usr ON opp.user_id = usr.id\nLEFT JOIN accounts acc ON opp.account_id = acc.id\nLEFT JOIN job_titles jt ON usr.job_title_id = jt.id\n\n# LEFT JOIN group_deal_risk_types gdrt ON gdrt.group_id = usr.group_id\n# LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id AND dr.group_deal_risk_type_id = gdrt.id\n\nLEFT JOIN crm_field_data cfd ON (cfd.object_id = opp.id and cfd.crm_field_id = 66814)\n\nWHERE opp.user_id IS NOT NULL\nAND opp.deleted_at IS NULL\nAND opp.is_closed = 1\nAND opp.is_won = 0\nAND opp.close_date >= '2024-01-01 00:00:00'\nAND opp.close_date <= '2024-12-31 23:59:59'\nAND usr.team_id = 1\n\n# and opp.id = 4823179\n\nGROUP BY opp.id, cfd.value\nORDER BY\n# cfd.value\n CAST(cfd.value AS UNSIGNED)\n# owner_name\n# (SELECT COUNT(dr.id)\n# FROM deal_risks dr\n# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n# WHERE dr.opportunity_id = opp.id\n# AND dr.is_active = 1\n# AND gdrt.is_enabled = 1)\nDESC\nLIMIT 25\n# OFFSET 0\n;\n\nselect * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';\nselect * from crm_layout_entities where crm_layout_id = 1493;\n\nSELECT * FROM opportunities WHERE id = 4584045;\nSELECT * FROM crm_field_data WHERE object_id = 4584045;\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id = '0061K00000lfb9IQAQ'\n\nSELECT\n opp.id as opportunity_id,\n opp.name,\n COUNT(dr.id) as risk_count\nFROM opportunities opp\nLEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id\nWHERE opp.id IN (\n 6069574,8900938,9507638,9524799,9660490,9662824,9705675,9749758,4158460,4391812,6450439,4448422,5118945,5590675,4584045,7228149,9002408,9165534,9446720,9641778,9665149,9703344,9709280,5747948,4158491,6182565,6263970,5798120,5111315,4536978,6062352,6548383,6072095,6548225,4480986,5011422,6548381,8760540,8917554,9509986,9514392,9569009,9569011,9578490,9713604,5784397,7276612,7288405,8994421,9118219,9608148,9818911,4510535,6479693,5958049,6271674,6550448,4158331,4158483,6126571,6171615,6540943,4897466,5190896,5796182,5932762,8572433,8723698,8892697,9711001,9789264,4549188,6100831,6170064,6260799,6263653,6449936,6530871,6538978,7777651,8059269,8319918,8787049,8901150,9263153,9453207,9514738,9696700,9791062,5752018,6421452,7439134,7878923,9354763,9369285,9514396,9582506,9889949,9890216,4094311,4158495,4158496,6098128,5585661,3872564,6442149,5891604,6164746,6199593,6583474,6519684,9018490,9809006,4496897,5041324,5829430,6198319,6253504,6555763,7242914,7931055,8024125,8797814,8058559,8673347,8892695,8994420,9616219,9714970,9722004,9809439,9818918,6523177,8134147,9002915,9711422,9892713,9901719,9954210,9978435,5800810,6243518,6416114,6222251,6411974,6512456,5791953,6545606,9914780,5805540,6238986,6463838,6547680,9767049,9809437,9810885,9890855,6673493,9902036,4335521,6379871,6503799,6546077,8018765,9907556,9958433,9905855,9916179,9946741,9957877,5563344,6271838,6450815,7641128,7762567,7780592,8684810,8685786,8685787,8685788\n)\nGROUP BY opp.id;\n\nSELECT COUNT(dr.id)\n FROM deal_risks dr\n JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n WHERE dr.opportunity_id = 5563344\n# AND gdrt.group_id = usr.group_id\n\n\n\nEXPLAIN SELECT\n opp.id as opp_id, opp.uuid, opp.name,\n\ncfd.value,\ncfv.sequence,\ncfv.value,\n# MAX(cfd.value) AS max_cfd_value,\nusr.name AS owner_name,\nopp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,\nusr.uuid as owner_uuid,\nusr.photo_path as owner_photo,\nusr.id AS owner_id,\njt.name as owner_job,\nopp.stage_id, opp.stage_updated_at,\nacc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,\nrt.business_process_id AS pipeline_id\nFROM opportunities opp\n\nLEFT JOIN record_types rt ON opp.record_type_id = rt.id\nLEFT JOIN users usr ON opp.user_id = usr.id\nLEFT JOIN accounts acc ON opp.account_id = acc.id\nLEFT JOIN job_titles jt ON usr.job_title_id = jt.id\n\nLEFT JOIN crm_field_data cfd ON cfd.id = (SELECT sub_cfd.id\n FROM crm_field_data sub_cfd\n WHERE sub_cfd.object_id = opp.id\n AND sub_cfd.crm_field_id = 66810\n ORDER BY sub_cfd.updated_at DESC\n LIMIT 1)\n\nLEFT JOIN crm_field_values cfv ON cfv.crm_field_id = 66810 AND cfv.value = cfd.value\n\nWHERE opp.user_id IS NOT NULL\nAND opp.deleted_at IS NULL\nAND opp.is_closed = 0\n# AND opp.is_closed = 1\n# AND opp.is_won = 0\nAND opp.close_date >= '2024-01-01 00:00:00'\nAND opp.close_date <= '2024-12-31 23:59:59'\nAND usr.team_id = 1\n\n# and opp.id = 4823179\n\n# GROUP BY opp.id\nORDER BY\n cfv.sequence DESC,\n# opp.name\n# owner_name\n cfd.value\n# CAST(cfd.value AS UNSIGNED)\n# CAST(MAX(cfd.value) AS UNSIGNED)\n# (SELECT COUNT(dr.id)\n# FROM deal_risks dr\n# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n# WHERE dr.opportunity_id = opp.id\n# AND dr.is_active = 1 AND gdrt.is_enabled = 1)\nDESC\n# LIMIT 75, 25\n;\n\n\n\nSELECT * FROM crm_field_values WHERE crm_field_id = 66810;\n\nSELECT f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value, fd.updated_at, fd.id, fd.created_at\nFROM crm_fields f\nINNER JOIN crm_field_data fd ON fd.crm_field_id = f.id\n WHERE (f.crm_configuration_id = 1)\n AND (f.object_type = 'opportunity')\n# AND (fd.object_id IN (4158331,4158483,4158495,4158496,6069574,6263970,6171615,6540943,6545606,5829430,6198319,6263653,6548381,7242914,8797814,7228149,7439134,7878923,8134147,9002915,9118219,9165534,9354763,9369285,9446720))\n AND (fd.object_id IN (4158460))\n AND (f.crm_provider_id IN ('ForecastCategoryName'))\n ORDER BY fd.object_id ASC, fd.updated_at DESC;\n\nselect * from crm_layouts where crm_configuration_id = 1;\nselect cf.* from crm_fields cf\njoin crm_layout_entities cle on cf.id = cle.crm_field_id\nwhere crm_layout_id = 1493;\n\nSELECT * FROM opportunities WHERE id = 4158460;\nSELECT * FROM crm_field_data WHERE object_id = 4158460;\n\nselect * from users where team_id = 1;\nSELECT * FROM users WHERE id = 7160;\nselect * from role_user where user_id = 3248;\n\nselect * from crm_field_values where crm_field_id = 66824;\n\nSELECT * FROM users WHERE id = 23470;\nselect * from crm_configurations;\n\n# ******************************************\nselect * from teams where id = 1038; # 23521, 966\nselect * from users where team_id = 1038;\nselect * from crm_configurations where id = 966;\nSELECT * FROM activities WHERE uuid_to_bin('e8dc7c34-31f6-4430-bd81-2d9d8f00dd07') = uuid; # 54965762\nSELECT * FROM activities WHERE uuid_to_bin('4b5727c0-69d1-428f-b8d9-b649055166e2') = uuid; # 54965764\nSELECT * FROM participants WHERE activity_id = 54965764;\n\nSELECT id, uuid, title, recording_state FROM activities WHERE uuid_to_bin('355f105d-5606-4379-b0c5-d91daf59f4ce') = uuid; # 54964075\nSELECT * FROM participants WHERE activity_id = 54964075;\nselect f.id, slug, title, tf.team_id from team_features tf JOIN features f on tf.feature_id = f.id\n where tf.team_id = 1038;\n\n\n# ****************************************** PD *************************\nselect * from teams where id = 1029;\nSELECT * FROM crm_configurations WHERE id = 957;\nSELECT * FROM activities WHERE uuid_to_bin('581a33cb-4343-44bd-ac0c-4a5cee71c5ec') = uuid; # 54963423\nSELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433\n\nSELECT * FROM activities WHERE uuid_to_bin('8b6bb2f7-e7a7-4aec-8806-fbfea7093ccd') = uuid; # 54963421\n\n# ****************************************** Close *************************\nselect * from teams where id = 1031;\nSELECT * FROM crm_configurations WHERE id = 959;\nSELECT * FROM activities WHERE uuid_to_bin('59c517e0-96c9-4dad-bfeb-b7bef72f8725') = uuid; # 54963517\n\n\nSELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433\n\nSELECT * FROM activities WHERE uuid_to_bin('d68311e3-ac34-49bd-bf04-88323a7e5352') = uuid; # 54966009\n\n# ****************************************** SF *************************\nSELECT * FROM activities WHERE uuid_to_bin('2bd4cfa3-4eb5-4c77-84a7-9ed46a21c988') = uuid;\n\nselect * from roles;\nselect * from permissions;\nselect * from permission_role where permission_id = 136;\n\nselect * from migrations order by id desc;\n\nselect * from teams where id IN (1, 1037);\nselect * from crm_layouts where crm_configuration_id = 1;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1493;\nSELECT * FROM crm_fields WHERE id IN (1652,1661,66799,66814,66821,66836,66843,66846,66864,84752,182306,323580,380378);\n\nselect * from features;\nselect * from team_features where feature_id = 33;\nselect * from opportunities;\n\nselect * from teams;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1052 and sa.provider = 'hubspot';\n\nSELECT * FROM accounts where id = 11512582;\nselect * from activities where account_id = 11512582;","depth":4,"on_screen":true,"value":"SELECT\n# DISTINCT\nopp.id as opp_id, opp.uuid, opp.name,\n# COUNT(dr.id) AS deal_risk_count,\n\n# dr.id,\n# cfd.value,\nusr.name AS owner_name,\nopp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,\nusr.uuid as owner_uuid,\nusr.photo_path as owner_photo,\nusr.id AS owner_id,\njt.name as owner_job,\nopp.stage_id, opp.stage_updated_at,\nacc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,\nrt.business_process_id AS pipeline_id\nFROM opportunities opp\nLEFT JOIN record_types rt ON opp.record_type_id = rt.id\nLEFT JOIN users usr ON opp.user_id = usr.id\nLEFT JOIN accounts acc ON opp.account_id = acc.id\nLEFT JOIN job_titles jt ON usr.job_title_id = jt.id\n\n# LEFT JOIN group_deal_risk_types gdrt ON gdrt.group_id = usr.group_id\n# LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id AND dr.group_deal_risk_type_id = gdrt.id\n\nLEFT JOIN crm_field_data cfd ON (cfd.object_id = opp.id and cfd.crm_field_id = 66814)\n\nWHERE opp.user_id IS NOT NULL\nAND opp.deleted_at IS NULL\nAND opp.is_closed = 1\nAND opp.is_won = 0\nAND opp.close_date >= '2024-01-01 00:00:00'\nAND opp.close_date <= '2024-12-31 23:59:59'\nAND usr.team_id = 1\n\n# and opp.id = 4823179\n\nGROUP BY opp.id, cfd.value\nORDER BY\n# cfd.value\n CAST(cfd.value AS UNSIGNED)\n# owner_name\n# (SELECT COUNT(dr.id)\n# FROM deal_risks dr\n# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n# WHERE dr.opportunity_id = opp.id\n# AND dr.is_active = 1\n# AND gdrt.is_enabled = 1)\nDESC\nLIMIT 25\n# OFFSET 0\n;\n\nselect * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';\nselect * from crm_layout_entities where crm_layout_id = 1493;\n\nSELECT * FROM opportunities WHERE id = 4584045;\nSELECT * FROM crm_field_data WHERE object_id = 4584045;\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id = '0061K00000lfb9IQAQ'\n\nSELECT\n opp.id as opportunity_id,\n opp.name,\n COUNT(dr.id) as risk_count\nFROM opportunities opp\nLEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id\nWHERE opp.id IN (\n 6069574,8900938,9507638,9524799,9660490,9662824,9705675,9749758,4158460,4391812,6450439,4448422,5118945,5590675,4584045,7228149,9002408,9165534,9446720,9641778,9665149,9703344,9709280,5747948,4158491,6182565,6263970,5798120,5111315,4536978,6062352,6548383,6072095,6548225,4480986,5011422,6548381,8760540,8917554,9509986,9514392,9569009,9569011,9578490,9713604,5784397,7276612,7288405,8994421,9118219,9608148,9818911,4510535,6479693,5958049,6271674,6550448,4158331,4158483,6126571,6171615,6540943,4897466,5190896,5796182,5932762,8572433,8723698,8892697,9711001,9789264,4549188,6100831,6170064,6260799,6263653,6449936,6530871,6538978,7777651,8059269,8319918,8787049,8901150,9263153,9453207,9514738,9696700,9791062,5752018,6421452,7439134,7878923,9354763,9369285,9514396,9582506,9889949,9890216,4094311,4158495,4158496,6098128,5585661,3872564,6442149,5891604,6164746,6199593,6583474,6519684,9018490,9809006,4496897,5041324,5829430,6198319,6253504,6555763,7242914,7931055,8024125,8797814,8058559,8673347,8892695,8994420,9616219,9714970,9722004,9809439,9818918,6523177,8134147,9002915,9711422,9892713,9901719,9954210,9978435,5800810,6243518,6416114,6222251,6411974,6512456,5791953,6545606,9914780,5805540,6238986,6463838,6547680,9767049,9809437,9810885,9890855,6673493,9902036,4335521,6379871,6503799,6546077,8018765,9907556,9958433,9905855,9916179,9946741,9957877,5563344,6271838,6450815,7641128,7762567,7780592,8684810,8685786,8685787,8685788\n)\nGROUP BY opp.id;\n\nSELECT COUNT(dr.id)\n FROM deal_risks dr\n JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n WHERE dr.opportunity_id = 5563344\n# AND gdrt.group_id = usr.group_id\n\n\n\nEXPLAIN SELECT\n opp.id as opp_id, opp.uuid, opp.name,\n\ncfd.value,\ncfv.sequence,\ncfv.value,\n# MAX(cfd.value) AS max_cfd_value,\nusr.name AS owner_name,\nopp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,\nusr.uuid as owner_uuid,\nusr.photo_path as owner_photo,\nusr.id AS owner_id,\njt.name as owner_job,\nopp.stage_id, opp.stage_updated_at,\nacc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,\nrt.business_process_id AS pipeline_id\nFROM opportunities opp\n\nLEFT JOIN record_types rt ON opp.record_type_id = rt.id\nLEFT JOIN users usr ON opp.user_id = usr.id\nLEFT JOIN accounts acc ON opp.account_id = acc.id\nLEFT JOIN job_titles jt ON usr.job_title_id = jt.id\n\nLEFT JOIN crm_field_data cfd ON cfd.id = (SELECT sub_cfd.id\n FROM crm_field_data sub_cfd\n WHERE sub_cfd.object_id = opp.id\n AND sub_cfd.crm_field_id = 66810\n ORDER BY sub_cfd.updated_at DESC\n LIMIT 1)\n\nLEFT JOIN crm_field_values cfv ON cfv.crm_field_id = 66810 AND cfv.value = cfd.value\n\nWHERE opp.user_id IS NOT NULL\nAND opp.deleted_at IS NULL\nAND opp.is_closed = 0\n# AND opp.is_closed = 1\n# AND opp.is_won = 0\nAND opp.close_date >= '2024-01-01 00:00:00'\nAND opp.close_date <= '2024-12-31 23:59:59'\nAND usr.team_id = 1\n\n# and opp.id = 4823179\n\n# GROUP BY opp.id\nORDER BY\n cfv.sequence DESC,\n# opp.name\n# owner_name\n cfd.value\n# CAST(cfd.value AS UNSIGNED)\n# CAST(MAX(cfd.value) AS UNSIGNED)\n# (SELECT COUNT(dr.id)\n# FROM deal_risks dr\n# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n# WHERE dr.opportunity_id = opp.id\n# AND dr.is_active = 1 AND gdrt.is_enabled = 1)\nDESC\n# LIMIT 75, 25\n;\n\n\n\nSELECT * FROM crm_field_values WHERE crm_field_id = 66810;\n\nSELECT f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value, fd.updated_at, fd.id, fd.created_at\nFROM crm_fields f\nINNER JOIN crm_field_data fd ON fd.crm_field_id = f.id\n WHERE (f.crm_configuration_id = 1)\n AND (f.object_type = 'opportunity')\n# AND (fd.object_id IN (4158331,4158483,4158495,4158496,6069574,6263970,6171615,6540943,6545606,5829430,6198319,6263653,6548381,7242914,8797814,7228149,7439134,7878923,8134147,9002915,9118219,9165534,9354763,9369285,9446720))\n AND (fd.object_id IN (4158460))\n AND (f.crm_provider_id IN ('ForecastCategoryName'))\n ORDER BY fd.object_id ASC, fd.updated_at DESC;\n\nselect * from crm_layouts where crm_configuration_id = 1;\nselect cf.* from crm_fields cf\njoin crm_layout_entities cle on cf.id = cle.crm_field_id\nwhere crm_layout_id = 1493;\n\nSELECT * FROM opportunities WHERE id = 4158460;\nSELECT * FROM crm_field_data WHERE object_id = 4158460;\n\nselect * from users where team_id = 1;\nSELECT * FROM users WHERE id = 7160;\nselect * from role_user where user_id = 3248;\n\nselect * from crm_field_values where crm_field_id = 66824;\n\nSELECT * FROM users WHERE id = 23470;\nselect * from crm_configurations;\n\n# ******************************************\nselect * from teams where id = 1038; # 23521, 966\nselect * from users where team_id = 1038;\nselect * from crm_configurations where id = 966;\nSELECT * FROM activities WHERE uuid_to_bin('e8dc7c34-31f6-4430-bd81-2d9d8f00dd07') = uuid; # 54965762\nSELECT * FROM activities WHERE uuid_to_bin('4b5727c0-69d1-428f-b8d9-b649055166e2') = uuid; # 54965764\nSELECT * FROM participants WHERE activity_id = 54965764;\n\nSELECT id, uuid, title, recording_state FROM activities WHERE uuid_to_bin('355f105d-5606-4379-b0c5-d91daf59f4ce') = uuid; # 54964075\nSELECT * FROM participants WHERE activity_id = 54964075;\nselect f.id, slug, title, tf.team_id from team_features tf JOIN features f on tf.feature_id = f.id\n where tf.team_id = 1038;\n\n\n# ****************************************** PD *************************\nselect * from teams where id = 1029;\nSELECT * FROM crm_configurations WHERE id = 957;\nSELECT * FROM activities WHERE uuid_to_bin('581a33cb-4343-44bd-ac0c-4a5cee71c5ec') = uuid; # 54963423\nSELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433\n\nSELECT * FROM activities WHERE uuid_to_bin('8b6bb2f7-e7a7-4aec-8806-fbfea7093ccd') = uuid; # 54963421\n\n# ****************************************** Close *************************\nselect * from teams where id = 1031;\nSELECT * FROM crm_configurations WHERE id = 959;\nSELECT * FROM activities WHERE uuid_to_bin('59c517e0-96c9-4dad-bfeb-b7bef72f8725') = uuid; # 54963517\n\n\nSELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433\n\nSELECT * FROM activities WHERE uuid_to_bin('d68311e3-ac34-49bd-bf04-88323a7e5352') = uuid; # 54966009\n\n# ****************************************** SF *************************\nSELECT * FROM activities WHERE uuid_to_bin('2bd4cfa3-4eb5-4c77-84a7-9ed46a21c988') = uuid;\n\nselect * from roles;\nselect * from permissions;\nselect * from permission_role where permission_id = 136;\n\nselect * from migrations order by id desc;\n\nselect * from teams where id IN (1, 1037);\nselect * from crm_layouts where crm_configuration_id = 1;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1493;\nSELECT * FROM crm_fields WHERE id IN (1652,1661,66799,66814,66821,66836,66843,66846,66864,84752,182306,323580,380378);\n\nselect * from features;\nselect * from team_features where feature_id = 33;\nselect * from opportunities;\n\nselect * from teams;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1052 and sa.provider = 'hubspot';\n\nSELECT * FROM accounts where id = 11512582;\nselect * from activities where account_id = 11512582;","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.28224733,"top":1.0,"width":0.024268618,"height":-0.04788506},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3382584868951134281
|
6551498163460912845
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
8
6
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldDataRepository;
use Jiminny\Services\Crm\Hubspot\DecorateActivity;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationCategoryV4;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationTypeIdV4;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Http\Response;
use SevenShores\Hubspot\Exceptions\HubspotException;
use Sentry;
trait WriteCrmTrait
{
private const string ACTION_CREATE = 'create';
private const string ACTION_UPDATE = 'update';
private const string ACTION_DELETE = 'delete';
/**
* @inheritdoc
*/
public function saveActivity(Activity $activity): Activity
{
switch ($activity->getCrmType()) {
case Activity::TYPE_CONFERENCE:
$activity = $this->buildConferencePayload($activity);
break;
case Activity::TYPE_SOFTPHONE:
case Activity::TYPE_SOFTPHONE_INBOUND:
$activity = $this->buildSoftphonePayload($activity);
break;
case Activity::TYPE_SMS_INBOUND:
case Activity::TYPE_SMS_OUTBOUND:
$activity = $this->buildTextMessagePayload($activity);
break;
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildConferencePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatStartTime($activity),
'type' => 'MEETING',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'meetingOutcome' => $decorator->convertMeetingOutcome($activity),
'internalMeetingNotes' => $decorator->generateDescription($activity),
];
// Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.
if (! $activity->hasCrmProviderId()) {
if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {
$metadata['startTime'] = $decorator->formatStartTime($activity);
$metadata['endTime'] = $decorator->formatEndTime($activity);
}
$metadata['title'] = $decorator->generateTitle($activity);
}
// For now, this is just a "meetingOutcome" field for some customers.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function fetchCustomFieldData(Activity $activity, string $objectType): array
{
$payload = [];
$fieldDataRepository = app(FieldDataRepository::class);
$fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);
foreach ($fieldData as $data) {
// Add the field and value to the payload.
$payload += [
$data->getField()->getCrmProviderId() => $data->getValue(),
];
}
return $payload;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity
{
if (! $activity->hasCrmProviderId()) {
$this->createActivity($activity, $engagement, $metadata);
return $activity;
}
try {
$oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());
} catch (HubspotException $e) {
$this->logger->warning('[HubSpot] Engagement not found', [
'activity_id' => $activity->getUuid(),
'crm_provider_id' => $activity->getCrmProviderId(),
'exception' => $e->getMessage(),
]);
// potentially here we can log new activity if it is not found
return $activity;
}
$type = $oldEngagement['engagement']['type'] ?? null;
$bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';
$decorator = $this->getDecorateActivity();
if ($type !== 'TASK') {
$oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldDescription
);
$oldAssociations = $oldEngagement['associations'] ?? [];
$this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);
} else {
$oldTaskBody = $oldEngagement['metadata']['body'] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldTaskBody
);
$this->updateTask($activity, $engagement, $metadata);
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateNonTaskActivity(
Activity $activity,
array $engagement,
array $metadata,
array $oldAssociations
): void {
if ($activity->isTypeConference()) {
$this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);
} else {
$this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);
}
$this->updateActivityAssociations($activity, $oldAssociations);
$this->logCrmEngagementManipulation(
self::ACTION_UPDATE,
['crmId' => $activity->getCrmProviderId()],
$metadata,
);
}
private function updateActivityAssociations(Activity $activity, array $oldAssociations): void
{
$currentAssociations = $this->getActivityAssociations($activity);
$associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);
// No changes - exit
if (empty($associationChanges)) {
return;
}
$this->logger->info('[HubSpot] Updating associations', [
'team' => $this->team->getUuid(),
'activity' => $activity->getUuid(),
'associationChanges' => $associationChanges,
]);
$this->updateAssociations($activity, $associationChanges);
}
private function getAssociationDifferences(
Activity $activity,
array $oldAssociations,
array $currentAssociations
): array {
$objects = ['contactIds', 'companyIds', 'dealIds'];
$changes = [];
foreach ($objects as $object) {
$oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);
$currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);
$added = array_diff($currentIds, $oldIds);
$removed = array_diff($oldIds, $currentIds);
$type = match ($object) {
'contactIds' => 'contact',
'companyIds' => 'company',
'dealIds' => 'deal',
};
$shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);
if (! empty($removed) && ! $shouldIncludeRemoved) {
$this->logger->info('[HubSpot] Skipping association removal', [
'team' => $this->team->getUuid(),
'type' => $type,
'oldIds' => $oldIds,
'currentIds' => $currentIds,
'skippedRemovals' => array_values($removed),
]);
}
if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {
$changes[$type] = [
'added' => array_values($added),
'removed' => array_values($removed),
];
}
}
return $changes;
}
/**
* Determines whether removed associations should be synced to HubSpot.
*
* For contacts and companies, we always sync removals (original behavior).
* For deals, we only sync removals when:
* - There are new deals being added (replacement scenario), OR
* - The activity has an account associated (indicating intentional association management)
*
* This prevents accidentally removing deal associations that exist in HubSpot
* but not in Jiminny's database (e.g., deals manually added by users in HubSpot).
*/
private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool
{
// Contacts and companies: always sync removals
if ($type !== 'deal') {
return true;
}
// Deals: sync removals if we're adding new deals (replacement)
if (! empty($added)) {
return true;
}
// Deals: sync removals only if activity has an account (intentional management)
return $activity->hasAccount();
}
private function normalizeIds(array $ids): array
{
$stringIds = array_map('strval', $ids);
$uniqueIds = array_unique($stringIds);
sort($uniqueIds);
return $uniqueIds;
}
private function updateAssociations(Activity $activity, array $associationChanges): void
{
$objectType = $activity->isTypeConference() ? 'meeting' : 'call';
foreach ($associationChanges as $type => $changes) {
if (! empty($changes['added'])) {
$associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);
$payload = $this->payloadBuilder->buildAddAssociationPayload(
$activity->getCrmProviderId(),
$changes['added'],
$associationType
);
$this->getClient()->addAssociations($objectType, $type, $payload);
}
if (! empty($changes['removed'])) {
$payload = $this->payloadBuilder->buildRemoveAssociationPayload(
$activity->getCrmProviderId(),
$changes['removed']
);
$this->getClient()->removeAssociations($objectType, $type, $payload);
}
}
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response
{
// If already set by us/client, do not override it
if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {
$this->logger->info('[HubSpot] Meeting activity type already set', [
'meetingId' => $meetingId,
'activityType' => $engagement['activityType'],
]);
unset($engagement['activityType']);
}
$payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);
return $this->getClient()->updateMeeting($meetingId, $payload);
}
private function hasEngagementActivityType(string $engagementId): bool
{
try {
$engagementData = $this->getClient()->getEngagementData($engagementId);
if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {
return true;
}
return false;
} catch (\Exception $exception) {
Sentry::captureException($exception);
$this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [
'engagementId' => $engagementId,
'exception' => $exception->getMessage(),
]);
}
return true;
}
private function updateTask(Activity $activity, array $engagement, array $metadata): void
{
$this->getClient()->deleteEngagement($activity->getCrmProviderId());
$this->logCrmEngagementManipulation(
self::ACTION_DELETE,
['crmId' => $activity->getCrmProviderId()],
);
$this->createActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createActivity(Activity $activity, array $engagement, array $metadata): void
{
$associations = $this->getActivityAssociations($activity);
$associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);
if ($activity->isTypeConference()) {
try {
$hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);
$activityId = $hsEngagement->data->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
} else {
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
$activityId = $hsEngagement->data->engagement->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
}
}
private function convertActivityMeetingAssociationsV4(Activity $activity): array
{
$associations = [];
$contactIds = $this->getParticipantsIds($activity);
foreach ($contactIds as $contactId) {
$associations[] = $this->buildAssociation(
(int) $contactId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]
);
}
if ($activity->hasAccount()) {
$crmProviderId = $activity->getAccount()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]
);
}
}
if ($activity->hasOpportunity()) {
$crmProviderId = $activity->getOpportunity()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]
);
}
}
return $associations;
}
private function buildAssociation(int $id, array $categoryToTypeIds): array
{
$types = [];
foreach ($categoryToTypeIds as $category => $typeId) {
$types[] = $this->buildAssociationType($category, $typeId);
}
return [
'to' => ['id' => (string) $id],
'types' => $types,
];
}
private function buildAssociationType(string $category, int $typeId): array
{
return [
'associationCategory' => $category,
'associationTypeId' => $typeId,
];
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createMeetingV3(array $engagement, array $associations, array $metadata): Response
{
$payload = $this->payloadBuilder->getV3MeetingPayload(
engagement: $engagement,
metadata: $metadata,
associations: $associations
);
return $this->getClient()->createMeeting($payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
private function buildSoftphonePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'CALL',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'toNumber' => $decorator->formatCallToParticipant($activity),
'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),
'status' => $decorator->convertCallStatus($activity),
'durationMilliseconds' => (int) $activity->getDuration() * 1000,
'body' => $decorator->generateDescription($activity),
];
// For now, this is just a "disposition" field to store Call Outcome.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildTextMessagePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'NOTE',
];
$metadata = [
'body' => $decorator->generateDescription($activity),
];
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function getActivityAssociations(Activity $activity): array
{
return [
'contactIds' => $this->getParticipantsIds($activity),
'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],
'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],
'ownerIds' => [],
];
}
/**
* Get DecorateActivity instance. Can be overridden in tests for mocking.
*/
protected function getDecorateActivity(): DecorateActivity
{
return new DecorateActivity();
}
public function saveFollowupActivity(Activity $activity, array $fields): ?string
{
if (empty($fields['subject'])) {
return null;
}
$associations = $this->getActivityAssociations($activity);
if (empty($fields['timestamp']) === false) {
$formatTimestamp = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['timestamp']);
$timestamp = $formatTimestamp->getTimestamp();
} else {
$timestamp = strtotime('+1 day');
}
$reminder = null;
if (empty($fields['reminder']) === false) {
$formatReminder = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['reminder']);
$reminder = $formatReminder->getTimestamp() * 1000;
}
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $timestamp * 1000,
'type' => 'TASK',
];
$metadata = [
'subject' => $fields['subject'],
'body' => '',
'status' => 'NOT_STARTED',
'taskType' => 'CALL',
'reminders' => $reminder ? [$reminder] : [],
];
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
} catch (BadRequest $e) {
$this->logger->info('[HubSpot] Failed to create followup task', [
'activity' => $activity->getUuid(),
'engagement' => $engagement,
'metadata' => $metadata,
'error' => $e->getMessage(),
]);
return null;
}
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
// We don't actually create a corresponding activity object on our side yet.
return (string) $hsEngagement->data->engagement->id;
}
private function logCrmEngagementManipulation(
string $action,
array $engagementData,
array $data = [],
array $associations = [],
): void {
$payload = ['team' => $this->team->getUuid()];
if ($action === self::ACTION_CREATE) {
$payload['engagementData'] = $engagementData;
$payload['data'] = $data;
$payload['associations'] = $associations;
}
if ($action === self::ACTION_UPDATE) {
$payload['engagementId'] = $engagementData['crmId'];
$payload['data'] = $data;
}
if ($action === self::ACTION_DELETE) {
$payload['engagementId'] = $engagementData['crmId'];
}
$this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
8
1
3
4
Previous Highlighted Error
Next Highlighted Error
SELECT
# DISTINCT
opp.id as opp_id, opp.uuid, opp.name,
# COUNT(dr.id) AS deal_risk_count,
# dr.id,
# cfd.value,
usr.name AS owner_name,
opp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,
usr.uuid as owner_uuid,
usr.photo_path as owner_photo,
usr.id AS owner_id,
jt.name as owner_job,
opp.stage_id, opp.stage_updated_at,
acc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,
rt.business_process_id AS pipeline_id
FROM opportunities opp
LEFT JOIN record_types rt ON opp.record_type_id = rt.id
LEFT JOIN users usr ON opp.user_id = usr.id
LEFT JOIN accounts acc ON opp.account_id = acc.id
LEFT JOIN job_titles jt ON usr.job_title_id = jt.id
# LEFT JOIN group_deal_risk_types gdrt ON gdrt.group_id = usr.group_id
# LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id AND dr.group_deal_risk_type_id = gdrt.id
LEFT JOIN crm_field_data cfd ON (cfd.object_id = opp.id and cfd.crm_field_id = 66814)
WHERE opp.user_id IS NOT NULL
AND opp.deleted_at IS NULL
AND opp.is_closed = 1
AND opp.is_won = 0
AND opp.close_date >= '2024-01-01 00:00:00'
AND opp.close_date <= '2024-12-31 23:59:59'
AND usr.team_id = 1
# and opp.id = 4823179
GROUP BY opp.id, cfd.value
ORDER BY
# cfd.value
CAST(cfd.value AS UNSIGNED)
# owner_name
# (SELECT COUNT(dr.id)
# FROM deal_risks dr
# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
# WHERE dr.opportunity_id = opp.id
# AND dr.is_active = 1
# AND gdrt.is_enabled = 1)
DESC
LIMIT 25
# OFFSET 0
;
select * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';
select * from crm_layout_entities where crm_layout_id = 1493;
SELECT * FROM opportunities WHERE id = 4584045;
SELECT * FROM crm_field_data WHERE object_id = 4584045;
SELECT * FROM opportunities where team_id = 1 and crm_provider_id = '0061K00000lfb9IQAQ'
SELECT
opp.id as opportunity_id,
opp.name,
COUNT(dr.id) as risk_count
FROM opportunities opp
LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id
WHERE opp.id IN (
6069574,8900938,9507638,9524799,9660490,9662824,9705675,9749758,4158460,4391812,6450439,4448422,5118945,5590675,4584045,7228149,9002408,9165534,9446720,9641778,9665149,9703344,9709280,5747948,4158491,6182565,6263970,5798120,5111315,4536978,6062352,6548383,6072095,6548225,4480986,5011422,6548381,8760540,8917554,9509986,9514392,9569009,9569011,9578490,9713604,5784397,7276612,7288405,8994421,9118219,9608148,9818911,4510535,6479693,5958049,6271674,6550448,4158331,4158483,6126571,6171615,6540943,4897466,5190896,5796182,5932762,8572433,8723698,8892697,9711001,9789264,4549188,6100831,6170064,6260799,6263653,6449936,6530871,6538978,7777651,8059269,8319918,8787049,8901150,9263153,9453207,9514738,9696700,9791062,5752018,6421452,7439134,7878923,9354763,9369285,9514396,9582506,9889949,9890216,4094311,4158495,4158496,6098128,5585661,3872564,6442149,5891604,6164746,6199593,6583474,6519684,9018490,9809006,4496897,5041324,5829430,6198319,6253504,6555763,7242914,7931055,8024125,8797814,8058559,8673347,8892695,8994420,9616219,9714970,9722004,9809439,9818918,6523177,8134147,9002915,9711422,9892713,9901719,9954210,9978435,5800810,6243518,6416114,6222251,6411974,6512456,5791953,6545606,9914780,5805540,6238986,6463838,6547680,9767049,9809437,9810885,9890855,6673493,9902036,4335521,6379871,6503799,6546077,8018765,9907556,9958433,9905855,9916179,9946741,9957877,5563344,6271838,6450815,7641128,7762567,7780592,8684810,8685786,8685787,8685788
)
GROUP BY opp.id;
SELECT COUNT(dr.id)
FROM deal_risks dr
JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
WHERE dr.opportunity_id = 5563344
# AND gdrt.group_id = usr.group_id
EXPLAIN SELECT
opp.id as opp_id, opp.uuid, opp.name,
cfd.value,
cfv.sequence,
cfv.value,
# MAX(cfd.value) AS max_cfd_value,
usr.name AS owner_name,
opp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,
usr.uuid as owner_uuid,
usr.photo_path as owner_photo,
usr.id AS owner_id,
jt.name as owner_job,
opp.stage_id, opp.stage_updated_at,
acc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,
rt.business_process_id AS pipeline_id
FROM opportunities opp
LEFT JOIN record_types rt ON opp.record_type_id = rt.id
LEFT JOIN users usr ON opp.user_id = usr.id
LEFT JOIN accounts acc ON opp.account_id = acc.id
LEFT JOIN job_titles jt ON usr.job_title_id = jt.id
LEFT JOIN crm_field_data cfd ON cfd.id = (SELECT sub_cfd.id
FROM crm_field_data sub_cfd
WHERE sub_cfd.object_id = opp.id
AND sub_cfd.crm_field_id = 66810
ORDER BY sub_cfd.updated_at DESC
LIMIT 1)
LEFT JOIN crm_field_values cfv ON cfv.crm_field_id = 66810 AND cfv.value = cfd.value
WHERE opp.user_id IS NOT NULL
AND opp.deleted_at IS NULL
AND opp.is_closed = 0
# AND opp.is_closed = 1
# AND opp.is_won = 0
AND opp.close_date >= '2024-01-01 00:00:00'
AND opp.close_date <= '2024-12-31 23:59:59'
AND usr.team_id = 1
# and opp.id = 4823179
# GROUP BY opp.id
ORDER BY
cfv.sequence DESC,
# opp.name
# owner_name
cfd.value
# CAST(cfd.value AS UNSIGNED)
# CAST(MAX(cfd.value) AS UNSIGNED)
# (SELECT COUNT(dr.id)
# FROM deal_risks dr
# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
# WHERE dr.opportunity_id = opp.id
# AND dr.is_active = 1 AND gdrt.is_enabled = 1)
DESC
# LIMIT 75, 25
;
SELECT * FROM crm_field_values WHERE crm_field_id = 66810;
SELECT f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value, fd.updated_at, fd.id, fd.created_at
FROM crm_fields f
INNER JOIN crm_field_data fd ON fd.crm_field_id = f.id
WHERE (f.crm_configuration_id = 1)
AND (f.object_type = 'opportunity')
# AND (fd.object_id IN (4158331,4158483,4158495,4158496,6069574,6263970,6171615,6540943,6545606,5829430,6198319,6263653,6548381,7242914,8797814,7228149,7439134,7878923,8134147,9002915,9118219,9165534,9354763,9369285,9446720))
AND (fd.object_id IN (4158460))
AND (f.crm_provider_id IN ('ForecastCategoryName'))
ORDER BY fd.object_id ASC, fd.updated_at DESC;
select * from crm_layouts where crm_configuration_id = 1;
select cf.* from crm_fields cf
join crm_layout_entities cle on cf.id = cle.crm_field_id
where crm_layout_id = 1493;
SELECT * FROM opportunities WHERE id = 4158460;
SELECT * FROM crm_field_data WHERE object_id = 4158460;
select * from users where team_id = 1;
SELECT * FROM users WHERE id = 7160;
select * from role_user where user_id = 3248;
select * from crm_field_values where crm_field_id = 66824;
SELECT * FROM users WHERE id = 23470;
select * from crm_configurations;
# [PASSWORD_DOTS]
select * from teams where id = 1038; # 23521, 966
select * from users where team_id = 1038;
select * from crm_configurations where id = 966;
SELECT * FROM activities WHERE uuid_to_bin('e8dc7c34-31f6-4430-bd81-2d9d8f00dd07') = uuid; # 54965762
SELECT * FROM activities WHERE uuid_to_bin('4b5727c0-69d1-428f-b8d9-b649055166e2') = uuid; # 54965764
SELECT * FROM participants WHERE activity_id = 54965764;
SELECT id, uuid, title, recording_state FROM activities WHERE uuid_to_bin('355f105d-5606-4379-b0c5-d91daf59f4ce') = uuid; # 54964075
SELECT * FROM participants WHERE activity_id = 54964075;
select f.id, slug, title, tf.team_id from team_features tf JOIN features f on tf.feature_id = f.id
where tf.team_id = 1038;
# [PASSWORD_DOTS] PD [PASSWORD_DOTS]
select * from teams where id = 1029;
SELECT * FROM crm_configurations WHERE id = 957;
SELECT * FROM activities WHERE uuid_to_bin('581a33cb-4343-44bd-ac0c-4a5cee71c5ec') = uuid; # 54963423
SELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433
SELECT * FROM activities WHERE uuid_to_bin('8b6bb2f7-e7a7-4aec-8806-fbfea7093ccd') = uuid; # 54963421
# [PASSWORD_DOTS] Close [PASSWORD_DOTS]
select * from teams where id = 1031;
SELECT * FROM crm_configurations WHERE id = 959;
SELECT * FROM activities WHERE uuid_to_bin('59c517e0-96c9-4dad-bfeb-b7bef72f8725') = uuid; # 54963517
SELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433
SELECT * FROM activities WHERE uuid_to_bin('d68311e3-ac34-49bd-bf04-88323a7e5352') = uuid; # 54966009
# [PASSWORD_DOTS] SF [PASSWORD_DOTS]
SELECT * FROM activities WHERE uuid_to_bin('2bd4cfa3-4eb5-4c77-84a7-9ed46a21c988') = uuid;
select * from roles;
select * from permissions;
select * from permission_role where permission_id = 136;
select * from migrations order by id desc;
select * from teams where id IN (1, 1037);
select * from crm_layouts where crm_configuration_id = 1;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 1493;
SELECT * FROM crm_fields WHERE id IN (1652,1661,66799,66814,66821,66836,66843,66846,66864,84752,182306,323580,380378);
select * from features;
select * from team_features where feature_id = 33;
select * from opportunities;
select * from teams;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1052 and sa.provider = 'hubspot';
SELECT * FROM accounts where id = 11512582;
select * from activities where account_id = 11512582;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
45159
|
NULL
|
NULL
|
NULL
|
|
45162
|
1622
|
6
|
2026-05-14T14:23:10.561487+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768590561_m2.jpg...
|
PhpStorm
|
faVsco.js – WriteCrmTrait.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
ActivityFilesLaterJiminny... ~@ jiminny-x-integrat ActivityFilesLaterJiminny... ~@ jiminny-x-integrati& platform-inner-team© Channels# ai-chapter# alertsi backend# bugscontusion-cllnia# curiosity_lab# engineering# general# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the people of iimi..• Direct messages8. A...• A3 62P. Galya Dimitrova Mvasil VasilevA. Stefka Stoyanova%: Todor Stamatovf. Mario GeorgievP. Nikolay Ivanov2o James Graham "2. Stoyan Tanev. Steliyan Georgiev. Petko Kashinski*. Lukas Kovali...a: Apps® ToastS lira Gloud6 Huddle with Aneliya Angelova& R. Aneliya Angelova •• Messagest Add canvasur FilesAneliya Angelova 2:30 PMЛукаш, само при Сейлсфорс и Хубспот се синкваха активити типовете, когато се направи плейбук,нали?пои останалите скМі тоябва оъчно ла се въвелат.Lukas Kovalik 2:47 PMтряова да се за всички, някьде не се ли попьлвапри зохо май беше hardcoded но май и там си връщаха две категорииAneliya Angelova @ 4:00 PMЛукаш имаш ли време да се чуем за тестването на https://jiminny.atlassian.net/browse/JY-20725X Preview insackt[HubSpot] Optimise CRM rematctC OpenReady for QA- MediumAA Aneliya AngelovaAs of today at 4:00 PM RetreshOpen in Jira+ SummariseLukas Kovalik 4:02 PMzean-iurwiniareelYou joined the huddle LIVE 4:06 PMAneliva Angelova is here toolAneliya Angelova 4:44 PM11512582Lukas Kovalik O 5:16 PMcurl --location "httns:ani.hubani.com/crm/v3/obiects/contacts/search--header 'Content-Type: application/json'\--header "Authorization: BearerC|Kvma?iMx7OINOM8kOFwrAowAcAkUAhIR24?A05D?xiVrIYMILuP6SA01Kwcmhr=n.v2 hytorDniMvDalYck-CMalovolNOM9kOFwrAUAcrkcaws-ThwRARIRAOF-ATFSAOFRAO-ROIEBAQUBEggBAQEBAYICFIґ7Au8O2Nwal9YW2CbCCxYvwngmSgNldTFSAFoAYABoAHAAeAA'\--data "{ "limit", 13'Message Aneliva Angelova+ AaIAl Notes: OffLeave~ (Q SearchDate ModifiedYesterday at 13.34Yesterday at 13:34Yesterday at 13.33Yesterdav at 13:32resterday al i5.sYesterdav at 13:31Yesterday at 13:30Yesterday at 13.29Yesterday at 13:29resterday at 15.2oYesterday at 13:27Yesterday at 13:26Yesterday at 13:26Yesterday at 13:25Yesterday at 13.24Yesterdav at 13:24Yesterday at 13:23Yesterday at 13:21Yesterday at 13:21Yesterday at 13.20Yesterdav at 13:19Yesterday at 13:19Yesterday at 13:18Yesterday at 13:18Yesterday at 13:17Yesterday at 13.16Yecterdav at 12:16Yesterday at 13:15Yesterday at 13.10Yesterdav at 13:14Yesterday at 13:14Yesterday at 13:13Yecterdav at 12:12Yesterday at 13.11Yesterdav at 13:11Yesterday at 13:10Yesterday at 13:09Yesterday at 13:09Yesterday at 13:08Yesterday at 13.08Yecterdav at 12:07Yesterday at 13:07Yesterdav at 13:0617K:MPEG-4 movie29 KBMPEG-4 movie6 KB12 Kb9 KBIMPEG-4 movieMPEG-4 movie7 KB8 KBMPEG-4 movie37 K:10 KBMPEG-4 movie7 KBMPEG-4 movie9 KBMPEG-4 movie8 KB72 KBMPEG-4 movie14 K:13 KBMPEG-4 movie9 KB18 KB|MPEG-4 movie12 KBMPEG-4 movie10 KB16 KBMPEG-4 movie6KEMPEG-4 movie6 KB MPEG-4 movie12 KB23 K:MPEG-4 movie8 KBMPEG-4 movie6 KBMPEG-4 movie11 KEMPEG-4 movie11 KBMPEG-4 movie20 KB34 K:MPEG-4 movie10 KBMPEG-4 movie7 KB5KBMPEG-4 movie11 KPMPEG-4 movie26 KB MPEG-4 movie111 KBMPEG-4 movie102 K:MPEG-4 movie88 KBMPEG-4 movie59 KB98 KBMPEG-4 movie07 KPMDEG-A movie66 KBMPEG-4 movie44 KBMPEG-4 movie03 K:IMPEG-A movid78 KB MPEG-4 movie50 KB58 KB27 KPMPEG-4 movieMPEG-4 movie7 KB12 KBMPEG-4 movie22 K:17 KBMPEG-4 movie19 KB32 K8MPEG-4 movieFavouritesE jiminny© Recents* ApplicationsiCloudiCloud Drive228 Sync folderQ DXP4800PLUS-B5FA@ Network|• CRMI• Orange• Red• Yellow• Green• Purple•) All lags..lohlDownloadsNameLoom.pkgAlfred copv.alfredoreterencesB KeychronAssist-1.0.2 (1).dmgA Keychron Assist-1.0.2.dmgmazanoke-images-ywJo.ziPhotos-3-001.zipD Transcript.pdf→mage U.loge1 Orioninstaller.dmaimage.jpg- image (2).1pc• ПО-22221726037035-004-001_ORGES.pdf• %D0%9F%D0%9E-22221726037035-004-001_archive.zipПO-22221726037035-004-001_archive (1).zip• repon 4).XmAltred copy2.altredoreterences05012026_0000000026574472_ SWIFT_OB70501260015890.pdf27022026_0000000026574472_SWIFT_OB72702260049200.pdf= 03042026 [CREDIT_CARD] SWIFT [CREDIT_CARD].001B reporti) xm=pdt.odipdf-1.pdfD pdf-2.pdf-pdf-5.pd1= ndf-1 ndipdf-3.pdfB Rovaix Famly Treo gedbitwarden export 20251031122528.isonKoválik Family Tree.zip*macOS Storage_Cleanup.mdal favicon icofirst_aid_notes_complete.docxrenortl2).esvconfig.ymlIteration run Search HS.postman_collection.json--report(1).csvm licence hettertouchtoalMariusHosting Config.json1ooks-891a6503-bbb7-4b2b-9c3.csv•Alfredmazanoke-images-YWJ6ана Ковалик.jpg•искане даниел Ковалик..pg• Фактура Март Даниел Ковалик.jрс• Фактура Април Даниел Ковалик.jpg• Dhotac 2.001Q SearchKind00,4 MDinstdlle..dckage55.9 MBAlfred...ferences10,1 MBDisk Image10,1 MBDisk ImageIL MBLiP archive6,6 MBZIP archive2,5 MB PDF Document2,5 MBJreo lmage2.2 MBDisk Image2,2 MB PDF Document2 MBJPEG image1,9 MBJPEG imaqe192 KBPDF Document140 KbZIP archive148 KВZIP archivel148 KBZIP archive122 KBXML document111 KBAlfred...ferences94 KBPDF Document92 KBPDr DocumentK:91 KB91 KB30 KB29 KBPDF DocumentXML documentPDF Document28 KBPDF Document28 KR28 KB27 KB14 KB11 KB6 KB6KBPDE DocumentDocumentJSONICSV DocumentZIP archiveMarkdo…..ument5KRWindo...n image4 KB3 KBword ..cumentCSV Document2 KBYAML document1 KBcSV Document928 byteshttlicence183 bytesZero butesJSONAlfred.. ferencesZero bytesFolde1,9 MB1,8 MB17 MB1,7 MBColdorJPEG imageJPEG ImageIPEG imadeJPEG imageColdo1 of 58 selected, 9.63 GB availabld• Inu 14 Mаy 1/.23.10Date AddedIs Mdl ZUzo dl 19:4530 Jan 2026 at 12:3617 Mar 2026 at 20:2717 Mar 2026 at 20:26Z3 Aor 2020 al 13:0229 Jan 2026 at 15:2019 Dec 2025 at 10:1619 Dec 2025 at 12:238 Aor 2026 at 20:3519 Dec 2025 at 10:2919 Dec 2025 at 12:1819 Dec 2025 at 12:4026 Mar 2026 at 11:2410 May 2026 at 13:5326 Mar 2026 at 11:2426 Mar 2026 at 11:2426 Mar 2026 at 11:2310 May 2026 at 14:3730 Jan 2026 at 12:3713 Feb 2026 at 11:54Z3 Apr 2026 at 13.0823 Aor 2026 at 13:0810 May 2026 at 13:5410 May 2026 at 14:3710 May 2026 at 13:4910 May 2026 at 13:5010 May 2026 at 13:5110 Mav 2026 at 12:51110 May 2026 at 13:5019 Dec 2025 at 11:3431 Oct 2025 at 12:2525 Nov 2025 at 17:596 Mar 2026 at 11.2224 Anr 2026 at 16:5220 Oct 2025 at 11:0218 Mar 2026 at 15:29• Mav 2026 at 11:09129 Oct 2025 at 19:329 May 2026 at 10:0418 Mar 2026 at 11:5510 May 2026 at 13:5712 Jun 2025 at 19:0430 Jan 2026 at 12:3630 Jan 2026 at 12:3616 Oct 2025 aт 16:0123 Apr 2026 at 13:0223 Apr 2026 at 13:0223 Apr 2026 at 13:0222 Anr 2026 at 12:0223 Apr 2026 at 13:0229 Jan 2026 at 15:20...
|
NULL
|
-5443580408892116924
|
NULL
|
click
|
ocr
|
NULL
|
ActivityFilesLaterJiminny... ~@ jiminny-x-integrat ActivityFilesLaterJiminny... ~@ jiminny-x-integrati& platform-inner-team© Channels# ai-chapter# alertsi backend# bugscontusion-cllnia# curiosity_lab# engineering# general# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the people of iimi..• Direct messages8. A...• A3 62P. Galya Dimitrova Mvasil VasilevA. Stefka Stoyanova%: Todor Stamatovf. Mario GeorgievP. Nikolay Ivanov2o James Graham "2. Stoyan Tanev. Steliyan Georgiev. Petko Kashinski*. Lukas Kovali...a: Apps® ToastS lira Gloud6 Huddle with Aneliya Angelova& R. Aneliya Angelova •• Messagest Add canvasur FilesAneliya Angelova 2:30 PMЛукаш, само при Сейлсфорс и Хубспот се синкваха активити типовете, когато се направи плейбук,нали?пои останалите скМі тоябва оъчно ла се въвелат.Lukas Kovalik 2:47 PMтряова да се за всички, някьде не се ли попьлвапри зохо май беше hardcoded но май и там си връщаха две категорииAneliya Angelova @ 4:00 PMЛукаш имаш ли време да се чуем за тестването на https://jiminny.atlassian.net/browse/JY-20725X Preview insackt[HubSpot] Optimise CRM rematctC OpenReady for QA- MediumAA Aneliya AngelovaAs of today at 4:00 PM RetreshOpen in Jira+ SummariseLukas Kovalik 4:02 PMzean-iurwiniareelYou joined the huddle LIVE 4:06 PMAneliva Angelova is here toolAneliya Angelova 4:44 PM11512582Lukas Kovalik O 5:16 PMcurl --location "httns:ani.hubani.com/crm/v3/obiects/contacts/search--header 'Content-Type: application/json'\--header "Authorization: BearerC|Kvma?iMx7OINOM8kOFwrAowAcAkUAhIR24?A05D?xiVrIYMILuP6SA01Kwcmhr=n.v2 hytorDniMvDalYck-CMalovolNOM9kOFwrAUAcrkcaws-ThwRARIRAOF-ATFSAOFRAO-ROIEBAQUBEggBAQEBAYICFIґ7Au8O2Nwal9YW2CbCCxYvwngmSgNldTFSAFoAYABoAHAAeAA'\--data "{ "limit", 13'Message Aneliva Angelova+ AaIAl Notes: OffLeave~ (Q SearchDate ModifiedYesterday at 13.34Yesterday at 13:34Yesterday at 13.33Yesterdav at 13:32resterday al i5.sYesterdav at 13:31Yesterday at 13:30Yesterday at 13.29Yesterday at 13:29resterday at 15.2oYesterday at 13:27Yesterday at 13:26Yesterday at 13:26Yesterday at 13:25Yesterday at 13.24Yesterdav at 13:24Yesterday at 13:23Yesterday at 13:21Yesterday at 13:21Yesterday at 13.20Yesterdav at 13:19Yesterday at 13:19Yesterday at 13:18Yesterday at 13:18Yesterday at 13:17Yesterday at 13.16Yecterdav at 12:16Yesterday at 13:15Yesterday at 13.10Yesterdav at 13:14Yesterday at 13:14Yesterday at 13:13Yecterdav at 12:12Yesterday at 13.11Yesterdav at 13:11Yesterday at 13:10Yesterday at 13:09Yesterday at 13:09Yesterday at 13:08Yesterday at 13.08Yecterdav at 12:07Yesterday at 13:07Yesterdav at 13:0617K:MPEG-4 movie29 KBMPEG-4 movie6 KB12 Kb9 KBIMPEG-4 movieMPEG-4 movie7 KB8 KBMPEG-4 movie37 K:10 KBMPEG-4 movie7 KBMPEG-4 movie9 KBMPEG-4 movie8 KB72 KBMPEG-4 movie14 K:13 KBMPEG-4 movie9 KB18 KB|MPEG-4 movie12 KBMPEG-4 movie10 KB16 KBMPEG-4 movie6KEMPEG-4 movie6 KB MPEG-4 movie12 KB23 K:MPEG-4 movie8 KBMPEG-4 movie6 KBMPEG-4 movie11 KEMPEG-4 movie11 KBMPEG-4 movie20 KB34 K:MPEG-4 movie10 KBMPEG-4 movie7 KB5KBMPEG-4 movie11 KPMPEG-4 movie26 KB MPEG-4 movie111 KBMPEG-4 movie102 K:MPEG-4 movie88 KBMPEG-4 movie59 KB98 KBMPEG-4 movie07 KPMDEG-A movie66 KBMPEG-4 movie44 KBMPEG-4 movie03 K:IMPEG-A movid78 KB MPEG-4 movie50 KB58 KB27 KPMPEG-4 movieMPEG-4 movie7 KB12 KBMPEG-4 movie22 K:17 KBMPEG-4 movie19 KB32 K8MPEG-4 movieFavouritesE jiminny© Recents* ApplicationsiCloudiCloud Drive228 Sync folderQ DXP4800PLUS-B5FA@ Network|• CRMI• Orange• Red• Yellow• Green• Purple•) All lags..lohlDownloadsNameLoom.pkgAlfred copv.alfredoreterencesB KeychronAssist-1.0.2 (1).dmgA Keychron Assist-1.0.2.dmgmazanoke-images-ywJo.ziPhotos-3-001.zipD Transcript.pdf→mage U.loge1 Orioninstaller.dmaimage.jpg- image (2).1pc• ПО-22221726037035-004-001_ORGES.pdf• %D0%9F%D0%9E-22221726037035-004-001_archive.zipПO-22221726037035-004-001_archive (1).zip• repon 4).XmAltred copy2.altredoreterences05012026_0000000026574472_ SWIFT_OB70501260015890.pdf27022026_0000000026574472_SWIFT_OB72702260049200.pdf= 03042026 [CREDIT_CARD] SWIFT [CREDIT_CARD].001B reporti) xm=pdt.odipdf-1.pdfD pdf-2.pdf-pdf-5.pd1= ndf-1 ndipdf-3.pdfB Rovaix Famly Treo gedbitwarden export 20251031122528.isonKoválik Family Tree.zip*macOS Storage_Cleanup.mdal favicon icofirst_aid_notes_complete.docxrenortl2).esvconfig.ymlIteration run Search HS.postman_collection.json--report(1).csvm licence hettertouchtoalMariusHosting Config.json1ooks-891a6503-bbb7-4b2b-9c3.csv•Alfredmazanoke-images-YWJ6ана Ковалик.jpg•искане даниел Ковалик..pg• Фактура Март Даниел Ковалик.jрс• Фактура Април Даниел Ковалик.jpg• Dhotac 2.001Q SearchKind00,4 MDinstdlle..dckage55.9 MBAlfred...ferences10,1 MBDisk Image10,1 MBDisk ImageIL MBLiP archive6,6 MBZIP archive2,5 MB PDF Document2,5 MBJreo lmage2.2 MBDisk Image2,2 MB PDF Document2 MBJPEG image1,9 MBJPEG imaqe192 KBPDF Document140 KbZIP archive148 KВZIP archivel148 KBZIP archive122 KBXML document111 KBAlfred...ferences94 KBPDF Document92 KBPDr DocumentK:91 KB91 KB30 KB29 KBPDF DocumentXML documentPDF Document28 KBPDF Document28 KR28 KB27 KB14 KB11 KB6 KB6KBPDE DocumentDocumentJSONICSV DocumentZIP archiveMarkdo…..ument5KRWindo...n image4 KB3 KBword ..cumentCSV Document2 KBYAML document1 KBcSV Document928 byteshttlicence183 bytesZero butesJSONAlfred.. ferencesZero bytesFolde1,9 MB1,8 MB17 MB1,7 MBColdorJPEG imageJPEG ImageIPEG imadeJPEG imageColdo1 of 58 selected, 9.63 GB availabld• Inu 14 Mаy 1/.23.10Date AddedIs Mdl ZUzo dl 19:4530 Jan 2026 at 12:3617 Mar 2026 at 20:2717 Mar 2026 at 20:26Z3 Aor 2020 al 13:0229 Jan 2026 at 15:2019 Dec 2025 at 10:1619 Dec 2025 at 12:238 Aor 2026 at 20:3519 Dec 2025 at 10:2919 Dec 2025 at 12:1819 Dec 2025 at 12:4026 Mar 2026 at 11:2410 May 2026 at 13:5326 Mar 2026 at 11:2426 Mar 2026 at 11:2426 Mar 2026 at 11:2310 May 2026 at 14:3730 Jan 2026 at 12:3713 Feb 2026 at 11:54Z3 Apr 2026 at 13.0823 Aor 2026 at 13:0810 May 2026 at 13:5410 May 2026 at 14:3710 May 2026 at 13:4910 May 2026 at 13:5010 May 2026 at 13:5110 Mav 2026 at 12:51110 May 2026 at 13:5019 Dec 2025 at 11:3431 Oct 2025 at 12:2525 Nov 2025 at 17:596 Mar 2026 at 11.2224 Anr 2026 at 16:5220 Oct 2025 at 11:0218 Mar 2026 at 15:29• Mav 2026 at 11:09129 Oct 2025 at 19:329 May 2026 at 10:0418 Mar 2026 at 11:5510 May 2026 at 13:5712 Jun 2025 at 19:0430 Jan 2026 at 12:3630 Jan 2026 at 12:3616 Oct 2025 aт 16:0123 Apr 2026 at 13:0223 Apr 2026 at 13:0223 Apr 2026 at 13:0222 Anr 2026 at 12:0223 Apr 2026 at 13:0229 Jan 2026 at 15:20...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45161
|
1621
|
15
|
2026-05-14T14:23:10.574462+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768590574_m1.jpg...
|
PhpStorm
|
faVsco.js – WriteCrmTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
8043719072324535154
|
-8628527368849355612
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
PhpStormFileEditViewNavig Project: faVsco.js, menu
PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelpabl100% C•FV faVsco.jsv#12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTest8• Thu 14 May 17:23:10* :QProject v© ReportController.php© AutomatedReportGenerated.phpTrackAutomatedReportGeneratedEvent.phpPlaybackController.php=custom.log> D CrmObjectsUserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.phpSendReportJob.php• DecorateActivity› D Dummy• DeleteCrmEntityTrait.phpDeleteAccountJob.php© ImportActivityTypes.phpT WriteCrmTrait.php x© AccountController.php› HelpersT IntegrationAppTrait.php.env.staging.envDetachActivityObject.phpRematchActivityOnCrmObjectDetach.phpv Hubspot© Client.php18D AccountSyncStrate› D ActionsD ContactSyncStrate!> DDTO› O Fields© MatchActivityCrmData.phptrait WriteCrmTrait* Othrows HubspotException* Othrows BadRequest*/1 usage© HubspotPaginationService.phpHandleHubspotRateLimit.php19525354A8M6AY› D Journal55private function buildConferencePayload(Activity Sactivity): ActivityMetadata56v OpportunitySyncSti57$decorator = $this->getDecorateActivity®:> [ Concerns58© HubspotLastMor59© HubspotLastMor60© HubspotLastMor61© HubspotLastMor62© HubspotLastMor63© HubspotSingleS:$engagement = ['active' => true,'ownerId' => $this->profile->getCrmProviderId(.'timestamp' => $decorator->formatStartTime(Sactivity),'type' => 'MEETING',64© HubspotSyncStr]:65© HubspotWebhoc66v D Pagination67if ($activity->hasActivityType() && $this-›matchesCrmType($activity->getActivityType())) {$engagement['activityType'] = $activity->getActivityType()->getName();D-—211212213214215216217218— 219220221Cascade & TI© HubspotPaginati68© PaginationConfig69224© PaginationState.22570> D ProspectSearchStra71> [ Redis72v D ServiceTraits$metadata = ['meetingOutcome' => $decorator->convertMeetingOutcome(Sactivity),'internalMeetingNotes' => $decorator-›generateDescription(Sactivity),=226_22773€ OpportunitySync1:22822974© SyncCrmEntities75230+ SyncFieldsTrait./// Only if they are creating a fresh engagement should we update the times, otherwise it will cause at76231if (! $activity->hasCrmProviderId)) {T WriteCrmTrait.pl77if (Sactivity->hasActualStartTime() && $activity->hasActualEndTime)232D Utils78— 233 VSmetadata['startTime'] = $decorator->formatStartTime($activity);D Webhook79@ RatchSvneCnllertn'Smetadata['endTime'] = $decorator->formatEndTime(Sactivity);234Workspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)=laravel.logSF [jiminny@localhost]A HS_local [jiminny@localho:W4 console [QAI PROD] X4 console [PROD]A console (EU]Go jiminny0841A3 X4 A Vm migrations oim teams wherem crm_layouts !iM crm_layout_eiIM crm_fields Wim features;m team_feature:m opportunitie:Command 8811.id,CASEWHENidFROMsocial,I on u.id= sa.:1..n<->1: on t.L_id = 1052 andIMaccounts wheiactivitiesW Windsurf Teams66:39 (15 chars)UTF-8Co 4 spaces...
|
45158
|
NULL
|
NULL
|
NULL
|
|
45163
|
1621
|
16
|
2026-05-14T14:23:13.247292+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768593247_m1.jpg...
|
PhpStorm
|
faVsco.js – WriteCrmTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorRun PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelpabl100% C•FV faVsco.jsv#12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTest8• Thu 14 May 17:23:12* :QProject v© ReportController.php© AutomatedReportGenerated.phpTrackAutomatedReportGeneratedEvent.phpPlaybackController.php=custom.log> D CrmObjectsUserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.phpSendReportJob.php• DecorateActivity› D Dummy• DeleteCrmEntityTrait.phpDeleteAccountJob.php© ImportActivityTypes.phpT WriteCrmTrait.php x© AccountController.php› HelpersT IntegrationAppTrait.php.env.staging.envDetachActivityObject.phpRematchActivityOnCrmObjectDetach.phpv Hubspot© Client.php18D AccountSyncStrate› D ActionsD ContactSyncStrate!> DDTO› O Fields› D Journal© MatchActivityCrmData.phptrait WriteCrmTrait* @throws HubspotException* @throws BadRequest*/1 usageHubspotPaginationService.phpHandleHubspotRateLimit.php19525354Metadata55private function buildConferencePayload(Activity Sactivity): Activity56v OpportunitySyncSti> [ Concerns57$decorator = Sthis->getDecorateActivityO;© HubspotLastMor58© HubspotLastMor59© HubspotLastMor60© HubspotLastMor6162© HubspotLastMor63© HubspotSingleS:$engagement = l'active'= true,'ownerid' => Sthis->profile->getCrmProviderId(),'timestamp' => $decorator->formatStartTime(Sactivity),'type' => 'MEETING',64© HubspotSyncStr]:© HubspotWebhoc6566v D Pagination© HubspotPaginati6768© PaginationConfig© PaginationState.6970> D ProspectSearchStra> [ Redis71if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType))) {$engagement['activityType'] = $activity->getAct< 1/2 >T ActivityPlaybookTraitpublic function matchesCrmType(PlaybookCategory Scategory: boolv D ServiceTraits72Smetadata = ['meetingOutcome' => $decorator->convertMeeting0'internalMeetingNotes' => $decorator->generateD€ OpportunitySync73];© SyncCrmEntities74Parameters: \PlaybookCategory$category+ SyncFieldsTrait./75// Only if they are creating a fresh engagementshoT WriteCrmTrait.pl76if (! $activity->hasCrmProviderId()) {Source:.../app/Services/Crm/Helpers/ActivityPlaybookTrait.phpD Utils77if (Sactivity->hasActualStartTime() && $activitD Webhook78Smetadata('startTime'] = $decorator->format@ RatchSvneCnllertn'79Smetadata["ondTime!]l= Sdeconaton->formatEndTime (Sactivity):Workspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)=laravel.logSF [jiminny@localhost]A HS_local [jiminny@localho:W4 console [QAI PROD] XA8M6AY4 console [PROD]A console (EU]Go jiminny0841A3 X4 A Vm migrations oim teams wherem crm_layouts !iM crm_layout_eiIM crm_fields Wim features;team_feature:m opportunitie:m teams;1.id, CASEWHENidFROMsocial.1 on u.id= sa.:1..n<->1: on t.L_id = 1052 andLUcauseaD-—211212213214215216217218— 219220221222223224225=226_227228229230231232- 233 V234IMaccounts wheiactivitiesW Windsurf Teams66:60UTF-8Co 4 spaces...
|
NULL
|
-525233212572782706
|
NULL
|
visual_change
|
ocr
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorRun PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelpabl100% C•FV faVsco.jsv#12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTest8• Thu 14 May 17:23:12* :QProject v© ReportController.php© AutomatedReportGenerated.phpTrackAutomatedReportGeneratedEvent.phpPlaybackController.php=custom.log> D CrmObjectsUserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.phpSendReportJob.php• DecorateActivity› D Dummy• DeleteCrmEntityTrait.phpDeleteAccountJob.php© ImportActivityTypes.phpT WriteCrmTrait.php x© AccountController.php› HelpersT IntegrationAppTrait.php.env.staging.envDetachActivityObject.phpRematchActivityOnCrmObjectDetach.phpv Hubspot© Client.php18D AccountSyncStrate› D ActionsD ContactSyncStrate!> DDTO› O Fields› D Journal© MatchActivityCrmData.phptrait WriteCrmTrait* @throws HubspotException* @throws BadRequest*/1 usageHubspotPaginationService.phpHandleHubspotRateLimit.php19525354Metadata55private function buildConferencePayload(Activity Sactivity): Activity56v OpportunitySyncSti> [ Concerns57$decorator = Sthis->getDecorateActivityO;© HubspotLastMor58© HubspotLastMor59© HubspotLastMor60© HubspotLastMor6162© HubspotLastMor63© HubspotSingleS:$engagement = l'active'= true,'ownerid' => Sthis->profile->getCrmProviderId(),'timestamp' => $decorator->formatStartTime(Sactivity),'type' => 'MEETING',64© HubspotSyncStr]:© HubspotWebhoc6566v D Pagination© HubspotPaginati6768© PaginationConfig© PaginationState.6970> D ProspectSearchStra> [ Redis71if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType))) {$engagement['activityType'] = $activity->getAct< 1/2 >T ActivityPlaybookTraitpublic function matchesCrmType(PlaybookCategory Scategory: boolv D ServiceTraits72Smetadata = ['meetingOutcome' => $decorator->convertMeeting0'internalMeetingNotes' => $decorator->generateD€ OpportunitySync73];© SyncCrmEntities74Parameters: \PlaybookCategory$category+ SyncFieldsTrait./75// Only if they are creating a fresh engagementshoT WriteCrmTrait.pl76if (! $activity->hasCrmProviderId()) {Source:.../app/Services/Crm/Helpers/ActivityPlaybookTrait.phpD Utils77if (Sactivity->hasActualStartTime() && $activitD Webhook78Smetadata('startTime'] = $decorator->format@ RatchSvneCnllertn'79Smetadata["ondTime!]l= Sdeconaton->formatEndTime (Sactivity):Workspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)=laravel.logSF [jiminny@localhost]A HS_local [jiminny@localho:W4 console [QAI PROD] XA8M6AY4 console [PROD]A console (EU]Go jiminny0841A3 X4 A Vm migrations oim teams wherem crm_layouts !iM crm_layout_eiIM crm_fields Wim features;team_feature:m opportunitie:m teams;1.id, CASEWHENidFROMsocial.1 on u.id= sa.:1..n<->1: on t.L_id = 1052 andLUcauseaD-—211212213214215216217218— 219220221222223224225=226_227228229230231232- 233 V234IMaccounts wheiactivitiesW Windsurf Teams66:60UTF-8Co 4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45164
|
1621
|
17
|
2026-05-14T14:23:16.259006+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768596259_m1.jpg...
|
PhpStorm
|
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Search
matchesCrmType (ActivityPlaybookTrait .../a Search
matchesCrmType (ActivityPlaybookTrait .../app/Services/Crm/Helpers), public method
matchesCrmType (WriteCrmTraitTest .../tests/Unit/Services/Crm/Hubspot/ServiceTraits), public method
Choose Declaration...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Search","depth":1,"on_screen":false,"role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"matchesCrmType (ActivityPlaybookTrait .../app/Services/Crm/Helpers), public method","depth":4,"bounds":{"left":0.5069444,"top":0.69,"width":0.49166667,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"matchesCrmType (WriteCrmTraitTest .../tests/Unit/Services/Crm/Hubspot/ServiceTraits), public method","depth":4,"bounds":{"left":0.5069444,"top":0.71444446,"width":0.49166667,"height":0.024444444},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Choose Declaration","depth":1,"bounds":{"left":0.5138889,"top":0.6533333,"width":0.47777778,"height":0.036666665},"on_screen":true,"role_description":"text"}]...
|
7591294825150517262
|
981909621225921017
|
visual_change
|
accessibility
|
NULL
|
Search
matchesCrmType (ActivityPlaybookTrait .../a Search
matchesCrmType (ActivityPlaybookTrait .../app/Services/Crm/Helpers), public method
matchesCrmType (WriteCrmTraitTest .../tests/Unit/Services/Crm/Hubspot/ServiceTraits), public method
Choose Declaration...
|
45163
|
NULL
|
NULL
|
NULL
|
|
45165
|
1622
|
7
|
2026-05-14T14:23:16.741515+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768596741_m2.jpg...
|
PhpStorm
|
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Search
matchesCrmType (ActivityPlaybookTrait .../a Search
matchesCrmType (ActivityPlaybookTrait .../app/Services/Crm/Helpers), public method
matchesCrmType (WriteCrmTraitTest .../tests/Unit/Services/Crm/Hubspot/ServiceTraits), public method
Choose Declaration...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Search","depth":1,"on_screen":false,"role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"matchesCrmType (ActivityPlaybookTrait .../app/Services/Crm/Helpers), public method","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"matchesCrmType (WriteCrmTraitTest .../tests/Unit/Services/Crm/Hubspot/ServiceTraits), public method","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Choose Declaration","depth":1,"on_screen":true,"role_description":"text"}]...
|
7591294825150517262
|
981909621225921017
|
click
|
accessibility
|
NULL
|
Search
matchesCrmType (ActivityPlaybookTrait .../a Search
matchesCrmType (ActivityPlaybookTrait .../app/Services/Crm/Helpers), public method
matchesCrmType (WriteCrmTraitTest .../tests/Unit/Services/Crm/Hubspot/ServiceTraits), public method
Choose Declaration...
|
45162
|
NULL
|
NULL
|
NULL
|
|
45166
|
1621
|
18
|
2026-05-14T14:23:19.290478+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768599290_m1.jpg...
|
PhpStorm
|
faVsco.js – ActivityPlaybookTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorRun PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelpabl100% C•FV faVsco.jsv* #12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTest8• Thu 14 May 17:23:19* :QProject v© ReportController.php© AutomatedReportGenerated.php© TrackAutomatedReportGeneratedEvent.php:=custom.logPlaybackController.php> D CrmObjects© UserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.php© SendReportJob.php• DecorateActivity› Dummy©DeleteCrmEntityTrait.php© DeleteAccountJob.php© ImportActivityTypes.phpT WriteCrmTrait.phpT ActivityPlaybookTrait.php x› Helpers© AccountController.phpIntegrationAppTrait.phpE .env.stagingE.env© DetachActivityObject.phpv HubspotRematchActivityOnCrmObjectDetach.php© MatchActivityCrmData.php© Client.php© HubspotPaginationService.php18D AccountSyncStrate› D ActionsD ContactSyncStrate!17HandleHubspotRateLimit.phptrait ActivityPlaybookTraitAnalyzing….> DDTO18› O Fields› D JournalMetadata1920protected CrmHelperRepository ScrmHelperRepository;v OpportunitySyncSti> [ Concerns21public function getPlaybookFromActivity(Activity $activity): ?Playbook{...}© HubspotLastMor29© HubspotLastMor30© HubspotLastMor31© HubspotLastMor32/***/* Gets the playbook for the user© HubspotLastMor© HubspotSingleS:33protected function getPlaybook(User $user): ?Playbook{...}© HubspotSyncStr39© HubspotWebhocv D Pagination40protected function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory{...}© HubspotPaginati 44© PaginationConfig© PaginationState.45public function matchesCrmType(PlaybookCategory $category): bool> D ProspectSearchStre46› [ Redis.47v D ServiceTraits48€ OpportunitySync49$playbook = $category->getPlaybook():if ($playbook === null) {return false;© SyncCrmEntities50+ SyncFieldsTrait./51T WriteCrmTrait.pl52C Utils53C Webhook54SactivityField = $playbook->getActivityFieldO;if ($activityField === null) {return false;@ RatchSuneCollerto 55Workspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)W Windsurf Teams=laravel.logSF [jiminny@localhost]A HS_local [jiminny@localho:W4 console [QAI PROD] X4 console [PROD]A console (EU]DGo jiminny08A1A3X4 ^ V211m migrations oi212213214215216m teams wherem crm_layouts !M crm_layout_eiM crm_fields Wi217218219220m features;m team_feature:m opportunitie:221222m teams:223224I.id, CASE WHEN225226227228229230,id FROM social,on u.id = sa.:1..n<->1: on t._id = 1052 and231232233 /IMaccounts wheiactivities23445:21UTF-8C 4 spaces...
|
NULL
|
-8906165182920389014
|
NULL
|
visual_change
|
ocr
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorRun PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelpabl100% C•FV faVsco.jsv* #12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTest8• Thu 14 May 17:23:19* :QProject v© ReportController.php© AutomatedReportGenerated.php© TrackAutomatedReportGeneratedEvent.php:=custom.logPlaybackController.php> D CrmObjects© UserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.php© SendReportJob.php• DecorateActivity› Dummy©DeleteCrmEntityTrait.php© DeleteAccountJob.php© ImportActivityTypes.phpT WriteCrmTrait.phpT ActivityPlaybookTrait.php x› Helpers© AccountController.phpIntegrationAppTrait.phpE .env.stagingE.env© DetachActivityObject.phpv HubspotRematchActivityOnCrmObjectDetach.php© MatchActivityCrmData.php© Client.php© HubspotPaginationService.php18D AccountSyncStrate› D ActionsD ContactSyncStrate!17HandleHubspotRateLimit.phptrait ActivityPlaybookTraitAnalyzing….> DDTO18› O Fields› D JournalMetadata1920protected CrmHelperRepository ScrmHelperRepository;v OpportunitySyncSti> [ Concerns21public function getPlaybookFromActivity(Activity $activity): ?Playbook{...}© HubspotLastMor29© HubspotLastMor30© HubspotLastMor31© HubspotLastMor32/***/* Gets the playbook for the user© HubspotLastMor© HubspotSingleS:33protected function getPlaybook(User $user): ?Playbook{...}© HubspotSyncStr39© HubspotWebhocv D Pagination40protected function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory{...}© HubspotPaginati 44© PaginationConfig© PaginationState.45public function matchesCrmType(PlaybookCategory $category): bool> D ProspectSearchStre46› [ Redis.47v D ServiceTraits48€ OpportunitySync49$playbook = $category->getPlaybook():if ($playbook === null) {return false;© SyncCrmEntities50+ SyncFieldsTrait./51T WriteCrmTrait.pl52C Utils53C Webhook54SactivityField = $playbook->getActivityFieldO;if ($activityField === null) {return false;@ RatchSuneCollerto 55Workspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)W Windsurf Teams=laravel.logSF [jiminny@localhost]A HS_local [jiminny@localho:W4 console [QAI PROD] X4 console [PROD]A console (EU]DGo jiminny08A1A3X4 ^ V211m migrations oi212213214215216m teams wherem crm_layouts !M crm_layout_eiM crm_fields Wi217218219220m features;m team_feature:m opportunitie:221222m teams:223224I.id, CASE WHEN225226227228229230,id FROM social,on u.id = sa.:1..n<->1: on t._id = 1052 and231232233 /IMaccounts wheiactivities23445:21UTF-8C 4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45167
|
1622
|
8
|
2026-05-14T14:23:20.679248+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768600679_m2.jpg...
|
PhpStorm
|
faVsco.js – ActivityPlaybookTrait.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
ActivityFilesLaterJiminny... ~@ jiminny-x-integrat ActivityFilesLaterJiminny... ~@ jiminny-x-integrati& platform-inner-team© Channels# ai-chapter# alertsi backend# bugscontusion-cllnia# curiosity_lab# engineering# general# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the people of iimi..• Direct messages8. A...• A3 62P. Galya Dimitrova Mvasil VasilevA. Stefka Stoyanova%: Todor Stamatovf. Mario GeorgievP. Nikolay Ivanov2o James Graham "2. Stoyan Tanev. Steliyan Georgiev. Petko Kashinski*. Lukas Kovali...a: Apps® ToastS lira Gloud6 Huddle with Aneliya Angelova& R. Aneliya Angelova •• Messagest Add canvasur FilesAneliya Angelova 2:30 PMЛукаш, само при Сейлсфорс и Хубспот се синкваха активити типовете, когато се направи плейбук,нали?пои останалите скМі тоябва оъчно ла се въвелат.Lukas Kovalik 2:47 PMтряова да се за всички, някьде не се ли попьлвапри зохо май беше hardcoded но май и там си връщаха две категорииAneliya Angelova @ 4:00 PMЛукаш имаш ли време да се чуем за тестването на https://jiminny.atlassian.net/browse/JY-20725X Preview insackt[HubSpot] Optimise CRM rematctC OpenReady for QA- MediumAA Aneliya AngelovaAs of today at 4:00 PM RetreshOpen in Jira+ SummariseLukas Kovalik 4:02 PMzean-iurwiniareelYou joined the huddle LIVE 4:06 PMAneliva Angelova is here toolAneliya Angelova 4:44 PM11512582Lukas Kovalik O 5:16 PMcurl --location "httns:ani.hubani.com/crm/v3/obiects/contacts/search--header 'Content-Type: application/json'\--header "Authorization: BearerC|Kvma?iMx7OINOM8kOFwrAowAcAkUAhIR24?A05D?xiVrIYMILuP6SA01Kwcmhr=n.v2 hytorDniMvDalYck-CMalovolNOM9kOFwrAUAcrkcaws-ThwRARIRAOF-ATFSAOFRAO-ROIEBAQUBEggBAQEBAYICFIґ7Au8O2Nwal9YW2CbCCxYvwngmSgNldTFSAFoAYABoAHAAeAA'\--data "{ "limit", 13'Message Aneliva Angelova+ AaIAl Notes: OffLeave~ (Q SearchDate ModifiedYesterdav at 13.35Yesterday at 13:34Yesterday at 13.33Yesterdav at 13:33Yesterday at 13:32Yesterday at 13:32Yesterday at 13:31Yesterday at 13.30Yesterday at 13:29Yesterday at 13:29resterday at 15.2oYesterdav at 13:28Yesterday at 13:27Yesterday at 13:26Yesterday at 13:26resterday at 13-40Yesterdav at 13:24Yesterday at 13:24Yesterday at 13:23Yesterday at 13.20Yesterdav at 13:20Yesterday at 13:19Yesterday at 13:19Yesterday at 13:18Yesterday at 13:18Yesterday at 13.17Yecterdav at 12:16Yesterday at 13:16Yesterday at 13.10Yesterdav at 13:15Yesterday at 13:14Yesterday at 13:13Yecterdav at 12:12Yesterday at 13:12Yesterday at 13.12Yesterdav at 13:11Yesterday at 13:11Yesterday at 13:10Yesterday at 13:09Yesterday at 13:09Yesterday at 13.08Yocterdav at 12:08Yesterday at 13:07Yesterdav at 13:0616 K:MPEG-4 movie17 KBMPEG-4 movie29 KBMPEG-4 movieMPEG-4 movie12 KE9 KBMPE0"4 movie8 K:37 KBMPEG-4 movie10 KBIKBnMP2G-4 movie8 KBMPEG-4 movie9 KB8 KBMPEG-4 movie72 K:14 KBMPEG-4 movie13 KBMPEG-4 movie18 KBMPEG-4 movie12 KB10 KBMPEG-4 movie16 KEMPEG-4 movie6 KB MPEG-4 movie6 KB12 KB|MPEG-4 movie23 KBMPEG-4 movieMPEG-4 movie6KEMPEG-4 movie11 KBMPEG-4 movie11 KBMPEG-4 movie20 K:MPEG-4 movie34 KBMPEG-4 movie10 KB7 KBMPEG-4 movie5KPMPEG-4 movie11 KBMPEG-4 movie26 KBMPEG-4 movie111 K:MPEG-4 movie102 KBMPEG-4 movie88 KB59 KBO9 KRMPEG-4 movieMDEG-A movie97 KB66 KBMPEG-4 movie44K:MPSG-A movid93 KBMPEG-4 movie78 KB50 KB59 KPMPEG-4 movieMPEG-4 movie27 KBMPEG-4 movie12 K:32 KBMPEG-4 movie17 KB19K8MPEG-4 movie• 0FavouritesE jiminny© Recents* ApplicationsiCloudiCloud Drive228 Sync folderQ DXP4800PLUS-B5FA@ Network|• CRMI• Orange• Red• Yellow• Green• Purple•) All lags..lohlDownloadsNameLoom.pkgAlfred copv.alfredoreterencesB KeychronAssist-1.0.2 (1).dmgA Keychron Assist-1.0.2.dmgmazanoke-images-yWJo.zioPhotos-3-001.zipD Transcript.pdf→mage U.loge1 Orioninstaller.dmaimage.jpg- image (2).1pc• ПО-22221726037035-004-001_ORGES.pdf• %D0%9F%D0%9E-22221726037035-004-001_archive.zipПO-22221726037035-004-001_archive (1).zip• repon 4).XmAltred copy2.altredoreterences05012026_0000000026574472_ SWIFT_OB70501260015890.pdf27022026_0000000026574472_SWIFT_OB72702260049200.pdf= 03042026 [CREDIT_CARD] SWIFT [CREDIT_CARD].001B reporti) xm=pdt.odipdf-1.pdfD pdf-2.pdf-pdf-5.pd1= ndf-1 ndipdf-3.pdfB Rovaix Famly Treo gedbitwarden export 20251031122528.isonKoválik Family Tree.zip*macOS Storage_Cleanup.mdal favicon icofirst_aid_notes_complete.docxrenortl2).esvconfig.ymlIteration run Search HS.postman_collection.jsonm licence bettertouchtoalMariusHosting Config.json1ooks-891a6503-bbb7-4b2b-9c3.csv•Alfredmazanoke-images-YWJ6ана Ковалик.jpg•искане даниел Ковалик..pa• Фактура Март Даниел Ковалик.jрс• Фактура Април Даниел Ковалик.jpg• Dhotac 2.001Q SearchKind00,4 MDinstdlle..dckage55.9 MBAlfred...ferences10,1 MBDisk Image10,1 MBDisk ImageIL MBLiP archive6,6 MBZIP archive2,5 MB PDF Document2,5 MBJreo lmage2.2 MBDisk Image2,2 MB PDF Document2 MBJPEG image1,9 MBJPEG imaqe192 KBPDF Document140 KbZIP archive148 KВZIP archivel148 KBZIP archive122 KBXML document111 KBAlfred...ferences94 KBPDF Document92 KBPDr DocumentK:91 KB91 KB30 KB29 KBPDF DocumentXML documentPDF Document28 KBPDF Document28 KR28 KB27 KB14 KB11 KB6 KB6KBPDE DocumentDocumentJSONICSV DocumentZIP archiveMarkdo…..ument5KRWindo...n image4 KB3 KBword ..cumentCSV Document2 KBYAML document1 KBcSV Document928 byteshttlicence183 bytesZero butesJSONAlfred.. ferencesZero bytesFolde1,9 MB1,8 MB17 MB1,7 MBColdorJPEG imageJPEG ImageIPEG imadeJPEG imageColdo1 of 58 selected, 9.63 GB availabld• Inu 14 May 1/.23.20Date AddedIs Mdl ZUzo dl 19:4530 Jan 2026 at 12:3617 Mar 2026 at 20:2717 Mar 2026 at 20:26Z3 Aor 2020 al 13:0229 Jan 2026 at 15:2019 Dec 2025 at 10:1619 Dec 2025 at 12:238 Aor 2026 at 20:3519 Dec 2025 at 10:2919 Dec 2025 at 12:1819 Dec 2025 at 12:4026 Mar 2026 at 11:2410 May 2026 at 13:5326 Mar 2026 at 11:2426 Mar 2026 at 11:2426 Mar 2026 at 11:2310 May 2026 at 14:3730 Jan 2026 at 12:3713 Feb 2026 at 11:54Z3 Apr 2026 at 13.0823 Aor 2026 at 13:0810 May 2026 at 13:5410 May 2026 at 14:3710 May 2026 at 13:4910 May 2026 at 13:5010 May 2026 at 13:5110 Mav 2026 at 12:51110 May 2026 at 13:5019 Dec 2025 at 11:3431 Oct 2025 at 12:2525 Nov 2025 at 17:596 Mar 2026 at 11.2224 Anr 2026 at 16:5220 Oct 2025 at 11:0218 Mar 2026 at 15:29• Mav 2026 at 11:09129 Oct 2025 at 19:329 May 2026 at 10:0418 Mar 2026 at 11:5510 May 2026 at 13:5712 Jun 2025 at 19:0430 Jan 2026 at 12:3630 Jan 2026 at 12:3616 Oct 2025 aт 16:0123 Apr 2026 at 13:0223 Apr 2026 at 13:0223 Apr 2026 at 13:0222 Anr 2026 at 12:0223 Apr 2026 at 13:0229 Jan 2026 at 15:20...
|
NULL
|
-3609530195320133031
|
NULL
|
click
|
ocr
|
NULL
|
ActivityFilesLaterJiminny... ~@ jiminny-x-integrat ActivityFilesLaterJiminny... ~@ jiminny-x-integrati& platform-inner-team© Channels# ai-chapter# alertsi backend# bugscontusion-cllnia# curiosity_lab# engineering# general# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the people of iimi..• Direct messages8. A...• A3 62P. Galya Dimitrova Mvasil VasilevA. Stefka Stoyanova%: Todor Stamatovf. Mario GeorgievP. Nikolay Ivanov2o James Graham "2. Stoyan Tanev. Steliyan Georgiev. Petko Kashinski*. Lukas Kovali...a: Apps® ToastS lira Gloud6 Huddle with Aneliya Angelova& R. Aneliya Angelova •• Messagest Add canvasur FilesAneliya Angelova 2:30 PMЛукаш, само при Сейлсфорс и Хубспот се синкваха активити типовете, когато се направи плейбук,нали?пои останалите скМі тоябва оъчно ла се въвелат.Lukas Kovalik 2:47 PMтряова да се за всички, някьде не се ли попьлвапри зохо май беше hardcoded но май и там си връщаха две категорииAneliya Angelova @ 4:00 PMЛукаш имаш ли време да се чуем за тестването на https://jiminny.atlassian.net/browse/JY-20725X Preview insackt[HubSpot] Optimise CRM rematctC OpenReady for QA- MediumAA Aneliya AngelovaAs of today at 4:00 PM RetreshOpen in Jira+ SummariseLukas Kovalik 4:02 PMzean-iurwiniareelYou joined the huddle LIVE 4:06 PMAneliva Angelova is here toolAneliya Angelova 4:44 PM11512582Lukas Kovalik O 5:16 PMcurl --location "httns:ani.hubani.com/crm/v3/obiects/contacts/search--header 'Content-Type: application/json'\--header "Authorization: BearerC|Kvma?iMx7OINOM8kOFwrAowAcAkUAhIR24?A05D?xiVrIYMILuP6SA01Kwcmhr=n.v2 hytorDniMvDalYck-CMalovolNOM9kOFwrAUAcrkcaws-ThwRARIRAOF-ATFSAOFRAO-ROIEBAQUBEggBAQEBAYICFIґ7Au8O2Nwal9YW2CbCCxYvwngmSgNldTFSAFoAYABoAHAAeAA'\--data "{ "limit", 13'Message Aneliva Angelova+ AaIAl Notes: OffLeave~ (Q SearchDate ModifiedYesterdav at 13.35Yesterday at 13:34Yesterday at 13.33Yesterdav at 13:33Yesterday at 13:32Yesterday at 13:32Yesterday at 13:31Yesterday at 13.30Yesterday at 13:29Yesterday at 13:29resterday at 15.2oYesterdav at 13:28Yesterday at 13:27Yesterday at 13:26Yesterday at 13:26resterday at 13-40Yesterdav at 13:24Yesterday at 13:24Yesterday at 13:23Yesterday at 13.20Yesterdav at 13:20Yesterday at 13:19Yesterday at 13:19Yesterday at 13:18Yesterday at 13:18Yesterday at 13.17Yecterdav at 12:16Yesterday at 13:16Yesterday at 13.10Yesterdav at 13:15Yesterday at 13:14Yesterday at 13:13Yecterdav at 12:12Yesterday at 13:12Yesterday at 13.12Yesterdav at 13:11Yesterday at 13:11Yesterday at 13:10Yesterday at 13:09Yesterday at 13:09Yesterday at 13.08Yocterdav at 12:08Yesterday at 13:07Yesterdav at 13:0616 K:MPEG-4 movie17 KBMPEG-4 movie29 KBMPEG-4 movieMPEG-4 movie12 KE9 KBMPE0"4 movie8 K:37 KBMPEG-4 movie10 KBIKBnMP2G-4 movie8 KBMPEG-4 movie9 KB8 KBMPEG-4 movie72 K:14 KBMPEG-4 movie13 KBMPEG-4 movie18 KBMPEG-4 movie12 KB10 KBMPEG-4 movie16 KEMPEG-4 movie6 KB MPEG-4 movie6 KB12 KB|MPEG-4 movie23 KBMPEG-4 movieMPEG-4 movie6KEMPEG-4 movie11 KBMPEG-4 movie11 KBMPEG-4 movie20 K:MPEG-4 movie34 KBMPEG-4 movie10 KB7 KBMPEG-4 movie5KPMPEG-4 movie11 KBMPEG-4 movie26 KBMPEG-4 movie111 K:MPEG-4 movie102 KBMPEG-4 movie88 KB59 KBO9 KRMPEG-4 movieMDEG-A movie97 KB66 KBMPEG-4 movie44K:MPSG-A movid93 KBMPEG-4 movie78 KB50 KB59 KPMPEG-4 movieMPEG-4 movie27 KBMPEG-4 movie12 K:32 KBMPEG-4 movie17 KB19K8MPEG-4 movie• 0FavouritesE jiminny© Recents* ApplicationsiCloudiCloud Drive228 Sync folderQ DXP4800PLUS-B5FA@ Network|• CRMI• Orange• Red• Yellow• Green• Purple•) All lags..lohlDownloadsNameLoom.pkgAlfred copv.alfredoreterencesB KeychronAssist-1.0.2 (1).dmgA Keychron Assist-1.0.2.dmgmazanoke-images-yWJo.zioPhotos-3-001.zipD Transcript.pdf→mage U.loge1 Orioninstaller.dmaimage.jpg- image (2).1pc• ПО-22221726037035-004-001_ORGES.pdf• %D0%9F%D0%9E-22221726037035-004-001_archive.zipПO-22221726037035-004-001_archive (1).zip• repon 4).XmAltred copy2.altredoreterences05012026_0000000026574472_ SWIFT_OB70501260015890.pdf27022026_0000000026574472_SWIFT_OB72702260049200.pdf= 03042026 [CREDIT_CARD] SWIFT [CREDIT_CARD].001B reporti) xm=pdt.odipdf-1.pdfD pdf-2.pdf-pdf-5.pd1= ndf-1 ndipdf-3.pdfB Rovaix Famly Treo gedbitwarden export 20251031122528.isonKoválik Family Tree.zip*macOS Storage_Cleanup.mdal favicon icofirst_aid_notes_complete.docxrenortl2).esvconfig.ymlIteration run Search HS.postman_collection.jsonm licence bettertouchtoalMariusHosting Config.json1ooks-891a6503-bbb7-4b2b-9c3.csv•Alfredmazanoke-images-YWJ6ана Ковалик.jpg•искане даниел Ковалик..pa• Фактура Март Даниел Ковалик.jрс• Фактура Април Даниел Ковалик.jpg• Dhotac 2.001Q SearchKind00,4 MDinstdlle..dckage55.9 MBAlfred...ferences10,1 MBDisk Image10,1 MBDisk ImageIL MBLiP archive6,6 MBZIP archive2,5 MB PDF Document2,5 MBJreo lmage2.2 MBDisk Image2,2 MB PDF Document2 MBJPEG image1,9 MBJPEG imaqe192 KBPDF Document140 KbZIP archive148 KВZIP archivel148 KBZIP archive122 KBXML document111 KBAlfred...ferences94 KBPDF Document92 KBPDr DocumentK:91 KB91 KB30 KB29 KBPDF DocumentXML documentPDF Document28 KBPDF Document28 KR28 KB27 KB14 KB11 KB6 KB6KBPDE DocumentDocumentJSONICSV DocumentZIP archiveMarkdo…..ument5KRWindo...n image4 KB3 KBword ..cumentCSV Document2 KBYAML document1 KBcSV Document928 byteshttlicence183 bytesZero butesJSONAlfred.. ferencesZero bytesFolde1,9 MB1,8 MB17 MB1,7 MBColdorJPEG imageJPEG ImageIPEG imadeJPEG imageColdo1 of 58 selected, 9.63 GB availabld• Inu 14 May 1/.23.20Date AddedIs Mdl ZUzo dl 19:4530 Jan 2026 at 12:3617 Mar 2026 at 20:2717 Mar 2026 at 20:26Z3 Aor 2020 al 13:0229 Jan 2026 at 15:2019 Dec 2025 at 10:1619 Dec 2025 at 12:238 Aor 2026 at 20:3519 Dec 2025 at 10:2919 Dec 2025 at 12:1819 Dec 2025 at 12:4026 Mar 2026 at 11:2410 May 2026 at 13:5326 Mar 2026 at 11:2426 Mar 2026 at 11:2426 Mar 2026 at 11:2310 May 2026 at 14:3730 Jan 2026 at 12:3713 Feb 2026 at 11:54Z3 Apr 2026 at 13.0823 Aor 2026 at 13:0810 May 2026 at 13:5410 May 2026 at 14:3710 May 2026 at 13:4910 May 2026 at 13:5010 May 2026 at 13:5110 Mav 2026 at 12:51110 May 2026 at 13:5019 Dec 2025 at 11:3431 Oct 2025 at 12:2525 Nov 2025 at 17:596 Mar 2026 at 11.2224 Anr 2026 at 16:5220 Oct 2025 at 11:0218 Mar 2026 at 15:29• Mav 2026 at 11:09129 Oct 2025 at 19:329 May 2026 at 10:0418 Mar 2026 at 11:5510 May 2026 at 13:5712 Jun 2025 at 19:0430 Jan 2026 at 12:3630 Jan 2026 at 12:3616 Oct 2025 aт 16:0123 Apr 2026 at 13:0223 Apr 2026 at 13:0223 Apr 2026 at 13:0222 Anr 2026 at 12:0223 Apr 2026 at 13:0229 Jan 2026 at 15:20...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45169
|
1621
|
19
|
2026-05-14T14:23:22.596619+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768602596_m1.jpg...
|
PhpStorm
|
faVsco.js – ActivityPlaybookTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorRun PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelpabl100% <7•FV faVsco.jsv#12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTestv8• Thu 14 May 17:23:22* :QProject v© ReportController.php© AutomatedReportGenerated.php:= custom.logTrackAutomatedReportGeneratedEvent.phpPlaybackController.php> D CrmObjectsUserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.php© SendReportJob.php=laravel.logO DecorateActivity› D Dummy© DeleteCrmEntityTrait.phpDeleteAccountJob.php© ImportActivityTypes.phpT WriteCrmTrait.phpT ActivityPlaybookTrait.php xSF [jiminny@localhost]v D Helpers.© AccountController.phpIntegrationAppTrait.php.env.staging.env© DetachActivityObject.phpA HS_local [jiminny@localho:WT, ActivityPlaybookTraRematchActivityOnCrmObjectDetach.phpMatchActivityCrmData.php© Client.php© HubspotPaginationService.php4 console [QAI PROD] X18© Arraylterator.php+ ConnectionState Tra© CrmHelperReposito17© FilterJoinedParticipHandleHubspotRateLimit.phptrait ActivityPlaybookTrait8 usages4 console [PROD]A console (EU]T OpportunitySyncab 19protected CrmHelperRepository $crmHelperRepository;v D Hubspot20D AccountSyncStrate21public function getPlaybookFromActivity(Activity Sactivity): ?Playbook{...}› D Actions29D ContactSyncStrate!30> ODTO31› OJ Fields32/***/* Gets the playbook for the user› D Journal33protected function getPlaybook(User $user): ?Playbook{... }Metadata39OpportunitySyncStiprotected function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory{..}> O Concerns44© HubspotLastMor45public function matchesCrmType(PlaybookCategory $category): bool© HubspotLastMor46© HubspotLastMor47$playbook = $category->getPlaybook() :||Cascade 28 XlCommand &81© HubspotLastMor 48© HubspotLastMor49return talse;© HubspotSingleS:50© HubspotSyncStr 51© HubspotWebhoc 52v D Pagination53© HubspotPaginati 54SactivityField = $playbook->getActivityField:if ($activityField === null) {return false;© PaginationConfig 55© PaginationState.56> O ProspectSearchStra• Redis58// Check the field values for a matching option.return $this->crmHelperRepository->existsActivityFieldValueByName($activityField, $category->getName()) 233 vv O ServiceTraits59D Go jiminny0841A3 X4 A Vm migrations oim teams wherem crm_layouts !iM crm_layout_eiIM crm_fields Wim features;team_feature:m opportunitie:teams:1.id, CASE WHENidFROMsocial.on u.id= sa.:1..n<->1: on t._id = 1052 andIMaccounts wheiactivities(). OnnortunitvSunr 4AWorkspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)W Windsurf Teams47:46 (37 chars)UTF-8C 4 spaces...
|
NULL
|
6828021430120088485
|
NULL
|
click
|
ocr
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorRun PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelpabl100% <7•FV faVsco.jsv#12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTestv8• Thu 14 May 17:23:22* :QProject v© ReportController.php© AutomatedReportGenerated.php:= custom.logTrackAutomatedReportGeneratedEvent.phpPlaybackController.php> D CrmObjectsUserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.php© SendReportJob.php=laravel.logO DecorateActivity› D Dummy© DeleteCrmEntityTrait.phpDeleteAccountJob.php© ImportActivityTypes.phpT WriteCrmTrait.phpT ActivityPlaybookTrait.php xSF [jiminny@localhost]v D Helpers.© AccountController.phpIntegrationAppTrait.php.env.staging.env© DetachActivityObject.phpA HS_local [jiminny@localho:WT, ActivityPlaybookTraRematchActivityOnCrmObjectDetach.phpMatchActivityCrmData.php© Client.php© HubspotPaginationService.php4 console [QAI PROD] X18© Arraylterator.php+ ConnectionState Tra© CrmHelperReposito17© FilterJoinedParticipHandleHubspotRateLimit.phptrait ActivityPlaybookTrait8 usages4 console [PROD]A console (EU]T OpportunitySyncab 19protected CrmHelperRepository $crmHelperRepository;v D Hubspot20D AccountSyncStrate21public function getPlaybookFromActivity(Activity Sactivity): ?Playbook{...}› D Actions29D ContactSyncStrate!30> ODTO31› OJ Fields32/***/* Gets the playbook for the user› D Journal33protected function getPlaybook(User $user): ?Playbook{... }Metadata39OpportunitySyncStiprotected function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory{..}> O Concerns44© HubspotLastMor45public function matchesCrmType(PlaybookCategory $category): bool© HubspotLastMor46© HubspotLastMor47$playbook = $category->getPlaybook() :||Cascade 28 XlCommand &81© HubspotLastMor 48© HubspotLastMor49return talse;© HubspotSingleS:50© HubspotSyncStr 51© HubspotWebhoc 52v D Pagination53© HubspotPaginati 54SactivityField = $playbook->getActivityField:if ($activityField === null) {return false;© PaginationConfig 55© PaginationState.56> O ProspectSearchStra• Redis58// Check the field values for a matching option.return $this->crmHelperRepository->existsActivityFieldValueByName($activityField, $category->getName()) 233 vv O ServiceTraits59D Go jiminny0841A3 X4 A Vm migrations oim teams wherem crm_layouts !iM crm_layout_eiIM crm_fields Wim features;team_feature:m opportunitie:teams:1.id, CASE WHENidFROMsocial.on u.id= sa.:1..n<->1: on t._id = 1052 andIMaccounts wheiactivities(). OnnortunitvSunr 4AWorkspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)W Windsurf Teams47:46 (37 chars)UTF-8C 4 spaces...
|
45166
|
NULL
|
NULL
|
NULL
|
|
45168
|
1622
|
9
|
2026-05-14T14:23:24.414752+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768604414_m2.jpg...
|
PhpStorm
|
faVsco.js – ActivityPlaybookTrait.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Integrations\PlaybookResolver;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\User;
/**
* Playbook related methods commonly used in all CRMs.
*/
trait ActivityPlaybookTrait
{
protected CrmHelperRepository $crmHelperRepository;
public function getPlaybookFromActivity(Activity $activity): ?Playbook
{
if ($activity->hasActivityType() && $activity->getActivityType()->hasPlaybook()) {
return $activity->getActivityType()->getPlaybook();
}
return $this->getPlaybook($activity->getUser());
}
/**
* Gets the playbook for the user
*/
protected function getPlaybook(User $user): ?Playbook
{
$playbookResolver = app(PlaybookResolver::class);
return $playbookResolver->resolvePlaybookByUser($user);
}
protected function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
return $this->crmHelperRepository->getPlaybookCategory($playbook, $categoryName);
}
public function matchesCrmType(PlaybookCategory $category): bool
{
$playbook = $category->getPlaybook();
if ($playbook === null) {
return false;
}
$activityField = $playbook->getActivityField();
if ($activityField === null) {
return false;
}
// Check the field values for a matching option.
return $this->crmHelperRepository->existsActivityFieldValueByName($activityField, $category->getName());
}
public function getCrmType(?PlaybookCategory $category): ?FieldValue
{
if ($category === null) {
return null;
}
$playbook = $category->getPlaybook();
if ($playbook === null) {
return null;
}
$activityField = $playbook->getActivityField();
if ($activityField === null) {
return null;
}
return $this->crmHelperRepository->getActivityFieldValueByName($activityField, $category->getName());
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.2962101,"top":1.0,"width":0.03856383,"height":-0.019952059},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#12066 on JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.33477393,"top":1.0,"width":0.122340426,"height":-0.019952059},"on_screen":true,"help_text":"Pull request #12066 exists for current branch JY-20725-handle-HS-search-rate-limit, 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.5731383,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HandleHubspotRateLimitTest","depth":6,"bounds":{"left":0.5884308,"top":1.0,"width":0.076130316,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.66456115,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.67586434,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.6871675,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.7150931,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.72639626,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.73769945,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Integrations\\PlaybookResolver;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\User;\n\n/**\n * Playbook related methods commonly used in all CRMs.\n */\ntrait ActivityPlaybookTrait\n{\n protected CrmHelperRepository $crmHelperRepository;\n\n public function getPlaybookFromActivity(Activity $activity): ?Playbook\n {\n if ($activity->hasActivityType() && $activity->getActivityType()->hasPlaybook()) {\n return $activity->getActivityType()->getPlaybook();\n }\n\n return $this->getPlaybook($activity->getUser());\n }\n\n /**\n * Gets the playbook for the user\n */\n protected function getPlaybook(User $user): ?Playbook\n {\n $playbookResolver = app(PlaybookResolver::class);\n\n return $playbookResolver->resolvePlaybookByUser($user);\n }\n\n protected function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n return $this->crmHelperRepository->getPlaybookCategory($playbook, $categoryName);\n }\n\n public function matchesCrmType(PlaybookCategory $category): bool\n {\n $playbook = $category->getPlaybook();\n if ($playbook === null) {\n return false;\n }\n\n $activityField = $playbook->getActivityField();\n if ($activityField === null) {\n return false;\n }\n\n // Check the field values for a matching option.\n return $this->crmHelperRepository->existsActivityFieldValueByName($activityField, $category->getName());\n }\n\n public function getCrmType(?PlaybookCategory $category): ?FieldValue\n {\n if ($category === null) {\n return null;\n }\n\n $playbook = $category->getPlaybook();\n if ($playbook === null) {\n return null;\n }\n\n $activityField = $playbook->getActivityField();\n if ($activityField === null) {\n return null;\n }\n\n return $this->crmHelperRepository->getActivityFieldValueByName($activityField, $category->getName());\n }\n}","depth":4,"bounds":{"left":0.3899601,"top":0.8667199,"width":0.29787233,"height":0.1332801},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Integrations\\PlaybookResolver;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\User;\n\n/**\n * Playbook related methods commonly used in all CRMs.\n */\ntrait ActivityPlaybookTrait\n{\n protected CrmHelperRepository $crmHelperRepository;\n\n public function getPlaybookFromActivity(Activity $activity): ?Playbook\n {\n if ($activity->hasActivityType() && $activity->getActivityType()->hasPlaybook()) {\n return $activity->getActivityType()->getPlaybook();\n }\n\n return $this->getPlaybook($activity->getUser());\n }\n\n /**\n * Gets the playbook for the user\n */\n protected function getPlaybook(User $user): ?Playbook\n {\n $playbookResolver = app(PlaybookResolver::class);\n\n return $playbookResolver->resolvePlaybookByUser($user);\n }\n\n protected function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n return $this->crmHelperRepository->getPlaybookCategory($playbook, $categoryName);\n }\n\n public function matchesCrmType(PlaybookCategory $category): bool\n {\n $playbook = $category->getPlaybook();\n if ($playbook === null) {\n return false;\n }\n\n $activityField = $playbook->getActivityField();\n if ($activityField === null) {\n return false;\n }\n\n // Check the field values for a matching option.\n return $this->crmHelperRepository->existsActivityFieldValueByName($activityField, $category->getName());\n }\n\n public function getCrmType(?PlaybookCategory $category): ?FieldValue\n {\n if ($category === null) {\n return null;\n }\n\n $playbook = $category->getPlaybook();\n if ($playbook === null) {\n return null;\n }\n\n $activityField = $playbook->getActivityField();\n if ($activityField === null) {\n return null;\n }\n\n return $this->crmHelperRepository->getActivityFieldValueByName($activityField, $category->getName());\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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"on_screen":false,"is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-5562972003945315574
|
-1468682120858978166
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Integrations\PlaybookResolver;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\User;
/**
* Playbook related methods commonly used in all CRMs.
*/
trait ActivityPlaybookTrait
{
protected CrmHelperRepository $crmHelperRepository;
public function getPlaybookFromActivity(Activity $activity): ?Playbook
{
if ($activity->hasActivityType() && $activity->getActivityType()->hasPlaybook()) {
return $activity->getActivityType()->getPlaybook();
}
return $this->getPlaybook($activity->getUser());
}
/**
* Gets the playbook for the user
*/
protected function getPlaybook(User $user): ?Playbook
{
$playbookResolver = app(PlaybookResolver::class);
return $playbookResolver->resolvePlaybookByUser($user);
}
protected function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
return $this->crmHelperRepository->getPlaybookCategory($playbook, $categoryName);
}
public function matchesCrmType(PlaybookCategory $category): bool
{
$playbook = $category->getPlaybook();
if ($playbook === null) {
return false;
}
$activityField = $playbook->getActivityField();
if ($activityField === null) {
return false;
}
// Check the field values for a matching option.
return $this->crmHelperRepository->existsActivityFieldValueByName($activityField, $category->getName());
}
public function getCrmType(?PlaybookCategory $category): ?FieldValue
{
if ($category === null) {
return null;
}
$playbook = $category->getPlaybook();
if ($playbook === null) {
return null;
}
$activityField = $playbook->getActivityField();
if ($activityField === null) {
return null;
}
return $this->crmHelperRepository->getActivityFieldValueByName($activityField, $category->getName());
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results...
|
45167
|
NULL
|
NULL
|
NULL
|
|
45170
|
1621
|
20
|
2026-05-14T14:23:25.855940+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768605855_m1.jpg...
|
PhpStorm
|
faVsco.js – ActivityPlaybookTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Integrations\PlaybookResolver;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\User;
/**
* Playbook related methods commonly used in all CRMs.
*/
trait ActivityPlaybookTrait
{
protected CrmHelperRepository $crmHelperRepository;
public function getPlaybookFromActivity(Activity $activity): ?Playbook
{
if ($activity->hasActivityType() && $activity->getActivityType()->hasPlaybook()) {
return $activity->getActivityType()->getPlaybook();
}
return $this->getPlaybook($activity->getUser());
}
/**
* Gets the playbook for the user
*/
protected function getPlaybook(User $user): ?Playbook
{
$playbookResolver = app(PlaybookResolver::class);
return $playbookResolver->resolvePlaybookByUser($user);
}
protected function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
return $this->crmHelperRepository->getPlaybookCategory($playbook, $categoryName);
}
public function matchesCrmType(PlaybookCategory $category): bool
{
$playbook = $category->getPlaybook();
if ($playbook === null) {
return false;
}
$activityField = $playbook->getActivityField();
if ($activityField === null) {
return false;
}
// Check the field values for a matching option.
return $this->crmHelperRepository->existsActivityFieldValueByName($activityField, $category->getName());
}
public function getCrmType(?PlaybookCategory $category): ?FieldValue
{
if ($category === null) {
return null;
}
$playbook = $category->getPlaybook();
if ($playbook === null) {
return null;
}
$activityField = $playbook->getActivityField();
if ($activityField === null) {
return null;
}
return $this->crmHelperRepository->getActivityFieldValueByName($activityField, $category->getName());
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
8
1
3
4
Previous Highlighted Error
Next Highlighted Error
SELECT
# DISTINCT
opp.id as opp_id, opp.uuid, opp.name,
# COUNT(dr.id) AS deal_risk_count,
# dr.id,
# cfd.value,
usr.name AS owner_name,
opp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,
usr.uuid as owner_uuid,
usr.photo_path as owner_photo,
usr.id AS owner_id,
jt.name as owner_job,
opp.stage_id, opp.stage_updated_at,
acc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,
rt.business_process_id AS pipeline_id
FROM opportunities opp
LEFT JOIN record_types rt ON opp.record_type_id = rt.id
LEFT JOIN users usr ON opp.user_id = usr.id
LEFT JOIN accounts acc ON opp.account_id = acc.id
LEFT JOIN job_titles jt ON usr.job_title_id = jt.id
# LEFT JOIN group_deal_risk_types gdrt ON gdrt.group_id = usr.group_id
# LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id AND dr.group_deal_risk_type_id = gdrt.id
LEFT JOIN crm_field_data cfd ON (cfd.object_id = opp.id and cfd.crm_field_id = 66814)
WHERE opp.user_id IS NOT NULL
AND opp.deleted_at IS NULL
AND opp.is_closed = 1
AND opp.is_won = 0
AND opp.close_date >= '2024-01-01 00:00:00'
AND opp.close_date <= '2024-12-31 23:59:59'
AND usr.team_id = 1
# and opp.id = 4823179
GROUP BY opp.id, cfd.value
ORDER BY
# cfd.value
CAST(cfd.value AS UNSIGNED)
# owner_name
# (SELECT COUNT(dr.id)
# FROM deal_risks dr
# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
# WHERE dr.opportunity_id = opp.id
# AND dr.is_active = 1
# AND gdrt.is_enabled = 1)
DESC
LIMIT 25
# OFFSET 0
;
select * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';
select * from crm_layout_entities where crm_layout_id = 1493;
SELECT * FROM opportunities WHERE id = 4584045;
SELECT * FROM crm_field_data WHERE object_id = 4584045;
SELECT * FROM opportunities where team_id = 1 and crm_provider_id = '0061K00000lfb9IQAQ'
SELECT
opp.id as opportunity_id,
opp.name,
COUNT(dr.id) as risk_count
FROM opportunities opp
LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id
WHERE opp.id IN (
6069574,8900938,9507638,9524799,9660490,9662824,9705675,9749758,4158460,4391812,6450439,4448422,5118945,5590675,4584045,7228149,9002408,9165534,9446720,9641778,9665149,9703344,9709280,5747948,4158491,6182565,6263970,5798120,5111315,4536978,6062352,6548383,6072095,6548225,4480986,5011422,6548381,8760540,8917554,9509986,9514392,9569009,9569011,9578490,9713604,5784397,7276612,7288405,8994421,9118219,9608148,9818911,4510535,6479693,5958049,6271674,6550448,4158331,4158483,6126571,6171615,6540943,4897466,5190896,5796182,5932762,8572433,8723698,8892697,9711001,9789264,4549188,6100831,6170064,6260799,6263653,6449936,6530871,6538978,7777651,8059269,8319918,8787049,8901150,9263153,9453207,9514738,9696700,9791062,5752018,6421452,7439134,7878923,9354763,9369285,9514396,9582506,9889949,9890216,4094311,4158495,4158496,6098128,5585661,3872564,6442149,5891604,6164746,6199593,6583474,6519684,9018490,9809006,4496897,5041324,5829430,6198319,6253504,6555763,7242914,7931055,8024125,8797814,8058559,8673347,8892695,8994420,9616219,9714970,9722004,9809439,9818918,6523177,8134147,9002915,9711422,9892713,9901719,9954210,9978435,5800810,6243518,6416114,6222251,6411974,6512456,5791953,6545606,9914780,5805540,6238986,6463838,6547680,9767049,9809437,9810885,9890855,6673493,9902036,4335521,6379871,6503799,6546077,8018765,9907556,9958433,9905855,9916179,9946741,9957877,5563344,6271838,6450815,7641128,7762567,7780592,8684810,8685786,8685787,8685788
)
GROUP BY opp.id;
SELECT COUNT(dr.id)
FROM deal_risks dr
JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
WHERE dr.opportunity_id = 5563344
# AND gdrt.group_id = usr.group_id
EXPLAIN SELECT
opp.id as opp_id, opp.uuid, opp.name,
cfd.value,
cfv.sequence,
cfv.value,
# MAX(cfd.value) AS max_cfd_value,
usr.name AS owner_name,
opp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,
usr.uuid as owner_uuid,
usr.photo_path as owner_photo,
usr.id AS owner_id,
jt.name as owner_job,
opp.stage_id, opp.stage_updated_at,
acc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,
rt.business_process_id AS pipeline_id
FROM opportunities opp
LEFT JOIN record_types rt ON opp.record_type_id = rt.id
LEFT JOIN users usr ON opp.user_id = usr.id
LEFT JOIN accounts acc ON opp.account_id = acc.id
LEFT JOIN job_titles jt ON usr.job_title_id = jt.id
LEFT JOIN crm_field_data cfd ON cfd.id = (SELECT sub_cfd.id
FROM crm_field_data sub_cfd
WHERE sub_cfd.object_id = opp.id
AND sub_cfd.crm_field_id = 66810
ORDER BY sub_cfd.updated_at DESC
LIMIT 1)
LEFT JOIN crm_field_values cfv ON cfv.crm_field_id = 66810 AND cfv.value = cfd.value
WHERE opp.user_id IS NOT NULL
AND opp.deleted_at IS NULL
AND opp.is_closed = 0
# AND opp.is_closed = 1
# AND opp.is_won = 0
AND opp.close_date >= '2024-01-01 00:00:00'
AND opp.close_date <= '2024-12-31 23:59:59'
AND usr.team_id = 1
# and opp.id = 4823179
# GROUP BY opp.id
ORDER BY
cfv.sequence DESC,
# opp.name
# owner_name
cfd.value
# CAST(cfd.value AS UNSIGNED)
# CAST(MAX(cfd.value) AS UNSIGNED)
# (SELECT COUNT(dr.id)
# FROM deal_risks dr
# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
# WHERE dr.opportunity_id = opp.id
# AND dr.is_active = 1 AND gdrt.is_enabled = 1)
DESC
# LIMIT 75, 25
;
SELECT * FROM crm_field_values WHERE crm_field_id = 66810;
SELECT f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value, fd.updated_at, fd.id, fd.created_at
FROM crm_fields f
INNER JOIN crm_field_data fd ON fd.crm_field_id = f.id
WHERE (f.crm_configuration_id = 1)
AND (f.object_type = 'opportunity')
# AND (fd.object_id IN (4158331,4158483,4158495,4158496,6069574,6263970,6171615,6540943,6545606,5829430,6198319,6263653,6548381,7242914,8797814,7228149,7439134,7878923,8134147,9002915,9118219,9165534,9354763,9369285,9446720))
AND (fd.object_id IN (4158460))
AND (f.crm_provider_id IN ('ForecastCategoryName'))
ORDER BY fd.object_id ASC, fd.updated_at DESC;
select * from crm_layouts where crm_configuration_id = 1;
select cf.* from crm_fields cf
join crm_layout_entities cle on cf.id = cle.crm_field_id
where crm_layout_id = 1493;
SELECT * FROM opportunities WHERE id = 4158460;
SELECT * FROM crm_field_data WHERE object_id = 4158460;
select * from users where team_id = 1;
SELECT * FROM users WHERE id = 7160;
select * from role_user where user_id = 3248;
select * from crm_field_values where crm_field_id = 66824;
SELECT * FROM users WHERE id = 23470;
select * from crm_configurations;
# [PASSWORD_DOTS]
select * from teams where id = 1038; # 23521, 966
select * from users where team_id = 1038;
select * from crm_configurations where id = 966;
SELECT * FROM activities WHERE uuid_to_bin('e8dc7c34-31f6-4430-bd81-2d9d8f00dd07') = uuid; # 54965762
SELECT * FROM activities WHERE uuid_to_bin('4b5727c0-69d1-428f-b8d9-b649055166e2') = uuid; # 54965764
SELECT * FROM participants WHERE activity_id = 54965764;
SELECT id, uuid, title, recording_state FROM activities WHERE uuid_to_bin('355f105d-5606-4379-b0c5-d91daf59f4ce') = uuid; # 54964075
SELECT * FROM participants WHERE activity_id = 54964075;
select f.id, slug, title, tf.team_id from team_features tf JOIN features f on tf.feature_id = f.id
where tf.team_id = 1038;
# [PASSWORD_DOTS] PD [PASSWORD_DOTS]
select * from teams where id = 1029;
SELECT * FROM crm_configurations WHERE id = 957;
SELECT * FROM activities WHERE uuid_to_bin('581a33cb-4343-44bd-ac0c-4a5cee71c5ec') = uuid; # 54963423
SELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433
SELECT * FROM activities WHERE uuid_to_bin('8b6bb2f7-e7a7-4aec-8806-fbfea7093ccd') = uuid; # 54963421
# [PASSWORD_DOTS] Close [PASSWORD_DOTS]
select * from teams where id = 1031;
SELECT * FROM crm_configurations WHERE id = 959;
SELECT * FROM activities WHERE uuid_to_bin('59c517e0-96c9-4dad-bfeb-b7bef72f8725') = uuid; # 54963517
SELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433
SELECT * FROM activities WHERE uuid_to_bin('d68311e3-ac34-49bd-bf04-88323a7e5352') = uuid; # 54966009
# [PASSWORD_DOTS] SF [PASSWORD_DOTS]
SELECT * FROM activities WHERE uuid_to_bin('2bd4cfa3-4eb5-4c77-84a7-9ed46a21c988') = uuid;
select * from roles;
select * from permissions;
select * from permission_role where permission_id = 136;
select * from migrations order by id desc;
select * from teams where id IN (1, 1037);
select * from crm_layouts where crm_configuration_id = 1;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 1493;
SELECT * FROM crm_fields WHERE id IN (1652,1661,66799,66814,66821,66836,66843,66846,66864,84752,182306,323580,380378);
select * from features;
select * from team_features where feature_id = 33;
select * from opportunities;
select * from teams;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1052 and sa.provider = 'hubspot';
SELECT * FROM accounts where id = 11512582;
select * from activities where account_id = 11512582;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Acl
ActionItems
Activity
ActivityAnalytics
ActivitySearch
AiActivityType...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.054166667,"top":0.027777778,"width":0.08055556,"height":0.035555556},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#12066 on JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.13472222,"top":0.027777778,"width":0.25555557,"height":0.035555556},"on_screen":true,"help_text":"Pull request #12066 exists for current branch JY-20725-handle-HS-search-rate-limit, 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.6326389,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HandleHubspotRateLimitTest","depth":6,"bounds":{"left":0.6645833,"top":0.027777778,"width":0.15902779,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.82361114,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.8472222,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.87083334,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.9291667,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9527778,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9763889,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Integrations\\PlaybookResolver;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\User;\n\n/**\n * Playbook related methods commonly used in all CRMs.\n */\ntrait ActivityPlaybookTrait\n{\n protected CrmHelperRepository $crmHelperRepository;\n\n public function getPlaybookFromActivity(Activity $activity): ?Playbook\n {\n if ($activity->hasActivityType() && $activity->getActivityType()->hasPlaybook()) {\n return $activity->getActivityType()->getPlaybook();\n }\n\n return $this->getPlaybook($activity->getUser());\n }\n\n /**\n * Gets the playbook for the user\n */\n protected function getPlaybook(User $user): ?Playbook\n {\n $playbookResolver = app(PlaybookResolver::class);\n\n return $playbookResolver->resolvePlaybookByUser($user);\n }\n\n protected function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n return $this->crmHelperRepository->getPlaybookCategory($playbook, $categoryName);\n }\n\n public function matchesCrmType(PlaybookCategory $category): bool\n {\n $playbook = $category->getPlaybook();\n if ($playbook === null) {\n return false;\n }\n\n $activityField = $playbook->getActivityField();\n if ($activityField === null) {\n return false;\n }\n\n // Check the field values for a matching option.\n return $this->crmHelperRepository->existsActivityFieldValueByName($activityField, $category->getName());\n }\n\n public function getCrmType(?PlaybookCategory $category): ?FieldValue\n {\n if ($category === null) {\n return null;\n }\n\n $playbook = $category->getPlaybook();\n if ($playbook === null) {\n return null;\n }\n\n $activityField = $playbook->getActivityField();\n if ($activityField === null) {\n return null;\n }\n\n return $this->crmHelperRepository->getActivityFieldValueByName($activityField, $category->getName());\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Integrations\\PlaybookResolver;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\User;\n\n/**\n * Playbook related methods commonly used in all CRMs.\n */\ntrait ActivityPlaybookTrait\n{\n protected CrmHelperRepository $crmHelperRepository;\n\n public function getPlaybookFromActivity(Activity $activity): ?Playbook\n {\n if ($activity->hasActivityType() && $activity->getActivityType()->hasPlaybook()) {\n return $activity->getActivityType()->getPlaybook();\n }\n\n return $this->getPlaybook($activity->getUser());\n }\n\n /**\n * Gets the playbook for the user\n */\n protected function getPlaybook(User $user): ?Playbook\n {\n $playbookResolver = app(PlaybookResolver::class);\n\n return $playbookResolver->resolvePlaybookByUser($user);\n }\n\n protected function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n return $this->crmHelperRepository->getPlaybookCategory($playbook, $categoryName);\n }\n\n public function matchesCrmType(PlaybookCategory $category): bool\n {\n $playbook = $category->getPlaybook();\n if ($playbook === null) {\n return false;\n }\n\n $activityField = $playbook->getActivityField();\n if ($activityField === null) {\n return false;\n }\n\n // Check the field values for a matching option.\n return $this->crmHelperRepository->existsActivityFieldValueByName($activityField, $category->getName());\n }\n\n public function getCrmType(?PlaybookCategory $category): ?FieldValue\n {\n if ($category === null) {\n return null;\n }\n\n $playbook = $category->getPlaybook();\n if ($playbook === null) {\n return null;\n }\n\n $activityField = $playbook->getActivityField();\n if ($activityField === null) {\n return null;\n }\n\n return $this->crmHelperRepository->getActivityFieldValueByName($activityField, $category->getName());\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.85486114,"top":0.31,"width":0.018055556,"height":0.026666667},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"bounds":{"left":0.87291664,"top":0.31,"width":0.018055556,"height":0.026666667},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"bounds":{"left":0.9138889,"top":0.31,"width":0.059027776,"height":0.026666667},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"8","depth":4,"bounds":{"left":0.8645833,"top":0.34444445,"width":0.016666668,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.8854167,"top":0.34444445,"width":0.015277778,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.9048611,"top":0.34444445,"width":0.016666668,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"4","depth":4,"bounds":{"left":0.92569447,"top":0.34444445,"width":0.016666668,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.9458333,"top":0.3422222,"width":0.015277778,"height":0.025555555},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.9611111,"top":0.3422222,"width":0.014583333,"height":0.025555555},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"SELECT\n# DISTINCT\nopp.id as opp_id, opp.uuid, opp.name,\n# COUNT(dr.id) AS deal_risk_count,\n\n# dr.id,\n# cfd.value,\nusr.name AS owner_name,\nopp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,\nusr.uuid as owner_uuid,\nusr.photo_path as owner_photo,\nusr.id AS owner_id,\njt.name as owner_job,\nopp.stage_id, opp.stage_updated_at,\nacc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,\nrt.business_process_id AS pipeline_id\nFROM opportunities opp\nLEFT JOIN record_types rt ON opp.record_type_id = rt.id\nLEFT JOIN users usr ON opp.user_id = usr.id\nLEFT JOIN accounts acc ON opp.account_id = acc.id\nLEFT JOIN job_titles jt ON usr.job_title_id = jt.id\n\n# LEFT JOIN group_deal_risk_types gdrt ON gdrt.group_id = usr.group_id\n# LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id AND dr.group_deal_risk_type_id = gdrt.id\n\nLEFT JOIN crm_field_data cfd ON (cfd.object_id = opp.id and cfd.crm_field_id = 66814)\n\nWHERE opp.user_id IS NOT NULL\nAND opp.deleted_at IS NULL\nAND opp.is_closed = 1\nAND opp.is_won = 0\nAND opp.close_date >= '2024-01-01 00:00:00'\nAND opp.close_date <= '2024-12-31 23:59:59'\nAND usr.team_id = 1\n\n# and opp.id = 4823179\n\nGROUP BY opp.id, cfd.value\nORDER BY\n# cfd.value\n CAST(cfd.value AS UNSIGNED)\n# owner_name\n# (SELECT COUNT(dr.id)\n# FROM deal_risks dr\n# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n# WHERE dr.opportunity_id = opp.id\n# AND dr.is_active = 1\n# AND gdrt.is_enabled = 1)\nDESC\nLIMIT 25\n# OFFSET 0\n;\n\nselect * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';\nselect * from crm_layout_entities where crm_layout_id = 1493;\n\nSELECT * FROM opportunities WHERE id = 4584045;\nSELECT * FROM crm_field_data WHERE object_id = 4584045;\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id = '0061K00000lfb9IQAQ'\n\nSELECT\n opp.id as opportunity_id,\n opp.name,\n COUNT(dr.id) as risk_count\nFROM opportunities opp\nLEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id\nWHERE opp.id IN (\n 6069574,8900938,9507638,9524799,9660490,9662824,9705675,9749758,4158460,4391812,6450439,4448422,5118945,5590675,4584045,7228149,9002408,9165534,9446720,9641778,9665149,9703344,9709280,5747948,4158491,6182565,6263970,5798120,5111315,4536978,6062352,6548383,6072095,6548225,4480986,5011422,6548381,8760540,8917554,9509986,9514392,9569009,9569011,9578490,9713604,5784397,7276612,7288405,8994421,9118219,9608148,9818911,4510535,6479693,5958049,6271674,6550448,4158331,4158483,6126571,6171615,6540943,4897466,5190896,5796182,5932762,8572433,8723698,8892697,9711001,9789264,4549188,6100831,6170064,6260799,6263653,6449936,6530871,6538978,7777651,8059269,8319918,8787049,8901150,9263153,9453207,9514738,9696700,9791062,5752018,6421452,7439134,7878923,9354763,9369285,9514396,9582506,9889949,9890216,4094311,4158495,4158496,6098128,5585661,3872564,6442149,5891604,6164746,6199593,6583474,6519684,9018490,9809006,4496897,5041324,5829430,6198319,6253504,6555763,7242914,7931055,8024125,8797814,8058559,8673347,8892695,8994420,9616219,9714970,9722004,9809439,9818918,6523177,8134147,9002915,9711422,9892713,9901719,9954210,9978435,5800810,6243518,6416114,6222251,6411974,6512456,5791953,6545606,9914780,5805540,6238986,6463838,6547680,9767049,9809437,9810885,9890855,6673493,9902036,4335521,6379871,6503799,6546077,8018765,9907556,9958433,9905855,9916179,9946741,9957877,5563344,6271838,6450815,7641128,7762567,7780592,8684810,8685786,8685787,8685788\n)\nGROUP BY opp.id;\n\nSELECT COUNT(dr.id)\n FROM deal_risks dr\n JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n WHERE dr.opportunity_id = 5563344\n# AND gdrt.group_id = usr.group_id\n\n\n\nEXPLAIN SELECT\n opp.id as opp_id, opp.uuid, opp.name,\n\ncfd.value,\ncfv.sequence,\ncfv.value,\n# MAX(cfd.value) AS max_cfd_value,\nusr.name AS owner_name,\nopp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,\nusr.uuid as owner_uuid,\nusr.photo_path as owner_photo,\nusr.id AS owner_id,\njt.name as owner_job,\nopp.stage_id, opp.stage_updated_at,\nacc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,\nrt.business_process_id AS pipeline_id\nFROM opportunities opp\n\nLEFT JOIN record_types rt ON opp.record_type_id = rt.id\nLEFT JOIN users usr ON opp.user_id = usr.id\nLEFT JOIN accounts acc ON opp.account_id = acc.id\nLEFT JOIN job_titles jt ON usr.job_title_id = jt.id\n\nLEFT JOIN crm_field_data cfd ON cfd.id = (SELECT sub_cfd.id\n FROM crm_field_data sub_cfd\n WHERE sub_cfd.object_id = opp.id\n AND sub_cfd.crm_field_id = 66810\n ORDER BY sub_cfd.updated_at DESC\n LIMIT 1)\n\nLEFT JOIN crm_field_values cfv ON cfv.crm_field_id = 66810 AND cfv.value = cfd.value\n\nWHERE opp.user_id IS NOT NULL\nAND opp.deleted_at IS NULL\nAND opp.is_closed = 0\n# AND opp.is_closed = 1\n# AND opp.is_won = 0\nAND opp.close_date >= '2024-01-01 00:00:00'\nAND opp.close_date <= '2024-12-31 23:59:59'\nAND usr.team_id = 1\n\n# and opp.id = 4823179\n\n# GROUP BY opp.id\nORDER BY\n cfv.sequence DESC,\n# opp.name\n# owner_name\n cfd.value\n# CAST(cfd.value AS UNSIGNED)\n# CAST(MAX(cfd.value) AS UNSIGNED)\n# (SELECT COUNT(dr.id)\n# FROM deal_risks dr\n# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n# WHERE dr.opportunity_id = opp.id\n# AND dr.is_active = 1 AND gdrt.is_enabled = 1)\nDESC\n# LIMIT 75, 25\n;\n\n\n\nSELECT * FROM crm_field_values WHERE crm_field_id = 66810;\n\nSELECT f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value, fd.updated_at, fd.id, fd.created_at\nFROM crm_fields f\nINNER JOIN crm_field_data fd ON fd.crm_field_id = f.id\n WHERE (f.crm_configuration_id = 1)\n AND (f.object_type = 'opportunity')\n# AND (fd.object_id IN (4158331,4158483,4158495,4158496,6069574,6263970,6171615,6540943,6545606,5829430,6198319,6263653,6548381,7242914,8797814,7228149,7439134,7878923,8134147,9002915,9118219,9165534,9354763,9369285,9446720))\n AND (fd.object_id IN (4158460))\n AND (f.crm_provider_id IN ('ForecastCategoryName'))\n ORDER BY fd.object_id ASC, fd.updated_at DESC;\n\nselect * from crm_layouts where crm_configuration_id = 1;\nselect cf.* from crm_fields cf\njoin crm_layout_entities cle on cf.id = cle.crm_field_id\nwhere crm_layout_id = 1493;\n\nSELECT * FROM opportunities WHERE id = 4158460;\nSELECT * FROM crm_field_data WHERE object_id = 4158460;\n\nselect * from users where team_id = 1;\nSELECT * FROM users WHERE id = 7160;\nselect * from role_user where user_id = 3248;\n\nselect * from crm_field_values where crm_field_id = 66824;\n\nSELECT * FROM users WHERE id = 23470;\nselect * from crm_configurations;\n\n# ******************************************\nselect * from teams where id = 1038; # 23521, 966\nselect * from users where team_id = 1038;\nselect * from crm_configurations where id = 966;\nSELECT * FROM activities WHERE uuid_to_bin('e8dc7c34-31f6-4430-bd81-2d9d8f00dd07') = uuid; # 54965762\nSELECT * FROM activities WHERE uuid_to_bin('4b5727c0-69d1-428f-b8d9-b649055166e2') = uuid; # 54965764\nSELECT * FROM participants WHERE activity_id = 54965764;\n\nSELECT id, uuid, title, recording_state FROM activities WHERE uuid_to_bin('355f105d-5606-4379-b0c5-d91daf59f4ce') = uuid; # 54964075\nSELECT * FROM participants WHERE activity_id = 54964075;\nselect f.id, slug, title, tf.team_id from team_features tf JOIN features f on tf.feature_id = f.id\n where tf.team_id = 1038;\n\n\n# ****************************************** PD *************************\nselect * from teams where id = 1029;\nSELECT * FROM crm_configurations WHERE id = 957;\nSELECT * FROM activities WHERE uuid_to_bin('581a33cb-4343-44bd-ac0c-4a5cee71c5ec') = uuid; # 54963423\nSELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433\n\nSELECT * FROM activities WHERE uuid_to_bin('8b6bb2f7-e7a7-4aec-8806-fbfea7093ccd') = uuid; # 54963421\n\n# ****************************************** Close *************************\nselect * from teams where id = 1031;\nSELECT * FROM crm_configurations WHERE id = 959;\nSELECT * FROM activities WHERE uuid_to_bin('59c517e0-96c9-4dad-bfeb-b7bef72f8725') = uuid; # 54963517\n\n\nSELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433\n\nSELECT * FROM activities WHERE uuid_to_bin('d68311e3-ac34-49bd-bf04-88323a7e5352') = uuid; # 54966009\n\n# ****************************************** SF *************************\nSELECT * FROM activities WHERE uuid_to_bin('2bd4cfa3-4eb5-4c77-84a7-9ed46a21c988') = uuid;\n\nselect * from roles;\nselect * from permissions;\nselect * from permission_role where permission_id = 136;\n\nselect * from migrations order by id desc;\n\nselect * from teams where id IN (1, 1037);\nselect * from crm_layouts where crm_configuration_id = 1;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1493;\nSELECT * FROM crm_fields WHERE id IN (1652,1661,66799,66814,66821,66836,66843,66846,66864,84752,182306,323580,380378);\n\nselect * from features;\nselect * from team_features where feature_id = 33;\nselect * from opportunities;\n\nselect * from teams;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1052 and sa.provider = 'hubspot';\n\nSELECT * FROM accounts where id = 11512582;\nselect * from activities where account_id = 11512582;","depth":4,"on_screen":true,"value":"SELECT\n# DISTINCT\nopp.id as opp_id, opp.uuid, opp.name,\n# COUNT(dr.id) AS deal_risk_count,\n\n# dr.id,\n# cfd.value,\nusr.name AS owner_name,\nopp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,\nusr.uuid as owner_uuid,\nusr.photo_path as owner_photo,\nusr.id AS owner_id,\njt.name as owner_job,\nopp.stage_id, opp.stage_updated_at,\nacc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,\nrt.business_process_id AS pipeline_id\nFROM opportunities opp\nLEFT JOIN record_types rt ON opp.record_type_id = rt.id\nLEFT JOIN users usr ON opp.user_id = usr.id\nLEFT JOIN accounts acc ON opp.account_id = acc.id\nLEFT JOIN job_titles jt ON usr.job_title_id = jt.id\n\n# LEFT JOIN group_deal_risk_types gdrt ON gdrt.group_id = usr.group_id\n# LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id AND dr.group_deal_risk_type_id = gdrt.id\n\nLEFT JOIN crm_field_data cfd ON (cfd.object_id = opp.id and cfd.crm_field_id = 66814)\n\nWHERE opp.user_id IS NOT NULL\nAND opp.deleted_at IS NULL\nAND opp.is_closed = 1\nAND opp.is_won = 0\nAND opp.close_date >= '2024-01-01 00:00:00'\nAND opp.close_date <= '2024-12-31 23:59:59'\nAND usr.team_id = 1\n\n# and opp.id = 4823179\n\nGROUP BY opp.id, cfd.value\nORDER BY\n# cfd.value\n CAST(cfd.value AS UNSIGNED)\n# owner_name\n# (SELECT COUNT(dr.id)\n# FROM deal_risks dr\n# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n# WHERE dr.opportunity_id = opp.id\n# AND dr.is_active = 1\n# AND gdrt.is_enabled = 1)\nDESC\nLIMIT 25\n# OFFSET 0\n;\n\nselect * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';\nselect * from crm_layout_entities where crm_layout_id = 1493;\n\nSELECT * FROM opportunities WHERE id = 4584045;\nSELECT * FROM crm_field_data WHERE object_id = 4584045;\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id = '0061K00000lfb9IQAQ'\n\nSELECT\n opp.id as opportunity_id,\n opp.name,\n COUNT(dr.id) as risk_count\nFROM opportunities opp\nLEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id\nWHERE opp.id IN (\n 6069574,8900938,9507638,9524799,9660490,9662824,9705675,9749758,4158460,4391812,6450439,4448422,5118945,5590675,4584045,7228149,9002408,9165534,9446720,9641778,9665149,9703344,9709280,5747948,4158491,6182565,6263970,5798120,5111315,4536978,6062352,6548383,6072095,6548225,4480986,5011422,6548381,8760540,8917554,9509986,9514392,9569009,9569011,9578490,9713604,5784397,7276612,7288405,8994421,9118219,9608148,9818911,4510535,6479693,5958049,6271674,6550448,4158331,4158483,6126571,6171615,6540943,4897466,5190896,5796182,5932762,8572433,8723698,8892697,9711001,9789264,4549188,6100831,6170064,6260799,6263653,6449936,6530871,6538978,7777651,8059269,8319918,8787049,8901150,9263153,9453207,9514738,9696700,9791062,5752018,6421452,7439134,7878923,9354763,9369285,9514396,9582506,9889949,9890216,4094311,4158495,4158496,6098128,5585661,3872564,6442149,5891604,6164746,6199593,6583474,6519684,9018490,9809006,4496897,5041324,5829430,6198319,6253504,6555763,7242914,7931055,8024125,8797814,8058559,8673347,8892695,8994420,9616219,9714970,9722004,9809439,9818918,6523177,8134147,9002915,9711422,9892713,9901719,9954210,9978435,5800810,6243518,6416114,6222251,6411974,6512456,5791953,6545606,9914780,5805540,6238986,6463838,6547680,9767049,9809437,9810885,9890855,6673493,9902036,4335521,6379871,6503799,6546077,8018765,9907556,9958433,9905855,9916179,9946741,9957877,5563344,6271838,6450815,7641128,7762567,7780592,8684810,8685786,8685787,8685788\n)\nGROUP BY opp.id;\n\nSELECT COUNT(dr.id)\n FROM deal_risks dr\n JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n WHERE dr.opportunity_id = 5563344\n# AND gdrt.group_id = usr.group_id\n\n\n\nEXPLAIN SELECT\n opp.id as opp_id, opp.uuid, opp.name,\n\ncfd.value,\ncfv.sequence,\ncfv.value,\n# MAX(cfd.value) AS max_cfd_value,\nusr.name AS owner_name,\nopp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,\nusr.uuid as owner_uuid,\nusr.photo_path as owner_photo,\nusr.id AS owner_id,\njt.name as owner_job,\nopp.stage_id, opp.stage_updated_at,\nacc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,\nrt.business_process_id AS pipeline_id\nFROM opportunities opp\n\nLEFT JOIN record_types rt ON opp.record_type_id = rt.id\nLEFT JOIN users usr ON opp.user_id = usr.id\nLEFT JOIN accounts acc ON opp.account_id = acc.id\nLEFT JOIN job_titles jt ON usr.job_title_id = jt.id\n\nLEFT JOIN crm_field_data cfd ON cfd.id = (SELECT sub_cfd.id\n FROM crm_field_data sub_cfd\n WHERE sub_cfd.object_id = opp.id\n AND sub_cfd.crm_field_id = 66810\n ORDER BY sub_cfd.updated_at DESC\n LIMIT 1)\n\nLEFT JOIN crm_field_values cfv ON cfv.crm_field_id = 66810 AND cfv.value = cfd.value\n\nWHERE opp.user_id IS NOT NULL\nAND opp.deleted_at IS NULL\nAND opp.is_closed = 0\n# AND opp.is_closed = 1\n# AND opp.is_won = 0\nAND opp.close_date >= '2024-01-01 00:00:00'\nAND opp.close_date <= '2024-12-31 23:59:59'\nAND usr.team_id = 1\n\n# and opp.id = 4823179\n\n# GROUP BY opp.id\nORDER BY\n cfv.sequence DESC,\n# opp.name\n# owner_name\n cfd.value\n# CAST(cfd.value AS UNSIGNED)\n# CAST(MAX(cfd.value) AS UNSIGNED)\n# (SELECT COUNT(dr.id)\n# FROM deal_risks dr\n# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n# WHERE dr.opportunity_id = opp.id\n# AND dr.is_active = 1 AND gdrt.is_enabled = 1)\nDESC\n# LIMIT 75, 25\n;\n\n\n\nSELECT * FROM crm_field_values WHERE crm_field_id = 66810;\n\nSELECT f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value, fd.updated_at, fd.id, fd.created_at\nFROM crm_fields f\nINNER JOIN crm_field_data fd ON fd.crm_field_id = f.id\n WHERE (f.crm_configuration_id = 1)\n AND (f.object_type = 'opportunity')\n# AND (fd.object_id IN (4158331,4158483,4158495,4158496,6069574,6263970,6171615,6540943,6545606,5829430,6198319,6263653,6548381,7242914,8797814,7228149,7439134,7878923,8134147,9002915,9118219,9165534,9354763,9369285,9446720))\n AND (fd.object_id IN (4158460))\n AND (f.crm_provider_id IN ('ForecastCategoryName'))\n ORDER BY fd.object_id ASC, fd.updated_at DESC;\n\nselect * from crm_layouts where crm_configuration_id = 1;\nselect cf.* from crm_fields cf\njoin crm_layout_entities cle on cf.id = cle.crm_field_id\nwhere crm_layout_id = 1493;\n\nSELECT * FROM opportunities WHERE id = 4158460;\nSELECT * FROM crm_field_data WHERE object_id = 4158460;\n\nselect * from users where team_id = 1;\nSELECT * FROM users WHERE id = 7160;\nselect * from role_user where user_id = 3248;\n\nselect * from crm_field_values where crm_field_id = 66824;\n\nSELECT * FROM users WHERE id = 23470;\nselect * from crm_configurations;\n\n# ******************************************\nselect * from teams where id = 1038; # 23521, 966\nselect * from users where team_id = 1038;\nselect * from crm_configurations where id = 966;\nSELECT * FROM activities WHERE uuid_to_bin('e8dc7c34-31f6-4430-bd81-2d9d8f00dd07') = uuid; # 54965762\nSELECT * FROM activities WHERE uuid_to_bin('4b5727c0-69d1-428f-b8d9-b649055166e2') = uuid; # 54965764\nSELECT * FROM participants WHERE activity_id = 54965764;\n\nSELECT id, uuid, title, recording_state FROM activities WHERE uuid_to_bin('355f105d-5606-4379-b0c5-d91daf59f4ce') = uuid; # 54964075\nSELECT * FROM participants WHERE activity_id = 54964075;\nselect f.id, slug, title, tf.team_id from team_features tf JOIN features f on tf.feature_id = f.id\n where tf.team_id = 1038;\n\n\n# ****************************************** PD *************************\nselect * from teams where id = 1029;\nSELECT * FROM crm_configurations WHERE id = 957;\nSELECT * FROM activities WHERE uuid_to_bin('581a33cb-4343-44bd-ac0c-4a5cee71c5ec') = uuid; # 54963423\nSELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433\n\nSELECT * FROM activities WHERE uuid_to_bin('8b6bb2f7-e7a7-4aec-8806-fbfea7093ccd') = uuid; # 54963421\n\n# ****************************************** Close *************************\nselect * from teams where id = 1031;\nSELECT * FROM crm_configurations WHERE id = 959;\nSELECT * FROM activities WHERE uuid_to_bin('59c517e0-96c9-4dad-bfeb-b7bef72f8725') = uuid; # 54963517\n\n\nSELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433\n\nSELECT * FROM activities WHERE uuid_to_bin('d68311e3-ac34-49bd-bf04-88323a7e5352') = uuid; # 54966009\n\n# ****************************************** SF *************************\nSELECT * FROM activities WHERE uuid_to_bin('2bd4cfa3-4eb5-4c77-84a7-9ed46a21c988') = uuid;\n\nselect * from roles;\nselect * from permissions;\nselect * from permission_role where permission_id = 136;\n\nselect * from migrations order by id desc;\n\nselect * from teams where id IN (1, 1037);\nselect * from crm_layouts where crm_configuration_id = 1;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1493;\nSELECT * FROM crm_fields WHERE id IN (1652,1661,66799,66814,66821,66836,66843,66846,66864,84752,182306,323580,380378);\n\nselect * from features;\nselect * from team_features where feature_id = 33;\nselect * from opportunities;\n\nselect * from teams;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1052 and sa.provider = 'hubspot';\n\nSELECT * FROM accounts where id = 11512582;\nselect * from activities where account_id = 11512582;","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.025,"top":0.06666667,"width":0.050694443,"height":0.034444444},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"app ~/jiminny/app","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".circleci","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".cursor","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".github","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".sonarlint","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".vscode","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".windsurf","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"app, sources root","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Actions","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Component","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Acl","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActionItems","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Activity","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActivityAnalytics","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActivitySearch","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiActivityType","depth":9,"on_screen":false,"role_description":"text"}]...
|
-7840118185622920921
|
6758386328208093789
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Integrations\PlaybookResolver;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\User;
/**
* Playbook related methods commonly used in all CRMs.
*/
trait ActivityPlaybookTrait
{
protected CrmHelperRepository $crmHelperRepository;
public function getPlaybookFromActivity(Activity $activity): ?Playbook
{
if ($activity->hasActivityType() && $activity->getActivityType()->hasPlaybook()) {
return $activity->getActivityType()->getPlaybook();
}
return $this->getPlaybook($activity->getUser());
}
/**
* Gets the playbook for the user
*/
protected function getPlaybook(User $user): ?Playbook
{
$playbookResolver = app(PlaybookResolver::class);
return $playbookResolver->resolvePlaybookByUser($user);
}
protected function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
return $this->crmHelperRepository->getPlaybookCategory($playbook, $categoryName);
}
public function matchesCrmType(PlaybookCategory $category): bool
{
$playbook = $category->getPlaybook();
if ($playbook === null) {
return false;
}
$activityField = $playbook->getActivityField();
if ($activityField === null) {
return false;
}
// Check the field values for a matching option.
return $this->crmHelperRepository->existsActivityFieldValueByName($activityField, $category->getName());
}
public function getCrmType(?PlaybookCategory $category): ?FieldValue
{
if ($category === null) {
return null;
}
$playbook = $category->getPlaybook();
if ($playbook === null) {
return null;
}
$activityField = $playbook->getActivityField();
if ($activityField === null) {
return null;
}
return $this->crmHelperRepository->getActivityFieldValueByName($activityField, $category->getName());
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
8
1
3
4
Previous Highlighted Error
Next Highlighted Error
SELECT
# DISTINCT
opp.id as opp_id, opp.uuid, opp.name,
# COUNT(dr.id) AS deal_risk_count,
# dr.id,
# cfd.value,
usr.name AS owner_name,
opp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,
usr.uuid as owner_uuid,
usr.photo_path as owner_photo,
usr.id AS owner_id,
jt.name as owner_job,
opp.stage_id, opp.stage_updated_at,
acc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,
rt.business_process_id AS pipeline_id
FROM opportunities opp
LEFT JOIN record_types rt ON opp.record_type_id = rt.id
LEFT JOIN users usr ON opp.user_id = usr.id
LEFT JOIN accounts acc ON opp.account_id = acc.id
LEFT JOIN job_titles jt ON usr.job_title_id = jt.id
# LEFT JOIN group_deal_risk_types gdrt ON gdrt.group_id = usr.group_id
# LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id AND dr.group_deal_risk_type_id = gdrt.id
LEFT JOIN crm_field_data cfd ON (cfd.object_id = opp.id and cfd.crm_field_id = 66814)
WHERE opp.user_id IS NOT NULL
AND opp.deleted_at IS NULL
AND opp.is_closed = 1
AND opp.is_won = 0
AND opp.close_date >= '2024-01-01 00:00:00'
AND opp.close_date <= '2024-12-31 23:59:59'
AND usr.team_id = 1
# and opp.id = 4823179
GROUP BY opp.id, cfd.value
ORDER BY
# cfd.value
CAST(cfd.value AS UNSIGNED)
# owner_name
# (SELECT COUNT(dr.id)
# FROM deal_risks dr
# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
# WHERE dr.opportunity_id = opp.id
# AND dr.is_active = 1
# AND gdrt.is_enabled = 1)
DESC
LIMIT 25
# OFFSET 0
;
select * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';
select * from crm_layout_entities where crm_layout_id = 1493;
SELECT * FROM opportunities WHERE id = 4584045;
SELECT * FROM crm_field_data WHERE object_id = 4584045;
SELECT * FROM opportunities where team_id = 1 and crm_provider_id = '0061K00000lfb9IQAQ'
SELECT
opp.id as opportunity_id,
opp.name,
COUNT(dr.id) as risk_count
FROM opportunities opp
LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id
WHERE opp.id IN (
6069574,8900938,9507638,9524799,9660490,9662824,9705675,9749758,4158460,4391812,6450439,4448422,5118945,5590675,4584045,7228149,9002408,9165534,9446720,9641778,9665149,9703344,9709280,5747948,4158491,6182565,6263970,5798120,5111315,4536978,6062352,6548383,6072095,6548225,4480986,5011422,6548381,8760540,8917554,9509986,9514392,9569009,9569011,9578490,9713604,5784397,7276612,7288405,8994421,9118219,9608148,9818911,4510535,6479693,5958049,6271674,6550448,4158331,4158483,6126571,6171615,6540943,4897466,5190896,5796182,5932762,8572433,8723698,8892697,9711001,9789264,4549188,6100831,6170064,6260799,6263653,6449936,6530871,6538978,7777651,8059269,8319918,8787049,8901150,9263153,9453207,9514738,9696700,9791062,5752018,6421452,7439134,7878923,9354763,9369285,9514396,9582506,9889949,9890216,4094311,4158495,4158496,6098128,5585661,3872564,6442149,5891604,6164746,6199593,6583474,6519684,9018490,9809006,4496897,5041324,5829430,6198319,6253504,6555763,7242914,7931055,8024125,8797814,8058559,8673347,8892695,8994420,9616219,9714970,9722004,9809439,9818918,6523177,8134147,9002915,9711422,9892713,9901719,9954210,9978435,5800810,6243518,6416114,6222251,6411974,6512456,5791953,6545606,9914780,5805540,6238986,6463838,6547680,9767049,9809437,9810885,9890855,6673493,9902036,4335521,6379871,6503799,6546077,8018765,9907556,9958433,9905855,9916179,9946741,9957877,5563344,6271838,6450815,7641128,7762567,7780592,8684810,8685786,8685787,8685788
)
GROUP BY opp.id;
SELECT COUNT(dr.id)
FROM deal_risks dr
JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
WHERE dr.opportunity_id = 5563344
# AND gdrt.group_id = usr.group_id
EXPLAIN SELECT
opp.id as opp_id, opp.uuid, opp.name,
cfd.value,
cfv.sequence,
cfv.value,
# MAX(cfd.value) AS max_cfd_value,
usr.name AS owner_name,
opp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,
usr.uuid as owner_uuid,
usr.photo_path as owner_photo,
usr.id AS owner_id,
jt.name as owner_job,
opp.stage_id, opp.stage_updated_at,
acc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,
rt.business_process_id AS pipeline_id
FROM opportunities opp
LEFT JOIN record_types rt ON opp.record_type_id = rt.id
LEFT JOIN users usr ON opp.user_id = usr.id
LEFT JOIN accounts acc ON opp.account_id = acc.id
LEFT JOIN job_titles jt ON usr.job_title_id = jt.id
LEFT JOIN crm_field_data cfd ON cfd.id = (SELECT sub_cfd.id
FROM crm_field_data sub_cfd
WHERE sub_cfd.object_id = opp.id
AND sub_cfd.crm_field_id = 66810
ORDER BY sub_cfd.updated_at DESC
LIMIT 1)
LEFT JOIN crm_field_values cfv ON cfv.crm_field_id = 66810 AND cfv.value = cfd.value
WHERE opp.user_id IS NOT NULL
AND opp.deleted_at IS NULL
AND opp.is_closed = 0
# AND opp.is_closed = 1
# AND opp.is_won = 0
AND opp.close_date >= '2024-01-01 00:00:00'
AND opp.close_date <= '2024-12-31 23:59:59'
AND usr.team_id = 1
# and opp.id = 4823179
# GROUP BY opp.id
ORDER BY
cfv.sequence DESC,
# opp.name
# owner_name
cfd.value
# CAST(cfd.value AS UNSIGNED)
# CAST(MAX(cfd.value) AS UNSIGNED)
# (SELECT COUNT(dr.id)
# FROM deal_risks dr
# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
# WHERE dr.opportunity_id = opp.id
# AND dr.is_active = 1 AND gdrt.is_enabled = 1)
DESC
# LIMIT 75, 25
;
SELECT * FROM crm_field_values WHERE crm_field_id = 66810;
SELECT f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value, fd.updated_at, fd.id, fd.created_at
FROM crm_fields f
INNER JOIN crm_field_data fd ON fd.crm_field_id = f.id
WHERE (f.crm_configuration_id = 1)
AND (f.object_type = 'opportunity')
# AND (fd.object_id IN (4158331,4158483,4158495,4158496,6069574,6263970,6171615,6540943,6545606,5829430,6198319,6263653,6548381,7242914,8797814,7228149,7439134,7878923,8134147,9002915,9118219,9165534,9354763,9369285,9446720))
AND (fd.object_id IN (4158460))
AND (f.crm_provider_id IN ('ForecastCategoryName'))
ORDER BY fd.object_id ASC, fd.updated_at DESC;
select * from crm_layouts where crm_configuration_id = 1;
select cf.* from crm_fields cf
join crm_layout_entities cle on cf.id = cle.crm_field_id
where crm_layout_id = 1493;
SELECT * FROM opportunities WHERE id = 4158460;
SELECT * FROM crm_field_data WHERE object_id = 4158460;
select * from users where team_id = 1;
SELECT * FROM users WHERE id = 7160;
select * from role_user where user_id = 3248;
select * from crm_field_values where crm_field_id = 66824;
SELECT * FROM users WHERE id = 23470;
select * from crm_configurations;
# [PASSWORD_DOTS]
select * from teams where id = 1038; # 23521, 966
select * from users where team_id = 1038;
select * from crm_configurations where id = 966;
SELECT * FROM activities WHERE uuid_to_bin('e8dc7c34-31f6-4430-bd81-2d9d8f00dd07') = uuid; # 54965762
SELECT * FROM activities WHERE uuid_to_bin('4b5727c0-69d1-428f-b8d9-b649055166e2') = uuid; # 54965764
SELECT * FROM participants WHERE activity_id = 54965764;
SELECT id, uuid, title, recording_state FROM activities WHERE uuid_to_bin('355f105d-5606-4379-b0c5-d91daf59f4ce') = uuid; # 54964075
SELECT * FROM participants WHERE activity_id = 54964075;
select f.id, slug, title, tf.team_id from team_features tf JOIN features f on tf.feature_id = f.id
where tf.team_id = 1038;
# [PASSWORD_DOTS] PD [PASSWORD_DOTS]
select * from teams where id = 1029;
SELECT * FROM crm_configurations WHERE id = 957;
SELECT * FROM activities WHERE uuid_to_bin('581a33cb-4343-44bd-ac0c-4a5cee71c5ec') = uuid; # 54963423
SELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433
SELECT * FROM activities WHERE uuid_to_bin('8b6bb2f7-e7a7-4aec-8806-fbfea7093ccd') = uuid; # 54963421
# [PASSWORD_DOTS] Close [PASSWORD_DOTS]
select * from teams where id = 1031;
SELECT * FROM crm_configurations WHERE id = 959;
SELECT * FROM activities WHERE uuid_to_bin('59c517e0-96c9-4dad-bfeb-b7bef72f8725') = uuid; # 54963517
SELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433
SELECT * FROM activities WHERE uuid_to_bin('d68311e3-ac34-49bd-bf04-88323a7e5352') = uuid; # 54966009
# [PASSWORD_DOTS] SF [PASSWORD_DOTS]
SELECT * FROM activities WHERE uuid_to_bin('2bd4cfa3-4eb5-4c77-84a7-9ed46a21c988') = uuid;
select * from roles;
select * from permissions;
select * from permission_role where permission_id = 136;
select * from migrations order by id desc;
select * from teams where id IN (1, 1037);
select * from crm_layouts where crm_configuration_id = 1;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 1493;
SELECT * FROM crm_fields WHERE id IN (1652,1661,66799,66814,66821,66836,66843,66846,66864,84752,182306,323580,380378);
select * from features;
select * from team_features where feature_id = 33;
select * from opportunities;
select * from teams;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1052 and sa.provider = 'hubspot';
SELECT * FROM accounts where id = 11512582;
select * from activities where account_id = 11512582;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Acl
ActionItems
Activity
ActivityAnalytics
ActivitySearch
AiActivityType...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45171
|
1622
|
10
|
2026-05-14T14:23:25.877873+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768605877_m2.jpg...
|
PhpStorm
|
faVsco.js – ActivityPlaybookTrait.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Integrations\PlaybookResolver;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\User;
/**
* Playbook related methods commonly used in all CRMs.
*/
trait ActivityPlaybookTrait
{
protected CrmHelperRepository $crmHelperRepository;
public function getPlaybookFromActivity(Activity $activity): ?Playbook
{
if ($activity->hasActivityType() && $activity->getActivityType()->hasPlaybook()) {
return $activity->getActivityType()->getPlaybook();
}
return $this->getPlaybook($activity->getUser());
}
/**
* Gets the playbook for the user
*/
protected function getPlaybook(User $user): ?Playbook
{
$playbookResolver = app(PlaybookResolver::class);
return $playbookResolver->resolvePlaybookByUser($user);
}
protected function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
return $this->crmHelperRepository->getPlaybookCategory($playbook, $categoryName);
}
public function matchesCrmType(PlaybookCategory $category): bool
{
$playbook = $category->getPlaybook();
if ($playbook === null) {
return false;
}
$activityField = $playbook->getActivityField();
if ($activityField === null) {
return false;
}
// Check the field values for a matching option.
return $this->crmHelperRepository->existsActivityFieldValueByName($activityField, $category->getName());
}
public function getCrmType(?PlaybookCategory $category): ?FieldValue
{
if ($category === null) {
return null;
}
$playbook = $category->getPlaybook();
if ($playbook === null) {
return null;
}
$activityField = $playbook->getActivityField();
if ($activityField === null) {
return null;
}
return $this->crmHelperRepository->getActivityFieldValueByName($activityField, $category->getName());
}
}...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.2962101,"top":1.0,"width":0.03856383,"height":-0.019952059},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#12066 on JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.33477393,"top":1.0,"width":0.122340426,"height":-0.019952059},"on_screen":true,"help_text":"Pull request #12066 exists for current branch JY-20725-handle-HS-search-rate-limit, 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.5731383,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HandleHubspotRateLimitTest","depth":6,"bounds":{"left":0.5884308,"top":1.0,"width":0.076130316,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.66456115,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.67586434,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.6871675,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.7150931,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.72639626,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.73769945,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Integrations\\PlaybookResolver;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\User;\n\n/**\n * Playbook related methods commonly used in all CRMs.\n */\ntrait ActivityPlaybookTrait\n{\n protected CrmHelperRepository $crmHelperRepository;\n\n public function getPlaybookFromActivity(Activity $activity): ?Playbook\n {\n if ($activity->hasActivityType() && $activity->getActivityType()->hasPlaybook()) {\n return $activity->getActivityType()->getPlaybook();\n }\n\n return $this->getPlaybook($activity->getUser());\n }\n\n /**\n * Gets the playbook for the user\n */\n protected function getPlaybook(User $user): ?Playbook\n {\n $playbookResolver = app(PlaybookResolver::class);\n\n return $playbookResolver->resolvePlaybookByUser($user);\n }\n\n protected function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n return $this->crmHelperRepository->getPlaybookCategory($playbook, $categoryName);\n }\n\n public function matchesCrmType(PlaybookCategory $category): bool\n {\n $playbook = $category->getPlaybook();\n if ($playbook === null) {\n return false;\n }\n\n $activityField = $playbook->getActivityField();\n if ($activityField === null) {\n return false;\n }\n\n // Check the field values for a matching option.\n return $this->crmHelperRepository->existsActivityFieldValueByName($activityField, $category->getName());\n }\n\n public function getCrmType(?PlaybookCategory $category): ?FieldValue\n {\n if ($category === null) {\n return null;\n }\n\n $playbook = $category->getPlaybook();\n if ($playbook === null) {\n return null;\n }\n\n $activityField = $playbook->getActivityField();\n if ($activityField === null) {\n return null;\n }\n\n return $this->crmHelperRepository->getActivityFieldValueByName($activityField, $category->getName());\n }\n}","depth":4,"bounds":{"left":0.3899601,"top":0.8667199,"width":0.29787233,"height":0.1332801},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Integrations\\PlaybookResolver;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\User;\n\n/**\n * Playbook related methods commonly used in all CRMs.\n */\ntrait ActivityPlaybookTrait\n{\n protected CrmHelperRepository $crmHelperRepository;\n\n public function getPlaybookFromActivity(Activity $activity): ?Playbook\n {\n if ($activity->hasActivityType() && $activity->getActivityType()->hasPlaybook()) {\n return $activity->getActivityType()->getPlaybook();\n }\n\n return $this->getPlaybook($activity->getUser());\n }\n\n /**\n * Gets the playbook for the user\n */\n protected function getPlaybook(User $user): ?Playbook\n {\n $playbookResolver = app(PlaybookResolver::class);\n\n return $playbookResolver->resolvePlaybookByUser($user);\n }\n\n protected function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n return $this->crmHelperRepository->getPlaybookCategory($playbook, $categoryName);\n }\n\n public function matchesCrmType(PlaybookCategory $category): bool\n {\n $playbook = $category->getPlaybook();\n if ($playbook === null) {\n return false;\n }\n\n $activityField = $playbook->getActivityField();\n if ($activityField === null) {\n return false;\n }\n\n // Check the field values for a matching option.\n return $this->crmHelperRepository->existsActivityFieldValueByName($activityField, $category->getName());\n }\n\n public function getCrmType(?PlaybookCategory $category): ?FieldValue\n {\n if ($category === null) {\n return null;\n }\n\n $playbook = $category->getPlaybook();\n if ($playbook === null) {\n return null;\n }\n\n $activityField = $playbook->getActivityField();\n if ($activityField === null) {\n return null;\n }\n\n return $this->crmHelperRepository->getActivityFieldValueByName($activityField, $category->getName());\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false}]...
|
5486440281962872685
|
-27547832286463784
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Integrations\PlaybookResolver;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\User;
/**
* Playbook related methods commonly used in all CRMs.
*/
trait ActivityPlaybookTrait
{
protected CrmHelperRepository $crmHelperRepository;
public function getPlaybookFromActivity(Activity $activity): ?Playbook
{
if ($activity->hasActivityType() && $activity->getActivityType()->hasPlaybook()) {
return $activity->getActivityType()->getPlaybook();
}
return $this->getPlaybook($activity->getUser());
}
/**
* Gets the playbook for the user
*/
protected function getPlaybook(User $user): ?Playbook
{
$playbookResolver = app(PlaybookResolver::class);
return $playbookResolver->resolvePlaybookByUser($user);
}
protected function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
return $this->crmHelperRepository->getPlaybookCategory($playbook, $categoryName);
}
public function matchesCrmType(PlaybookCategory $category): bool
{
$playbook = $category->getPlaybook();
if ($playbook === null) {
return false;
}
$activityField = $playbook->getActivityField();
if ($activityField === null) {
return false;
}
// Check the field values for a matching option.
return $this->crmHelperRepository->existsActivityFieldValueByName($activityField, $category->getName());
}
public function getCrmType(?PlaybookCategory $category): ?FieldValue
{
if ($category === null) {
return null;
}
$playbook = $category->getPlaybook();
if ($playbook === null) {
return null;
}
$activityField = $playbook->getActivityField();
if ($activityField === null) {
return null;
}
return $this->crmHelperRepository->getActivityFieldValueByName($activityField, $category->getName());
}
}...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45173
|
1622
|
11
|
2026-05-14T14:23:29.536165+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768609536_m2.jpg...
|
PhpStorm
|
faVsco.js – ActivityPlaybookTrait.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.2962101,"top":1.0,"width":0.03856383,"height":-0.019952059},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#12066 on JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.33477393,"top":1.0,"width":0.122340426,"height":-0.019952059},"on_screen":true,"help_text":"Pull request #12066 exists for current branch JY-20725-handle-HS-search-rate-limit, 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.5731383,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HandleHubspotRateLimitTest","depth":6,"bounds":{"left":0.5884308,"top":1.0,"width":0.076130316,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.66456115,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-7759925082980464890
|
-7195274138718106135
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
ActivityFilesLaterJiminny... ~@ jiminny-x-integrati& platform-inner-team© Channels# ai-chapter# alertsi backend# bugscontusion-cllnia# curiosity_lab# engineering# general# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the people of iimi..• Direct messages8. A...• A3 62P. Galya Dimitrova Mvasil VasilevA. Stefka Stoyanova%: Todor Stamatovf. Mario GeorgievP. Nikolay Ivanov2o James Graham "2. Stoyan Tanev. Steliyan Georgiev. Petko Kashinski*. Lukas Kovali...a: Apps® ToastS lira Gloud6 Huddle with Aneliya Angelova& R. Aneliya Angelova •• Messagest Add canvasur FilesAneliya Angelova 2:30 PMЛукаш, само при Сейлсфорс и Хубспот се синкваха активити типовете, когато се направи плейбук,нали?пои останалите скМі тоябва оъчно ла се въвелат.Lukas Kovalik 2:47 PMтряова да се за всички, някьде не се ли попьлвапри зохо май беше hardcoded но май и там си връщаха две категорииAneliya Angelova @ 4:00 PMЛукаш имаш ли време да се чуем за тестването на https://jiminny.atlassian.net/browse/JY-20725X Preview insackt[HubSpot] Optimise CRM rematctC OpenReady for QA- MediumAA Aneliya AngelovaAs of today at 4:00 PM RetreshOpen in Jira+ SummariseLukas Kovalik 4:02 PMzean-iurwiniareelYou joined the huddle LIVE 4:06 PMAneliva Angelova is here toolAneliya Angelova 4:44 PM11512582Lukas Kovalik O 5:16 PMcurl --location "httns:ani.hubani.com/crm/v3/obiects/contacts/search--header 'Content-Type: application/json'\--header "Authorization: BearerC|Kvma?iMx7OINOM8kOFwrAowAcAkUAhIR24?A05D?xiVrIYMILuP6SA01Kwcmhr=n.v2 hytorDniMvDalYck-CMalovolNOM9kOFwrAUAcrkcaws-ThwRARIRAOF-ATFSAOFRAO-ROIEBAQUBEggBAQEBAYICFIґ7Au8O2Nwal9YW2CbCCxYvwngmSgNldTFSAFoAYABoAHAAeAA'\--data "{ "limit", 13'Message Aneliva Angelova+ AaIAl Notes: OffLeave~ (Q SearchDate ModifiedYesterdav at 13.33Yesterday at 13:33Yesterday at 13.32Yesterdav at 13:31Yesterday at 13:30Yesterdav at 13:30Yesterday at 13:29Yesterday at 13.28Yesterday at 13:28resterday at 15.21Yesterdav at 13:26Yesterday at 13:26Yesterday at 13:25Yesterday at 13:23Yesterday at 13:24resterday at 13-43Yesterdav at 13:22Yesterday at 13:20Yesterday at 13:20Yesterday at 13.19Yesterdav at 13:18Yesterday at 13:18Yesterday at 13:17Yesterday at 13:17Yesterday at 13:16Yesterday at 13.15Yecterdav at 12:15Yesterday at 13:14Yesterday at 13-14Yesterdav at 13:13Yesterday at 13:13Yesterday at 13:12Yecterdav at 12:11Yesterday at 13:10Yesterdav at 13:10Yesterday at 13:09Yesterday at 13:08Yoctorday at 12:09Yesterday at 13.0/Yocterdav at 13:06Yesterday at 13:06Yesterdav at 13:05MPEG-4 movie12 KBMPEG-4 movie9 KBMPEG-4 movieMPEG-4 movie8 KE37 KB10 KBMPEG-4 movie7 K:8 KBMPEG-4 movie9 KBMPEG-4 movie72 KBMPEG-4 movie14 KB15 KbMPEG-4 movie9 K:18 KBMPEG-4 movie12 KB10 K:16 KRIMPEG-4 movieMPEG-4 movie6 KBMPEG-4 movie12 KE23 KBMPEG-4 movie8 KB6 KB1MPEG-4 movie6 KBMPEG-4 movie11 KB11 KBMPEG-4 movie20 KEMPEG-4 movie34 KBMPEG-4 movie10 KB7K:MPEG-4 movie5 KBMPEG-4 movie11 KB26 KBMPEG-4 movie111 KPMPEG-4 movie102 KBMPEG-4 movie88 KB59 K:MPEG-4 movie98 KBMPEG-4 movie97 KB66 KBЛAKEMPEG-4 movieMDEG-A movie93 KB78 KBMPEG-4 movie50 K:MPEG-A movid58 KB MPEG-4 movie27 KB7 KB112 KрMPEG-4 movieMPEG-4 movie32 KB17 KBMPEG-4 movie19 Kг32 KBMPEG-4 movie10 KB24 K8MPEG-4 movieFavouritesE jiminny© Recents* ApplicationsiCloudiCloud Drive228 Sync folderQ DXP4800PLUS-B5FA@ Network|• CRMI• Orange• Red• Yellow• Green• Purple•) All lags..lohlDownloadsNameLoom.pkgAlfred copv.alfredoreterencesB KeychronAssist-1.0.2 (1).dmgA Keychron Assist-1.0.2.dmgmazanoke-images-ywJo.ziPhotos-3-001.zipD Transcript.pdf→mage U.loge1 Orioninstaller.dma- image (2).1pc• ПО-22221726037035-004-001_ORGES.pdf• %D0%9F%D0%9E-22221726037035-004-001_archive.zipПO-22221726037035-004-001_archive (1).zip• repon 4).XmAltred copy2.altredoreterences05012026_0000000026574472_ SWIFT_OB70501260015890.pdf27022026_0000000026574472_SWIFT_OB72702260049200.pdf= 03042026 [CREDIT_CARD] SWIFT [CREDIT_CARD].001B reporti) xm=pdt.odipdf-1.pdfD pdf-2.pdf-pdf-5.pd1= ndf-1 ndipdf-3.pdfB Rovaix Famly Treo gedbitwarden export 20251031122528.isonKoválik Family Tree.zip*macOS Storage_Cleanup.mdal favicon icofirst_aid_notes_complete.docxrenortl2).esvconfig.ymlIteration run Search HS.postman_collection.json--report(1).csvm licence hettertouchtoalMariusHosting Config.json1ooks-891a6503-bbb7-4b2b-9c3.csv•Alfredmazanoke-images-YWJ6ана Ковалик.jpg•искане даниел Ковалик..pg• Фактура Март Даниел Ковалик.jрс• Фактура Април Даниел Ковалик.jpg• Dhotac 2.001Q SearchKind00,4 MDinstdlle..dckage55.9 MBAlfred...ferences10,1 MBDisk Image10,1 MBDisk ImageIL MBLiP archive6,6 MBZIP archive2,5 MB PDF Document2,5 MBJreo lmage2.2 MBDisk Image2,2 MB PDF Document2 MBJPEG image1,9 MBJPEG imaqe192 KBPDF Document140 KbZIP archive148 KВZIP archivel148 KBZIP archive122 KBXML document111 KBAlfred...ferences94 KBPDF Document92 KBPDr DocumentK:91 KB91 KB30 KB29 KBPDF DocumentXML documentPDF Document28 KBPDF Document28 KR28 KB27 KB14 KB11 KB6 KB6KBPDE DocumentDocumentJSONICSV DocumentZIP archiveMarkdo…..ument5KRWindo...n image4 KB3 KBword ..cumentCSV Document2 KBYAML document1 KBcSV Document928 byteshttlicence183 bytesZero butesJSONAlfred.. ferencesZero bytesFolde1,9 MB1,8 MB17 MB1,7 MBColdorJPEG imageJPEG ImageIPEG imadeJPEG imageColdo1 of 58 selected, 9.63 GB availabld• Inu 14 May 1/.23.49Date AddedIs Mdl ZUzo dl 19:4530 Jan 2026 at 12:3617 Mar 2026 at 20:2717 Mar 2026 at 20:26Z3 Aor 2020 al 13:0229 Jan 2026 at 15:2019 Dec 2025 at 10:1619 Dec 2025 at 12:238 Aor 2026 at 20:3519 Dec 2025 at 10:2919 Dec 2025 at 12:1819 Dec 2025 at 12:4026 Mar 2026 at 11:2410 May 2026 at 13:5326 Mar 2026 at 11:2426 Mar 2026 at 11:2426 Mar 2026 at 11:2310 May 2026 at 14:3730 Jan 2026 at 12:3713 Feb 2026 at 11:54Z3 Apr 2026 at 13.0823 Aor 2026 at 13:0810 May 2026 at 13:5410 May 2026 at 14:3710 May 2026 at 13:4910 May 2026 at 13:5010 May 2026 at 13:5110 Mav 2026 at 12:51110 May 2026 at 13:5019 Dec 2025 at 11:3431 Oct 2025 at 12:2525 Nov 2025 at 17:596 Mar 2026 at 11.2224 Anr 2026 at 16:5220 Oct 2025 at 11:0218 Mar 2026 at 15:29• Mav 2026 at 11:09129 Oct 2025 at 19:329 May 2026 at 10:0418 Mar 2026 at 11:5510 May 2026 at 13:5712 Jun 2025 at 19:0430 Jan 2026 at 12:3630 Jan 2026 at 12:3616 Oct 2025 aт 16:0123 Apr 2026 at 13:0223 Apr 2026 at 13:0223 Apr 2026 at 13:0222 Anr 2026 at 12:0223 Apr 2026 at 13:0229 Jan 2026 at 15:20...
|
45171
|
NULL
|
NULL
|
NULL
|
|
45172
|
1621
|
21
|
2026-05-14T14:23:29.568203+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768609568_m1.jpg...
|
PhpStorm
|
faVsco.js – ActivityPlaybookTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStorm•FileEditViewNavigateCodeLaravelRefactorRu PhpStorm•FileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelpabl100% CFV faVsco.jsv#12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTestv8• Thu 14 May 17:23:29* :QProject v© ReportController.php© AutomatedReportGenerated.php:= custom.log18TrackAutomatedReportGeneratedEvent.phpPlaybackController.php> D CrmObjects• DecorateActivity> D Dummyv D Helpers.T, ActivityPlaybookTra© Arraylterator.php+ ConnectionState Tra© CrmHelperReposito17© FilterJoinedParticip32T OpportunitySyncab33v D Hubspot39D AccountSyncStrate40› D Actions44D ContactSyncStrate! 45> ODTO46› OJ Fields47› Journal48Metadata49v OpportunitySyncSti50> O Concerns51© HubspotLastMor52© HubspotLastMor53© HubspotLastMor54© HubspotLastMor55© HubspotLastMor56© HubspotSingleS:57© HubspotSyncStr58© HubspotWebhoc59v D Pagination60© HubspotPaginati 61© PaginationConfig 79© PaginationState.80> O ProspectSearchStra• Redisv O ServiceTraits(. OnnortunitvSvnrUserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.phpSendReportJob.php=laravel.log• DeleteCrmEntityTrait.phpDeleteAccountJob.php© ImportActivityTypes.phpT WriteCrmTrait.phpT ActivityPlaybookTrait.php xSF [jiminny@localhost]© AccountController.phpIntegrationAppTrait.php.env.staging.envDetachActivityObject.phpA HS_local [jiminny@localho:WRematchActivityOnCrmObjectDetach.phpMatchActivityCrmData.php© Client.php© HubspotPaginationService.php4 console [QAI PROD] XHandleHubspotRateLimit.phptrait ActivityPlaybookTrait*/4 console [PROD]A console (EU]protected function getPlaybook(User Suser): ?Playbook{...}protected function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory{...}public function matchesCrmType(PlaybookCategory $category): bool$playbook = Scategory->getPlaybookO:if ($playbook === null) {return false;SactivityField = $playbook->getActivityField();if (SactivityField === null) {return false;// Check the field values for a matching option.return $this->crmHelperRepository->existsActivityFieldValueByName($activityField, $category->getName());public function getCrmType(?PlaybookCategory $category): ?FieldValuel...}D V234Go jiminny0841A3 X4 A Vm migrations oim teams wherem crm_layouts !iM crm_layout_eiIM crm_fields Wim features;m team_feature:m opportunitie:m teams;1.id, CASE WHEN,id FROM social,1 on u.id= sa.:1..n<->1: on t.L_id = 1052 andIMaccounts wheiactivitiesWorkspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)W Windsurf Teams58:53UTF-8Co 4 spaces...
|
NULL
|
2668727136616592203
|
NULL
|
click
|
ocr
|
NULL
|
PhpStorm•FileEditViewNavigateCodeLaravelRefactorRu PhpStorm•FileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelpabl100% CFV faVsco.jsv#12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTestv8• Thu 14 May 17:23:29* :QProject v© ReportController.php© AutomatedReportGenerated.php:= custom.log18TrackAutomatedReportGeneratedEvent.phpPlaybackController.php> D CrmObjects• DecorateActivity> D Dummyv D Helpers.T, ActivityPlaybookTra© Arraylterator.php+ ConnectionState Tra© CrmHelperReposito17© FilterJoinedParticip32T OpportunitySyncab33v D Hubspot39D AccountSyncStrate40› D Actions44D ContactSyncStrate! 45> ODTO46› OJ Fields47› Journal48Metadata49v OpportunitySyncSti50> O Concerns51© HubspotLastMor52© HubspotLastMor53© HubspotLastMor54© HubspotLastMor55© HubspotLastMor56© HubspotSingleS:57© HubspotSyncStr58© HubspotWebhoc59v D Pagination60© HubspotPaginati 61© PaginationConfig 79© PaginationState.80> O ProspectSearchStra• Redisv O ServiceTraits(. OnnortunitvSvnrUserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.phpSendReportJob.php=laravel.log• DeleteCrmEntityTrait.phpDeleteAccountJob.php© ImportActivityTypes.phpT WriteCrmTrait.phpT ActivityPlaybookTrait.php xSF [jiminny@localhost]© AccountController.phpIntegrationAppTrait.php.env.staging.envDetachActivityObject.phpA HS_local [jiminny@localho:WRematchActivityOnCrmObjectDetach.phpMatchActivityCrmData.php© Client.php© HubspotPaginationService.php4 console [QAI PROD] XHandleHubspotRateLimit.phptrait ActivityPlaybookTrait*/4 console [PROD]A console (EU]protected function getPlaybook(User Suser): ?Playbook{...}protected function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory{...}public function matchesCrmType(PlaybookCategory $category): bool$playbook = Scategory->getPlaybookO:if ($playbook === null) {return false;SactivityField = $playbook->getActivityField();if (SactivityField === null) {return false;// Check the field values for a matching option.return $this->crmHelperRepository->existsActivityFieldValueByName($activityField, $category->getName());public function getCrmType(?PlaybookCategory $category): ?FieldValuel...}D V234Go jiminny0841A3 X4 A Vm migrations oim teams wherem crm_layouts !iM crm_layout_eiIM crm_fields Wim features;m team_feature:m opportunitie:m teams;1.id, CASE WHEN,id FROM social,1 on u.id= sa.:1..n<->1: on t.L_id = 1052 andIMaccounts wheiactivitiesWorkspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)W Windsurf Teams58:53UTF-8Co 4 spaces...
|
45170
|
NULL
|
NULL
|
NULL
|
|
45175
|
1621
|
22
|
2026-05-14T14:23:35.470701+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768615470_m1.jpg...
|
PhpStorm
|
faVsco.js – CrmHelperRepository.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.054166667,"top":0.027777778,"width":0.08055556,"height":0.035555556},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#12066 on JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.13472222,"top":0.027777778,"width":0.25555557,"height":0.035555556},"on_screen":true,"help_text":"Pull request #12066 exists for current branch JY-20725-handle-HS-search-rate-limit, 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.6326389,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HandleHubspotRateLimitTest","depth":6,"bounds":{"left":0.6645833,"top":0.027777778,"width":0.15902779,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.82361114,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.8472222,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.87083334,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.9291667,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-5784594327568142026
|
-7196678489907033719
|
visual_change
|
hybrid
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelpabl•FV faVsco.jsv#12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTestv100% (C478• Thu 14 May 17:23:35QProject v© ReportController.php© AutomatedReportGenerated.php:18TrackAutomatedReportGeneratedEvent.phpPlaybackController.php• CrmObjects• DecorateActivity> D Dummyv @ Helpers.T ActivityPlaybookTra© Arraylterator.php€ ConnectionStateTra© CrmHelperReposito© FilterJoinedParticipT OpportunitySyncab1314v D HubspotD AccountSyncStrate15› D Actions16D ContactSyncStrate!17> ODTO18› OJ Fields19› 0 Journal27Metadatav OpportunitySyncSti28> O Concerns35© HubspotLastMor© HubspotLastMor36© HubspotLastMor37© HubspotLastMor38© HubspotLastMor39© HubspotSingleS:40© HubspotSyncStr4142© HubspotWebhocv D Pagination43© HubspotPaginati49© PaginationConfig50© PaginationState.> O ProspectSearchStraRedisv O ServiceTraits(. OnnortunitvSvnrUserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.phpSendReportJob.php• DeleteCrmEntityTrait.phpDeleteAccountJob.php© ImportActivityTypes.phpT WriteCrmTrait.php© ActivityPlaybookTrait.php© CrmHelperRepository.php xAccountController.phpT IntegrationAppTrait.php.env.staging= .env© DetachActivityObject.phpRematchActivityOnCrmObjectDetach.php© MatchActivityCrmData.php© Client.php© HubspotPaginationService.phpHandleHubspotRateLimit.phpUSe/***/* A collection of DB-related internal functionalities or all CRMs.class CrmHelperRepositorypublic function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory{...}1 usagepublic function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue{…}1 usagepublic function existsActivityFieldValueByName(Field SactivityField, string $categoryName): boolreturn $activityField->values()->whSactivityField: Field->exSource: .../app/Services/Crm/Helpers/CrmHelperRepository.phppublic funct0 :reEnum $featureName): boolf...}Workspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)=custom.log=laravel.logSF [jiminny@localhost]A HS_local [jiminny@localho:W4 console [QAI PROD] X4 console [PROD]A console (EU]D V234Go jiminny0841A3 X4 A Vm migrations oim teams wherem crm_layouts !iM crm_layout_eiIM crm_fields Wim features;m team_feature:m opportunitie:m teams;1.id, CASEWHENidFROMsocial.1 on u.id= sa.:1..n<->1: on t.Lid = 1052 andIMaccounts wheiactivities36:21UTF-8W Windsurf TeamsCo 4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45174
|
1622
|
12
|
2026-05-14T14:23:35.799112+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768615799_m2.jpg...
|
PhpStorm
|
faVsco.js – CrmHelperRepository.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
Execute...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.2962101,"top":1.0,"width":0.03856383,"height":-0.019952059},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#12066 on JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.33477393,"top":1.0,"width":0.122340426,"height":-0.019952059},"on_screen":true,"help_text":"Pull request #12066 exists for current branch JY-20725-handle-HS-search-rate-limit, 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.5731383,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HandleHubspotRateLimitTest","depth":6,"bounds":{"left":0.5884308,"top":1.0,"width":0.076130316,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.66456115,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.67586434,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.6871675,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.7150931,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.72639626,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.73769945,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-3489503532064552681
|
-8168608824874202789
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
Execute...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45176
|
1621
|
23
|
2026-05-14T14:23:39.593326+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768619593_m1.jpg...
|
PhpStorm
|
faVsco.js – CrmHelperRepository.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorRun PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelpabl•FV faVsco.jsv#12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTestv100% (C478• Thu 14 May 17:23:39QProject v© ReportController.php© AutomatedReportGenerated.php:18TrackAutomatedReportGeneratedEvent.phpPlaybackController.php• CrmObjectsO DecorateActivity> D Dummyv @ Helpers.T ActivityPlaybookTra© Arraylterator.php+ ConnectionState Tra© CrmHelperReposito© FilterJoinedParticipT OpportunitySyncab1314v D HubspotD AccountSyncStrate15› D Actions16D ContactSyncStrate!17> ODTO18› OJ Fields19› 0 Journal27Metadata28v OpportunitySyncSti> O Concerns35© HubspotLastMor© HubspotLastMor3637© HubspotLastMor© HubspotLastMor38© HubspotLastMor39© HubspotSingleS:40© HubspotSyncStr4142© HubspotWebhocv D Pagination43© HubspotPaginati49© PaginationConfig50© PaginationState.> O ProspectSearchStra• Redisv O ServiceTraits(. OnnortunitvSvnrUserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.phpSendReportJob.php• DeleteCrmEntityTrait.phpDeleteAccountJob.php© ImportActivityTypes.phpT WriteCrmTrait.php© ActivityPlaybookTrait.php© CrmHelperRepository.php xAccountController.phpT IntegrationAppTrait.php.env.staging= .env© DetachActivityObject.phpRematchActivityOnCrmObjectDetach.php© MatchActivityCrmData.php© Client.php© HubspotPaginationService.phpHandleHubspotRateLimit.phpUSe/***/* A collection of DB-related internal functionalities or all CRMs.class CrmHelperRepositorypublic function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory{...}1 usagepublic function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue{…}1 usagepublic function existsActivityFieldValueByName(Field $activityField, string $categoryName): boolreturn $activityField->values)l->where( column: 'Label', ScategoryName)-›exists():public function hasFeature(Activity $activity, FeatureEnum $featureName): boolf...}Workspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)=custom.log=laravel.logSF [jiminny@localhost]A HS_local [jiminny@localho:W4 console [QAI PROD] X4 console [PROD]A console (EU]D V234Go jiminny0841A3 X4 A Vm migrations oim teams wherem crm_layouts !M crm_layout_eilIM crm_fields Wim features;m team_feature:m opportunitie:m teams;1.id, CASEWHENidFROMsocial.i on u.id= sa.:1..n<->1: on t.Lid = 1052 andIMaccounts wheiactivitiesUTF-8W Windsurf Teams38:40Co 4 spaces...
|
NULL
|
5406396250506849296
|
NULL
|
click
|
ocr
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorRun PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelpabl•FV faVsco.jsv#12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTestv100% (C478• Thu 14 May 17:23:39QProject v© ReportController.php© AutomatedReportGenerated.php:18TrackAutomatedReportGeneratedEvent.phpPlaybackController.php• CrmObjectsO DecorateActivity> D Dummyv @ Helpers.T ActivityPlaybookTra© Arraylterator.php+ ConnectionState Tra© CrmHelperReposito© FilterJoinedParticipT OpportunitySyncab1314v D HubspotD AccountSyncStrate15› D Actions16D ContactSyncStrate!17> ODTO18› OJ Fields19› 0 Journal27Metadata28v OpportunitySyncSti> O Concerns35© HubspotLastMor© HubspotLastMor3637© HubspotLastMor© HubspotLastMor38© HubspotLastMor39© HubspotSingleS:40© HubspotSyncStr4142© HubspotWebhocv D Pagination43© HubspotPaginati49© PaginationConfig50© PaginationState.> O ProspectSearchStra• Redisv O ServiceTraits(. OnnortunitvSvnrUserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.phpSendReportJob.php• DeleteCrmEntityTrait.phpDeleteAccountJob.php© ImportActivityTypes.phpT WriteCrmTrait.php© ActivityPlaybookTrait.php© CrmHelperRepository.php xAccountController.phpT IntegrationAppTrait.php.env.staging= .env© DetachActivityObject.phpRematchActivityOnCrmObjectDetach.php© MatchActivityCrmData.php© Client.php© HubspotPaginationService.phpHandleHubspotRateLimit.phpUSe/***/* A collection of DB-related internal functionalities or all CRMs.class CrmHelperRepositorypublic function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory{...}1 usagepublic function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue{…}1 usagepublic function existsActivityFieldValueByName(Field $activityField, string $categoryName): boolreturn $activityField->values)l->where( column: 'Label', ScategoryName)-›exists():public function hasFeature(Activity $activity, FeatureEnum $featureName): boolf...}Workspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)=custom.log=laravel.logSF [jiminny@localhost]A HS_local [jiminny@localho:W4 console [QAI PROD] X4 console [PROD]A console (EU]D V234Go jiminny0841A3 X4 A Vm migrations oim teams wherem crm_layouts !M crm_layout_eilIM crm_fields Wim features;m team_feature:m opportunitie:m teams;1.id, CASEWHENidFROMsocial.i on u.id= sa.:1..n<->1: on t.Lid = 1052 andIMaccounts wheiactivitiesUTF-8W Windsurf Teams38:40Co 4 spaces...
|
45175
|
NULL
|
NULL
|
NULL
|
|
45177
|
1622
|
13
|
2026-05-14T14:23:39.593444+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768619593_m2.jpg...
|
PhpStorm
|
faVsco.js – CrmHelperRepository.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
ActivityFilesLaterJiminny... ~@ jiminny-x-integrat ActivityFilesLaterJiminny... ~@ jiminny-x-integrati& platform-inner-team© Channels# ai-chapter# alertsi backend# bugscontusion-cllnia# curiosity_lab# engineering# general# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the people of iimi..• Direct messages8. A...• A3 62P. Galya Dimitrova Mvasil VasilevA. Stefka Stoyanova%: Todor Stamatovf. Mario GeorgievP. Nikolay Ivanov2o James Graham "2. Stoyan Tanev. Steliyan Georgiev. Petko Kashinski*. Lukas Kovali...a: Apps® ToastS lira Gloud6 Huddle with Aneliya Angelova& R. Aneliya Angelova •• Messagest Add canvasur FilesAneliya Angelova 2:30 PMЛукаш, само при Сейлсфорс и Хубспот се синкваха активити типовете, когато се направи плейбук,нали?пои останалите скМі тоябва оъчно ла се въвелат.Lukas Kovalik 2:47 PMтряова да се за всички, някьде не се ли попьлвапри зохо май беше hardcoded но май и там си връщаха две категорииAneliya Angelova @ 4:00 PMЛукаш имаш ли време да се чуем за тестването на https://jiminny.atlassian.net/browse/JY-20725X Preview insackt[HubSpot] Optimise CRM rematctC OpenReady for QA- MediumAA Aneliya AngelovaAs of today at 4:00 PM RetreshOpen in Jira+ SummariseLukas Kovalik 4:02 PMzean-iurwiniareelYou joined the huddle LIVE 4:06 PMAneliva Angelova is here toolAneliya Angelova 4:44 PM11512582Lukas Kovalik O 5:16 PMcurl --location "httns:ani.hubani.com/crm/v3/obiects/contacts/search--header 'Content-Type: application/json'\--header "Authorization: BearerC|Kvma?iMx7OINOM8kOFwrAowAcAkUAhIR24?A05D?xiVrIYMILuP6SA01Kwcmhr=n.v2 hytorDniMvDalYck-CMalovolNOM9kOFwrAUAcrkcaws-ThwRARIRAOF-ATFSAOFRAO-ROIEBAQUBEggBAQEBAYICFIґ7Au8O2Nwal9YW2CbCCxYvwngmSgNldTFSAFoAYABoAHAAeAA'\--data "{ "limit", 13'Message Aneliva Angelova+ AaIAl Notes: OffLeave~ (Q SearchDate ModifiedYesterdav at 13.33Yesterday at 13:33Yesterday at 13.32Yesterdav at 13:31Yesterday at 13:30Yesterdav at 13:30Yesterday at 13:29Yesterday at 13.28Yesterday at 13:28resterday at 15.21Yesterdav at 13:26Yesterday at 13:26Yesterday at 13:25Yesterday at 13:23Yesterday at 13:24resterday at 13-43Yesterdav at 13:22Yesterday at 13:20Yesterday at 13:20Yesterday at 13.19Yesterdav at 13:18Yesterday at 13:18Yesterday at 13:17Yesterday at 13:17Yesterday at 13:16Yesterday at 13.15Yecterdav at 12:15Yesterday at 13:14Yesterday at 13-14Yesterdav at 13:13Yesterday at 13:13Yesterday at 13:12Yecterdav at 12:11Yesterday at 13:10Yesterdav at 13:10Yesterday at 13:09Yesterday at 13:08Yoctorday at 12:09Yesterday at 13.0/Yocterdav at 13:06Yesterday at 13:06Yesterdav at 13:05MPEG-4 movie12 KBMPEG-4 movie9 KBMPEG-4 movieMPEG-4 movie8 KE37 KB10 KBMPEG-4 movie7 K:8 KBMPEG-4 movie9 KBMPEG-4 movie72 KBMPEG-4 movie14 KB15 KbMPEG-4 movie9 K:18 KBMPEG-4 movie12 KB10 K:16 KRIMPEG-4 movieMPEG-4 movie6 KBMPEG-4 movie12 KE23 KBMPEG-4 movie8 KB6 KB1MPEG-4 movie6 KBMPEG-4 movie11 KB11 KBMPEG-4 movie20 KEMPEG-4 movie34 KBMPEG-4 movie10 KB7K:MPEG-4 movie5 KBMPEG-4 movie11 KB26 KBMPEG-4 movie111 KPMPEG-4 movie102 KBMPEG-4 movie88 KB59 K:MPEG-4 movie98 KBMPEG-4 movie97 KB66 KBЛAKEMPEG-4 movieMDEG-A movie93 KB78 KBMPEG-4 movie50 K:MPEG-A movid58 KB MPEG-4 movie27 KB7 KB112 KрMPEG-4 movieMPEG-4 movie32 KB17 KBMPEG-4 movie19 Kг32 KBMPEG-4 movie10 KB24 K8MPEG-4 movieFavouritesE jiminny© Recents* ApplicationsiCloudiCloud Drive228 Sync folderQ DXP4800PLUS-B5FA@ Network|• CRMI• Orange• Red• Yellow• Green• Purple•) All lags..lohlDownloadsNameLoom.pkgAlfred copv.alfredoreterencesB KeychronAssist-1.0.2 (1).dmgA Keychron Assist-1.0.2.dmgmazanoke-images-ywJo.ziPhotos-3-001.zipD Transcript.pdf→mage U.loge1 Orioninstaller.dma- image (2).1pc• ПО-22221726037035-004-001_ORGES.pdf• %D0%9F%D0%9E-22221726037035-004-001_archive.zipПO-22221726037035-004-001_archive (1).zip• repon 4).XmAltred copy2.altredoreterences05012026_0000000026574472_ SWIFT_OB70501260015890.pdf27022026_0000000026574472_SWIFT_OB72702260049200.pdf= 03042026 [CREDIT_CARD] SWIFT [CREDIT_CARD].001B reporti) xm=pdt.odipdf-1.pdfD pdf-2.pdf-pdf-5.pd1= ndf-1 ndipdf-3.pdfB Rovaix Famly Treo gedbitwarden export 20251031122528.isonKoválik Family Tree.zip*macOS Storage_Cleanup.mdal favicon icofirst_aid_notes_complete.docxrenortl2).esvconfig.ymlIteration run Search HS.postman_collection.json--report(1).csvm licence hettertouchtoalMariusHosting Config.json1ooks-891a6503-bbb7-4b2b-9c3.csv•Alfredmazanoke-images-YWJ6ана Ковалик.jpg•искане даниел Ковалик..pg• Фактура Март Даниел Ковалик.jрс• Фактура Април Даниел Ковалик.jpg• Dhotac 2.001Q SearchKind00,4 MDinstdlle..dckage55.9 MBAlfred...ferences10,1 MBDisk Image10,1 MBDisk ImageIL MBLiP archive6,6 MBZIP archive2,5 MB PDF Document2,5 MBJreo lmage2.2 MBDisk Image2,2 MB PDF Document2 MBJPEG image1,9 MBJPEG imaqe192 KBPDF Document140 KbZIP archive148 KВZIP archivel148 KBZIP archive122 KBXML document111 KBAlfred...ferences94 KBPDF Document92 KBPDr DocumentK:91 KB91 KB30 KB29 KBPDF DocumentXML documentPDF Document28 KBPDF Document28 KR28 KB27 KB14 KB11 KB6 KB6KBPDE DocumentDocumentJSONICSV DocumentZIP archiveMarkdo…..ument5KRWindo...n image4 KB3 KBword ..cumentCSV Document2 KBYAML document1 KBcSV Document928 byteshttlicence183 bytesZero butesJSONAlfred.. ferencesZero bytesFolde1,9 MB1,8 MB17 MB1,7 MBColdorJPEG imageJPEG ImageIPEG imadeJPEG imageColdo1 of 58 selected, 9.63 GB availabld• Inu 14 Mау 1/•23.34Date AddedIs Mdl ZUzo dl 19:4530 Jan 2026 at 12:3617 Mar 2026 at 20:2717 Mar 2026 at 20:26Z3 Aor 2020 al 13:0229 Jan 2026 at 15:2019 Dec 2025 at 10:1619 Dec 2025 at 12:238 Aor 2026 at 20:3519 Dec 2025 at 10:2919 Dec 2025 at 12:1819 Dec 2025 at 12:4026 Mar 2026 at 11:2410 May 2026 at 13:5326 Mar 2026 at 11:2426 Mar 2026 at 11:2426 Mar 2026 at 11:2310 May 2026 at 14:3730 Jan 2026 at 12:3713 Feb 2026 at 11:54Z3 Apr 2026 at 13.0823 Aor 2026 at 13:0810 May 2026 at 13:5410 May 2026 at 14:3710 May 2026 at 13:4910 May 2026 at 13:5010 May 2026 at 13:5110 Mav 2026 at 12:51110 May 2026 at 13:5019 Dec 2025 at 11:3431 Oct 2025 at 12:2525 Nov 2025 at 17:596 Mar 2026 at 11.2224 Anr 2026 at 16:5220 Oct 2025 at 11:0218 Mar 2026 at 15:29• Mav 2026 at 11:09129 Oct 2025 at 19:329 May 2026 at 10:0418 Mar 2026 at 11:5510 May 2026 at 13:5712 Jun 2025 at 19:0430 Jan 2026 at 12:3630 Jan 2026 at 12:3616 Oct 2025 aт 16:0123 Apr 2026 at 13:0223 Apr 2026 at 13:0223 Apr 2026 at 13:0222 Anr 2026 at 12:0223 Apr 2026 at 13:0229 Jan 2026 at 15:20...
|
NULL
|
-5006563693464471080
|
NULL
|
click
|
ocr
|
NULL
|
ActivityFilesLaterJiminny... ~@ jiminny-x-integrat ActivityFilesLaterJiminny... ~@ jiminny-x-integrati& platform-inner-team© Channels# ai-chapter# alertsi backend# bugscontusion-cllnia# curiosity_lab# engineering# general# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the people of iimi..• Direct messages8. A...• A3 62P. Galya Dimitrova Mvasil VasilevA. Stefka Stoyanova%: Todor Stamatovf. Mario GeorgievP. Nikolay Ivanov2o James Graham "2. Stoyan Tanev. Steliyan Georgiev. Petko Kashinski*. Lukas Kovali...a: Apps® ToastS lira Gloud6 Huddle with Aneliya Angelova& R. Aneliya Angelova •• Messagest Add canvasur FilesAneliya Angelova 2:30 PMЛукаш, само при Сейлсфорс и Хубспот се синкваха активити типовете, когато се направи плейбук,нали?пои останалите скМі тоябва оъчно ла се въвелат.Lukas Kovalik 2:47 PMтряова да се за всички, някьде не се ли попьлвапри зохо май беше hardcoded но май и там си връщаха две категорииAneliya Angelova @ 4:00 PMЛукаш имаш ли време да се чуем за тестването на https://jiminny.atlassian.net/browse/JY-20725X Preview insackt[HubSpot] Optimise CRM rematctC OpenReady for QA- MediumAA Aneliya AngelovaAs of today at 4:00 PM RetreshOpen in Jira+ SummariseLukas Kovalik 4:02 PMzean-iurwiniareelYou joined the huddle LIVE 4:06 PMAneliva Angelova is here toolAneliya Angelova 4:44 PM11512582Lukas Kovalik O 5:16 PMcurl --location "httns:ani.hubani.com/crm/v3/obiects/contacts/search--header 'Content-Type: application/json'\--header "Authorization: BearerC|Kvma?iMx7OINOM8kOFwrAowAcAkUAhIR24?A05D?xiVrIYMILuP6SA01Kwcmhr=n.v2 hytorDniMvDalYck-CMalovolNOM9kOFwrAUAcrkcaws-ThwRARIRAOF-ATFSAOFRAO-ROIEBAQUBEggBAQEBAYICFIґ7Au8O2Nwal9YW2CbCCxYvwngmSgNldTFSAFoAYABoAHAAeAA'\--data "{ "limit", 13'Message Aneliva Angelova+ AaIAl Notes: OffLeave~ (Q SearchDate ModifiedYesterdav at 13.33Yesterday at 13:33Yesterday at 13.32Yesterdav at 13:31Yesterday at 13:30Yesterdav at 13:30Yesterday at 13:29Yesterday at 13.28Yesterday at 13:28resterday at 15.21Yesterdav at 13:26Yesterday at 13:26Yesterday at 13:25Yesterday at 13:23Yesterday at 13:24resterday at 13-43Yesterdav at 13:22Yesterday at 13:20Yesterday at 13:20Yesterday at 13.19Yesterdav at 13:18Yesterday at 13:18Yesterday at 13:17Yesterday at 13:17Yesterday at 13:16Yesterday at 13.15Yecterdav at 12:15Yesterday at 13:14Yesterday at 13-14Yesterdav at 13:13Yesterday at 13:13Yesterday at 13:12Yecterdav at 12:11Yesterday at 13:10Yesterdav at 13:10Yesterday at 13:09Yesterday at 13:08Yoctorday at 12:09Yesterday at 13.0/Yocterdav at 13:06Yesterday at 13:06Yesterdav at 13:05MPEG-4 movie12 KBMPEG-4 movie9 KBMPEG-4 movieMPEG-4 movie8 KE37 KB10 KBMPEG-4 movie7 K:8 KBMPEG-4 movie9 KBMPEG-4 movie72 KBMPEG-4 movie14 KB15 KbMPEG-4 movie9 K:18 KBMPEG-4 movie12 KB10 K:16 KRIMPEG-4 movieMPEG-4 movie6 KBMPEG-4 movie12 KE23 KBMPEG-4 movie8 KB6 KB1MPEG-4 movie6 KBMPEG-4 movie11 KB11 KBMPEG-4 movie20 KEMPEG-4 movie34 KBMPEG-4 movie10 KB7K:MPEG-4 movie5 KBMPEG-4 movie11 KB26 KBMPEG-4 movie111 KPMPEG-4 movie102 KBMPEG-4 movie88 KB59 K:MPEG-4 movie98 KBMPEG-4 movie97 KB66 KBЛAKEMPEG-4 movieMDEG-A movie93 KB78 KBMPEG-4 movie50 K:MPEG-A movid58 KB MPEG-4 movie27 KB7 KB112 KрMPEG-4 movieMPEG-4 movie32 KB17 KBMPEG-4 movie19 Kг32 KBMPEG-4 movie10 KB24 K8MPEG-4 movieFavouritesE jiminny© Recents* ApplicationsiCloudiCloud Drive228 Sync folderQ DXP4800PLUS-B5FA@ Network|• CRMI• Orange• Red• Yellow• Green• Purple•) All lags..lohlDownloadsNameLoom.pkgAlfred copv.alfredoreterencesB KeychronAssist-1.0.2 (1).dmgA Keychron Assist-1.0.2.dmgmazanoke-images-ywJo.ziPhotos-3-001.zipD Transcript.pdf→mage U.loge1 Orioninstaller.dma- image (2).1pc• ПО-22221726037035-004-001_ORGES.pdf• %D0%9F%D0%9E-22221726037035-004-001_archive.zipПO-22221726037035-004-001_archive (1).zip• repon 4).XmAltred copy2.altredoreterences05012026_0000000026574472_ SWIFT_OB70501260015890.pdf27022026_0000000026574472_SWIFT_OB72702260049200.pdf= 03042026 [CREDIT_CARD] SWIFT [CREDIT_CARD].001B reporti) xm=pdt.odipdf-1.pdfD pdf-2.pdf-pdf-5.pd1= ndf-1 ndipdf-3.pdfB Rovaix Famly Treo gedbitwarden export 20251031122528.isonKoválik Family Tree.zip*macOS Storage_Cleanup.mdal favicon icofirst_aid_notes_complete.docxrenortl2).esvconfig.ymlIteration run Search HS.postman_collection.json--report(1).csvm licence hettertouchtoalMariusHosting Config.json1ooks-891a6503-bbb7-4b2b-9c3.csv•Alfredmazanoke-images-YWJ6ана Ковалик.jpg•искане даниел Ковалик..pg• Фактура Март Даниел Ковалик.jрс• Фактура Април Даниел Ковалик.jpg• Dhotac 2.001Q SearchKind00,4 MDinstdlle..dckage55.9 MBAlfred...ferences10,1 MBDisk Image10,1 MBDisk ImageIL MBLiP archive6,6 MBZIP archive2,5 MB PDF Document2,5 MBJreo lmage2.2 MBDisk Image2,2 MB PDF Document2 MBJPEG image1,9 MBJPEG imaqe192 KBPDF Document140 KbZIP archive148 KВZIP archivel148 KBZIP archive122 KBXML document111 KBAlfred...ferences94 KBPDF Document92 KBPDr DocumentK:91 KB91 KB30 KB29 KBPDF DocumentXML documentPDF Document28 KBPDF Document28 KR28 KB27 KB14 KB11 KB6 KB6KBPDE DocumentDocumentJSONICSV DocumentZIP archiveMarkdo…..ument5KRWindo...n image4 KB3 KBword ..cumentCSV Document2 KBYAML document1 KBcSV Document928 byteshttlicence183 bytesZero butesJSONAlfred.. ferencesZero bytesFolde1,9 MB1,8 MB17 MB1,7 MBColdorJPEG imageJPEG ImageIPEG imadeJPEG imageColdo1 of 58 selected, 9.63 GB availabld• Inu 14 Mау 1/•23.34Date AddedIs Mdl ZUzo dl 19:4530 Jan 2026 at 12:3617 Mar 2026 at 20:2717 Mar 2026 at 20:26Z3 Aor 2020 al 13:0229 Jan 2026 at 15:2019 Dec 2025 at 10:1619 Dec 2025 at 12:238 Aor 2026 at 20:3519 Dec 2025 at 10:2919 Dec 2025 at 12:1819 Dec 2025 at 12:4026 Mar 2026 at 11:2410 May 2026 at 13:5326 Mar 2026 at 11:2426 Mar 2026 at 11:2426 Mar 2026 at 11:2310 May 2026 at 14:3730 Jan 2026 at 12:3713 Feb 2026 at 11:54Z3 Apr 2026 at 13.0823 Aor 2026 at 13:0810 May 2026 at 13:5410 May 2026 at 14:3710 May 2026 at 13:4910 May 2026 at 13:5010 May 2026 at 13:5110 Mav 2026 at 12:51110 May 2026 at 13:5019 Dec 2025 at 11:3431 Oct 2025 at 12:2525 Nov 2025 at 17:596 Mar 2026 at 11.2224 Anr 2026 at 16:5220 Oct 2025 at 11:0218 Mar 2026 at 15:29• Mav 2026 at 11:09129 Oct 2025 at 19:329 May 2026 at 10:0418 Mar 2026 at 11:5510 May 2026 at 13:5712 Jun 2025 at 19:0430 Jan 2026 at 12:3630 Jan 2026 at 12:3616 Oct 2025 aт 16:0123 Apr 2026 at 13:0223 Apr 2026 at 13:0223 Apr 2026 at 13:0222 Anr 2026 at 12:0223 Apr 2026 at 13:0229 Jan 2026 at 15:20...
|
45174
|
NULL
|
NULL
|
NULL
|
|
45178
|
1621
|
24
|
2026-05-14T14:23:42.176417+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768622176_m1.jpg...
|
PhpStorm
|
faVsco.js – CrmHelperRepository.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorRun PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelplhl•FV faVsco.jsv#12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTestv100% (C478• Thu 14 May 17:23:41QProject v© ReportController.php© AutomatedReportGenerated.php:18TrackAutomatedReportGeneratedEvent.phpPlaybackController.php• CrmObjectsO DecorateActivity> D Dummyv D Helpers.T ActivityPlaybookTra© Arraylterator.php+ ConnectionState Tra© CrmHelperReposito© FilterJoinedParticipT OpportunitySyncab1314v D HubspotD AccountSyncStrate15› D Actions16D ContactSyncStrate!17> ODTO18› OJ Fields19› 0 Journal27Metadata28v OpportunitySyncSti> O Concerns35© HubspotLastMor© HubspotLastMor3637© HubspotLastMor© HubspotLastMor3839© HubspotLastMor© HubspotSingleS:40© HubspotSyncStr4142© HubspotWebhocv D Pagination43© HubspotPaginati49© PaginationConfig50© PaginationState.> O ProspectSearchStra• Redisv O ServiceTraits(. OnnortunitvSvnrUserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.phpSendReportJob.php• DeleteCrmEntityTrait.phpDeleteAccountJob.php© ImportActivityTypes.phpT WriteCrmTrait.php© ActivityPlaybookTrait.php© CrmHelperRepository.php xAccountController.phpT IntegrationAppTrait.php.env.staging= .env© DetachActivityObject.phpRematchActivityOnCrmObjectDetach.php© MatchActivityCrmData.php© Client.php© HubspotPaginationService.phpHandleHubspotRateLimit.phpUSe/***/* A collection of DB-related internal functionalities or all CRMs.class CrmHelperRepositorypublic function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory{...}1 usagepublic function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue{…}1 usagepublic function existsActivityFieldValueByName(Field $activityField, string $categoryName): boolreturn $activityField->values()->where( column: 'Label', $categohyName)->exists();public function hasFeature(Activity $activity, FeatureEnum $featureName): boolf...}Workspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)=custom.log=laravel.logSF [jiminny@localhost]A HS_local [jiminny@localho:W4 console [QAI PROD] X4 console [PROD]A console (EU]D V234Go jiminny0841A3 X4 A Vm migrations oim teams wherem crm_layouts !M crm_layout_eilIM crm_fields Wim features;m team_feature:m opportunitie:m teams;1.id, CASEWHENidFROMsocial.1 on u.id= sa.:1..n<->1: on t.Lid = 1052 andIMaccounts wheiactivities39:37UTF-8W Windsurf TeamsCo 4 spaces...
|
NULL
|
2342549807754423779
|
NULL
|
click
|
ocr
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorRun PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelplhl•FV faVsco.jsv#12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTestv100% (C478• Thu 14 May 17:23:41QProject v© ReportController.php© AutomatedReportGenerated.php:18TrackAutomatedReportGeneratedEvent.phpPlaybackController.php• CrmObjectsO DecorateActivity> D Dummyv D Helpers.T ActivityPlaybookTra© Arraylterator.php+ ConnectionState Tra© CrmHelperReposito© FilterJoinedParticipT OpportunitySyncab1314v D HubspotD AccountSyncStrate15› D Actions16D ContactSyncStrate!17> ODTO18› OJ Fields19› 0 Journal27Metadata28v OpportunitySyncSti> O Concerns35© HubspotLastMor© HubspotLastMor3637© HubspotLastMor© HubspotLastMor3839© HubspotLastMor© HubspotSingleS:40© HubspotSyncStr4142© HubspotWebhocv D Pagination43© HubspotPaginati49© PaginationConfig50© PaginationState.> O ProspectSearchStra• Redisv O ServiceTraits(. OnnortunitvSvnrUserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.phpSendReportJob.php• DeleteCrmEntityTrait.phpDeleteAccountJob.php© ImportActivityTypes.phpT WriteCrmTrait.php© ActivityPlaybookTrait.php© CrmHelperRepository.php xAccountController.phpT IntegrationAppTrait.php.env.staging= .env© DetachActivityObject.phpRematchActivityOnCrmObjectDetach.php© MatchActivityCrmData.php© Client.php© HubspotPaginationService.phpHandleHubspotRateLimit.phpUSe/***/* A collection of DB-related internal functionalities or all CRMs.class CrmHelperRepositorypublic function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory{...}1 usagepublic function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue{…}1 usagepublic function existsActivityFieldValueByName(Field $activityField, string $categoryName): boolreturn $activityField->values()->where( column: 'Label', $categohyName)->exists();public function hasFeature(Activity $activity, FeatureEnum $featureName): boolf...}Workspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)=custom.log=laravel.logSF [jiminny@localhost]A HS_local [jiminny@localho:W4 console [QAI PROD] X4 console [PROD]A console (EU]D V234Go jiminny0841A3 X4 A Vm migrations oim teams wherem crm_layouts !M crm_layout_eilIM crm_fields Wim features;m team_feature:m opportunitie:m teams;1.id, CASEWHENidFROMsocial.1 on u.id= sa.:1..n<->1: on t.Lid = 1052 andIMaccounts wheiactivities39:37UTF-8W Windsurf TeamsCo 4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45179
|
1621
|
25
|
2026-05-14T14:23:50.137655+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768630137_m1.jpg...
|
PhpStorm
|
faVsco.js – WriteCrmTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorRun PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelpabl100% C•FV faVsco.jsv#12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTest8• Thu 14 May 17:23:50* :QProject v© ReportController.php© AutomatedReportGenerated.phpTrackAutomatedReportGeneratedEvent.phpPlaybackController.php:=custom.log€ ConnectionStateTraUserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.phpSendReportJob.php© CrmHelperReposito© FilterJoinedParticip• DeleteCrmEntityTrait.phpDeleteAccountJob.php© ImportActivityTypes.phpWriteCrmTrait.php X© ActivityPlaybookTrait.phpT OpportunitySyncab© CrmHelperRepository.php© AccountController.phpT IntegrationAppTrait.php.env.staging.envv HubspotDetachActivityObject.phpC RematchActivityOnCrmObjectDetach.php© MatchActivityCrmData.php© Client.php18D AccountSyncStrate> DJ ActionsD ContactSyncStrate!› O DTO› O Fields› D JournalMetadata© HubspotPaginationService.phptrait WriteCrmTrait* @throws HubspotException* @throws BadRequest*/1 usageHandleHubspotRateLimit.php19525354A8 ж6^v OpportunitySyncSti55private function buildConferencePayload(Activity Sactivity): Activity> [ Concerns56© HubspotLastMor57$decorator = $this->getDecorateActivity();© HubspotLastMor58© HubspotLastMor59© HubspotLastMor60© HubspotLastMor61© HubspotSingleS:62© HubspotSyncStr63$engagement = l'active' => true,'ownerId' = $this->profile->getCrmProviderId®,'timestamp' => $decorator->formatStartTime(Sactivity),'type' => 'MEETING',© HubspotWebhoc64]:v D Pagination65© HubspotPaginati66© PaginationConfig67if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType))) {$engagement['activityType'] = $activity->getActivityType()->getNameO:© PaginationState.68> D ProspectSearchStra69› [ Redis70v D ServiceTraits71€ OpportunitySync72$metadata = ['meetingOutcome' => $decorator->convertMeetingOutcome(Sactivity),'internalMeetingNotes' => $decorator-›generateDescription($activity),© SyncCrmEntities73+ SyncFieldsTrait./74T WriteCrmTrait.pl75// Only if they are creating a fresh engagement should we update the times, otherwise it will cause a ‹232D Utils76if (! $activity->hasCrmProviderId)) {D Webhook77if (Sactivity->hasActualStartTime() && $activity->hasActualEndTime)) {D— 211212- =226227- 228229230231- 233 v234@ RatchSvneCnllertn78Smetadatal 'startTime'l = Sdecorator->formatStartTime(Sactivitv):Workspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)W Windsurf Teams=laravel.logSF [jiminny@localhost]A HS_local [jiminny@localho:W4 console [QAI PROD] X4 console [PROD]A console (EU]Go jiminny0841A3 X4 A Vm migrations oim teams wherem crm_layouts !iM crm_layout_eiIM crm_fields Wim features;m team_feature:m opportunitie:m teams;1.id, CASEWHENidFROMsocial,1 on u.id= sa.:1..n<->1: on t.L_id = 1052 andIMaccounts wheiactivities66:60UTF-8Co 4 spaces...
|
NULL
|
-6670788643496401873
|
NULL
|
click
|
ocr
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorRun PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelpabl100% C•FV faVsco.jsv#12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTest8• Thu 14 May 17:23:50* :QProject v© ReportController.php© AutomatedReportGenerated.phpTrackAutomatedReportGeneratedEvent.phpPlaybackController.php:=custom.log€ ConnectionStateTraUserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.phpSendReportJob.php© CrmHelperReposito© FilterJoinedParticip• DeleteCrmEntityTrait.phpDeleteAccountJob.php© ImportActivityTypes.phpWriteCrmTrait.php X© ActivityPlaybookTrait.phpT OpportunitySyncab© CrmHelperRepository.php© AccountController.phpT IntegrationAppTrait.php.env.staging.envv HubspotDetachActivityObject.phpC RematchActivityOnCrmObjectDetach.php© MatchActivityCrmData.php© Client.php18D AccountSyncStrate> DJ ActionsD ContactSyncStrate!› O DTO› O Fields› D JournalMetadata© HubspotPaginationService.phptrait WriteCrmTrait* @throws HubspotException* @throws BadRequest*/1 usageHandleHubspotRateLimit.php19525354A8 ж6^v OpportunitySyncSti55private function buildConferencePayload(Activity Sactivity): Activity> [ Concerns56© HubspotLastMor57$decorator = $this->getDecorateActivity();© HubspotLastMor58© HubspotLastMor59© HubspotLastMor60© HubspotLastMor61© HubspotSingleS:62© HubspotSyncStr63$engagement = l'active' => true,'ownerId' = $this->profile->getCrmProviderId®,'timestamp' => $decorator->formatStartTime(Sactivity),'type' => 'MEETING',© HubspotWebhoc64]:v D Pagination65© HubspotPaginati66© PaginationConfig67if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType))) {$engagement['activityType'] = $activity->getActivityType()->getNameO:© PaginationState.68> D ProspectSearchStra69› [ Redis70v D ServiceTraits71€ OpportunitySync72$metadata = ['meetingOutcome' => $decorator->convertMeetingOutcome(Sactivity),'internalMeetingNotes' => $decorator-›generateDescription($activity),© SyncCrmEntities73+ SyncFieldsTrait./74T WriteCrmTrait.pl75// Only if they are creating a fresh engagement should we update the times, otherwise it will cause a ‹232D Utils76if (! $activity->hasCrmProviderId)) {D Webhook77if (Sactivity->hasActualStartTime() && $activity->hasActualEndTime)) {D— 211212- =226227- 228229230231- 233 v234@ RatchSvneCnllertn78Smetadatal 'startTime'l = Sdecorator->formatStartTime(Sactivitv):Workspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)W Windsurf Teams=laravel.logSF [jiminny@localhost]A HS_local [jiminny@localho:W4 console [QAI PROD] X4 console [PROD]A console (EU]Go jiminny0841A3 X4 A Vm migrations oim teams wherem crm_layouts !iM crm_layout_eiIM crm_fields Wim features;m team_feature:m opportunitie:m teams;1.id, CASEWHENidFROMsocial,1 on u.id= sa.:1..n<->1: on t.L_id = 1052 andIMaccounts wheiactivities66:60UTF-8Co 4 spaces...
|
45178
|
NULL
|
NULL
|
NULL
|
|
45180
|
1622
|
14
|
2026-05-14T14:23:50.173150+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768630173_m2.jpg...
|
PhpStorm
|
faVsco.js – WriteCrmTrait.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.2962101,"top":1.0,"width":0.03856383,"height":-0.019952059},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#12066 on JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.33477393,"top":1.0,"width":0.122340426,"height":-0.019952059},"on_screen":true,"help_text":"Pull request #12066 exists for current branch JY-20725-handle-HS-search-rate-limit, 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.5731383,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HandleHubspotRateLimitTest","depth":6,"bounds":{"left":0.5884308,"top":1.0,"width":0.076130316,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.66456115,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.67586434,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.6871675,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.7150931,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button"}]...
|
-5784594327568142026
|
-7196678489907033719
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Jiminny... ~ActivityFilesLater@ jiminny-x-integrati• plattorm-inner-teamE Channels# ai-chapter# alertsi backend# bugscontusion-cllnia# curiosity lab# engineering# general#jiminny-bgac nlattorm-nckets# product launches# random# releases# sofia-officed sunport# thank-yous# the people of iimi... Direct messages• 8la 62% Galya Dimitrovavasil VasilevM Stefka StovanovaSg: Todor StamatovMario GeorgievNiudlay lanay. James Graham "* Stoyan Tanevo Stelivan Georgiev( Petko Kashinski*. Lukas Kovali...::: AnnsToastS lira Gloud6d Huddle with Aneliva Angelova* Aneliya Angelova &• Messagest Add canvasur FilesAneliya Angelova 2:30 PMЛукаш, само при Сейлсфорс и Хубспот се синкваха активити типовете, когато се направи плейбук,нали?пои останалите скМі тоябва оъчно ла се въвелат.Lukas Kovalik 2:47 PMтряова да се за всички, някъде не се ли попьлвапри зохо май беше hardcoded но май и там си връщаха две категорииAneliya Angelova 4:00 PMЛукаш имаш ли време да се чуем за тестването на https://jiminny.atlassian.net/browse/JY-20725X Preview insackt[HubSpotl Optimise CRM rematct(7 OpenRosdy for OA- MediumAA Aneliya AngelovaAs of today at 4:00 PM RetreshOpen in Jira* SummariseLukas Kovalik 4:02 PMzean-iurwiniareelYou joined the huddle (LIVE 4:06 PMAneliva Angelova is here toolAneliva Angelova 4:44 PN11512582Lukas Kovalik M 5.16 PNcurl --location "httns:ani.hubani.com/crm/v3/obiects/contacts/search--header 'Content-vne' annlication/ison'--header "Authorization: BearerC|Kvma?iMx7OINOM8kOFwrAowAcAkUAhIR24?A05D?xiVrIYMILuP6SA01Kwcmhr=n.v2 hytorDniMvDalYck-CMalovolNOM9kOFwrAUAcrkcaws-ThwRARIRAOF-ATFSAOFRAO-ROIEBAQUBEggBAQEBAYICFIr7Au8O2Nwal9YW2CbCCxYvwngmSgNldTFSAFoAYABoAHAAeAA'\--data "{ "limit", 13'Message Aneliva Angelova+ AaIđ 0Pmv Q SearchDate ModifiedYesterdav at 13.34Yesterday at 13:33Yesterday at 13.32Yesterdav at 13:32Yesterday at 13:31Yesterdav at 13:30Yesterday at 13:30Yesterday at 13.29Yesterdav at 13:28resterday at 15.21Yesterdav at 13:27Yesterday at 13:26Yesterday at 13:26Yesterday at 13:23Yesterdav at 13:25Yesterday at 13.24Yesterdav at 13:23Yesterday at 13:22Testerady dl ls.2.Yesterday at 13:21Voctordau at 12:20lYesterday at 13.19Yesterdav at 13:19Yesterdav at 13:18Yesterday at 13:18Yesterday at 13:17Yesterday at 13:17Yesterday at 13:16Yesterday at 13.16Yecterdav at 12:15Yesterdav at 13:15Yesterday at 13-14Yesterdav at 13:14Yesterday at 13:13Yesterday at 13:12Yecterdav at 12:12Yesterday at 13.11Yesterdav at 13:10Yesterday at 13:10Yesterday at 13:09Yoctorday at 12:09Yesterdav at 13:08Yesterday at 13.0Yecterdav at 12:07Yesterday at 13:06Yesterdav at 13:0529 K:MPEG-4 movie6 KB12 KBMDEG-A movieMPEG-4 movieMPEG-4 movie7 KBI8 KB37 KB10 K:MPEG-4 movie7 KBMDEG-A movie8 KEMPEG-4 movie8 KBI72 KB14 KB13 KEMPEG-4 movieMPEG-4 movie9 KBMDECA mavie18 KB12 KB|MPEG-4 movie10 KRI16 KB6 KBMPEG-1 movidMPEG-4 movie6KEMPEG-4 movie12 KBMPEG-4 movie23 KB8 K:MPEG-4 movie6 KB6 KB11 KBMDSG.A movicMPEG-4 movie11 KE20 KBMPEG-4 movieMPEG-4 movie34 KB10 K:MPEG-4 movie7 Kp5 KB11 KBMDSG.A movidMPEG-4 movie26KPMDEG-A movie111 KB MPEG-4 movie102 KB88 KB59 KB98 KB97 KBMPEG-4 movieMDEC.A moviaMPEG-4 movieRAKRMDEG-A movie44 KB93 KBMPEG-4 movie78 K:MPEG-A movie50 KBMPEG-4 movie58 KB27 KB7 KPMPEG-4 movieMDEG.A movio12 KB32 KBMPEG-4 movie17 Kг19 KBMDEC A movid32 KB10K8MPEG-4 movieAl Notes: OffLeave• • CFavourites• jiminny• Recents* ApplicationsiCloud• iCloud Drive992 Svnc tolde0 DXP4800PLUS-B5F49 Networl• CRMI• Orange• Red|• Yellow• Green• Purple•) All lags..IhlDownloadsNameLoom.pkgAlfred copv.alfredoreterences=KeychronAssist-1.0.2 (1).dmeKeychronAssist-1.0.2.dmgmazanoke-images-yWJo.zioPhotos-3-001.zip• Transcript.pd→mage U.loge1 Orioninstaller.dma_image.jpg- image (2).1pc• ПO-22221726037035-004-001_ORGES.pdf%D0%9F%D0%9E-22221726037035-004-001 archive.zipПO-22221726037035-004-001_archive (1).zip• repon 4).XmAltred copy2.altredoreterencesСE 060209С О000000026571172 CWICT 0Р70501260015900 ndt= 27022026_0000000026574472_ SWIFT_ [IBAN].pdf= 03042026 [CREDIT_CARD] SWIFT [CREDIT_CARD].001reportxmmi=pdt.odiB pdf-1.pdfD pdf-2.pdf-pdf-5.pd1= ndf-1 ndipdf-3.pdfP Kdf-a,k Ffmiv Tree.sebitwarden export 20251031122528.isonlKoválik Family Tree.zip*macOS Storage_Cleanup.mdal favicon icofirst aid_notes_complete.docxrenortl2).esviconfig.vmlIteration run Search HS.postman_collection.json--report(1).csvm licence hettertouchtoalMariusHosting Config.isonnokc.901a6502.6667.A62h-062 ccu•AlfredwPmazanoko.imnaoc.YWIfана Ковалик.jpg•искане даниел Ковалик..pg• Фактура Март Даниел Ковалик.jрс• Фактура Април Даниел Ковалик iро• Dhotac 2.001Q SearchKind00,4 MD55.9 MBinstdlle..dckageAlfred...ferences10,1 MB10,1 MBIL MBDisk ImageDisk ImageLiP archive6.6 MBZIp archive2,5 MB PDF Document2,5 MBJreo lmage2.2 MBDisk Image2,2 MB PDF Document2 MBJPEG image1,9 MBJPEG imaqe192 KBPDE Document140 KbZIP archive148 KВ148 KB122 KBZIP archivelZIP archiveXML document111 KBAlfred...ferences94 KBDDE Documont92 KBPDr DocumentK:PDF Document91 KB91 KBXML document30 KBn0kpDDE Nocumont28 KBPDF Document28 KRPDE Document28 KB27 KBDocument14 KB11 KB6 KB6KBJSONINCV NacumontZIP archiveMarkdo…..ument5KRWindo...n image4 KB3 KBword ..cumentCSV Document2 KBVAMI dosumon1 KB029 buteccSV Documenthttlicence183 bytesZero butesJSONAlfrod foronso!Zero bytesFolde1.9 MB1,8 MB17 MB1,7 MBColdorJPEG imageJPEG ImageIPEG imadeJPEG imageColdo1 of 58 selected, 9,62 GB availabld• Inu 14 Mау 1/•23.44Date AddedIs Mdl ZUzo dl 19:4530 Jan 2026 at 12:3617 Mar 2026 at 20:2717 Mar 2026 at 20:26Z3 Aor 2020 al 13:0229 ,Jan 2026 at 15:2019 Dec 2025 at 10:1619 Dec 2025 at 12:238 Aor 2026 at 20:3519 Dec 2025 at 10:2919 Dec 2025 at 12:1819 Dec 2025 at 12:4026 Mar 2026 at 11:2410 May 2026 at 13:5326 Mar 2026 at 11:2426 Mar 2026 at 11:2426 Mar 2026 at 11:2310 May 2026 at 14:3730 Jan 2026 at 12:3712 Goh 2006 9t 11:5 A1Z3 Apr 2026 at 13.0823 Aor 2026 at 13:0810 May 2026 at 13:5410 May 2026 at 14:3710 May 2026 at 13:4910 May 2026 at 13:5010 May 2026 at 13:5110 Mav 2026 at 12:51110 May 2026 at 13:5019 Dec 2025 at 11:3431 Oct 2025 at 12:2525 Nau 2025 4+ 17:506 Mar 2026 at 11.2224 Anr 2026 at 16:5220 Oct 2025 at 11:0218 Mar 2026 at 15:29• Mav 2026 at 11:09129 Oct 2025 at 19:329 May 2026 at 10:0419 Mar 2026 at 11:55|10 May 2026 at 13:5712 Jun 2025 at 19:0430 Jan 2026 at 12:362616 Oct 2025 aт 16:0122 Aar 2026 at 12:0223 Apr 2026 at 13:0223 Apr 2026 at 13:0222 Anr 2026 at 12:0223 Apr 2026 at 13:0229 Jan 2026 at 15:20...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45181
|
1621
|
26
|
2026-05-14T14:23:52.276580+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768632276_m1.jpg...
|
PhpStorm
|
faVsco.js – WriteCrmTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorRun PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelpabl100% C•FV faVsco.jsv#12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTest8• Thu 14 May 17:23:52* :QProject v© ReportController.php© AutomatedReportGenerated.phpTrackAutomatedReportGeneratedEvent.phpPlaybackController.php:=custom.log€ ConnectionStateTraUserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.phpSendReportJob.php© CrmHelperReposito© FilterJoinedParticip• DeleteCrmEntityTrait.phpDeleteAccountJob.php© ImportActivityTypes.phpWriteCrmTrait.php X© ActivityPlaybookTrait.phpT OpportunitySyncab© CrmHelperRepository.php© AccountController.phpT IntegrationAppTrait.php.env.staging.envv HubspotDetachActivityObject.phpC RematchActivityOnCrmObjectDetach.php© MatchActivityCrmData.php© Client.php18D AccountSyncStrate› DJ ActionsD ContactSyncStrate!› O DTO› O Fields› D JournalMetadata© HubspotPaginationService.phptrait WriteCrmTrait* @throws HubspotException* @throws BadRequest*/1 usageHandleHubspotRateLimit.php19525354A8 ж6^v OpportunitySyncSti55private function buildConferencePayload(Activity Sactivity): Activity> [ Concerns56© HubspotLastMor57$decorator = $this->getDecorateActivity();© HubspotLastMor58© HubspotLastMor59© HubspotLastMor60© HubspotLastMor61© HubspotSingleS:62© HubspotSyncStr63$engagement= ['active' => true,'ownerId' = $this->profile->getCrmProviderId®,'timestamp' => $decorator->formatStartTime(Sactivity),'type' => 'MEETING',© HubspotWebhoc64]:v D Pagination65© HubspotPaginati66© PaginationConfig67if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType))) {Sengagement['activityType'] = $activity->getActivityType()->getName();© PaginationState.68> D ProspectSearchStra69› [ Redis70v D ServiceTraits71€ OpportunitySync72$metadata = ['meetingOutcome' => $decorator->convertMeetingOutcome(Sactivity),'internalMeetingNotes' => $decorator-›generateDescription($activity),© SyncCrmEntities73+ SyncFieldsTrait./74T WriteCrmTrait.pl75// Only if they are creating a fresh engagement should we update the times, otherwise it will cause a ‹232D Utils76if (! $activity->hasCrmProviderId)) {D Webhook77if (Sactivity->hasActualStartTime() && $activity->hasActualEndTime)) {D— 211212-213214215216217218219-220221222223224225=226227-228229230231— 233 v234@ RatchSvneCnllertn78Smetadatal 'startTime'l = Sdecorator->formatStartTime(Sactivitv):Workspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)W Windsurf Teams=laravel.logSF [jiminny@localhost]A HS_local [jiminny@localho:W4 console [QAI PROD] X4 console [PROD]A console (EU]Go jiminny0841A3 X4 A Vm migrations oim teams wherem crm_layouts !iM crm_layout_eiIM crm_fields Wim features;m team_feature:m opportunitie:m teams;1.id, CASEWHENidFROMsocial,1 on u.id= sa.:1..n<->1: on t.L_id = 1052 andIMaccounts wheiactivities67:13UTF-8Co 4 spaces...
|
NULL
|
2572463782370577634
|
NULL
|
click
|
ocr
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorRun PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelpabl100% C•FV faVsco.jsv#12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTest8• Thu 14 May 17:23:52* :QProject v© ReportController.php© AutomatedReportGenerated.phpTrackAutomatedReportGeneratedEvent.phpPlaybackController.php:=custom.log€ ConnectionStateTraUserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.phpSendReportJob.php© CrmHelperReposito© FilterJoinedParticip• DeleteCrmEntityTrait.phpDeleteAccountJob.php© ImportActivityTypes.phpWriteCrmTrait.php X© ActivityPlaybookTrait.phpT OpportunitySyncab© CrmHelperRepository.php© AccountController.phpT IntegrationAppTrait.php.env.staging.envv HubspotDetachActivityObject.phpC RematchActivityOnCrmObjectDetach.php© MatchActivityCrmData.php© Client.php18D AccountSyncStrate› DJ ActionsD ContactSyncStrate!› O DTO› O Fields› D JournalMetadata© HubspotPaginationService.phptrait WriteCrmTrait* @throws HubspotException* @throws BadRequest*/1 usageHandleHubspotRateLimit.php19525354A8 ж6^v OpportunitySyncSti55private function buildConferencePayload(Activity Sactivity): Activity> [ Concerns56© HubspotLastMor57$decorator = $this->getDecorateActivity();© HubspotLastMor58© HubspotLastMor59© HubspotLastMor60© HubspotLastMor61© HubspotSingleS:62© HubspotSyncStr63$engagement= ['active' => true,'ownerId' = $this->profile->getCrmProviderId®,'timestamp' => $decorator->formatStartTime(Sactivity),'type' => 'MEETING',© HubspotWebhoc64]:v D Pagination65© HubspotPaginati66© PaginationConfig67if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType))) {Sengagement['activityType'] = $activity->getActivityType()->getName();© PaginationState.68> D ProspectSearchStra69› [ Redis70v D ServiceTraits71€ OpportunitySync72$metadata = ['meetingOutcome' => $decorator->convertMeetingOutcome(Sactivity),'internalMeetingNotes' => $decorator-›generateDescription($activity),© SyncCrmEntities73+ SyncFieldsTrait./74T WriteCrmTrait.pl75// Only if they are creating a fresh engagement should we update the times, otherwise it will cause a ‹232D Utils76if (! $activity->hasCrmProviderId)) {D Webhook77if (Sactivity->hasActualStartTime() && $activity->hasActualEndTime)) {D— 211212-213214215216217218219-220221222223224225=226227-228229230231— 233 v234@ RatchSvneCnllertn78Smetadatal 'startTime'l = Sdecorator->formatStartTime(Sactivitv):Workspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)W Windsurf Teams=laravel.logSF [jiminny@localhost]A HS_local [jiminny@localho:W4 console [QAI PROD] X4 console [PROD]A console (EU]Go jiminny0841A3 X4 A Vm migrations oim teams wherem crm_layouts !iM crm_layout_eiIM crm_fields Wim features;m team_feature:m opportunitie:m teams;1.id, CASEWHENidFROMsocial,1 on u.id= sa.:1..n<->1: on t.L_id = 1052 andIMaccounts wheiactivities67:13UTF-8Co 4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45182
|
1622
|
15
|
2026-05-14T14:23:54.568655+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768634568_m2.jpg...
|
PhpStorm
|
faVsco.js – WriteCrmTrait.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
8
6
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldDataRepository;
use Jiminny\Services\Crm\Hubspot\DecorateActivity;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationCategoryV4;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationTypeIdV4;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Http\Response;
use SevenShores\Hubspot\Exceptions\HubspotException;
use Sentry;
trait WriteCrmTrait
{
private const string ACTION_CREATE = 'create';
private const string ACTION_UPDATE = 'update';
private const string ACTION_DELETE = 'delete';
/**
* @inheritdoc
*/
public function saveActivity(Activity $activity): Activity
{
switch ($activity->getCrmType()) {
case Activity::TYPE_CONFERENCE:
$activity = $this->buildConferencePayload($activity);
break;
case Activity::TYPE_SOFTPHONE:
case Activity::TYPE_SOFTPHONE_INBOUND:
$activity = $this->buildSoftphonePayload($activity);
break;
case Activity::TYPE_SMS_INBOUND:
case Activity::TYPE_SMS_OUTBOUND:
$activity = $this->buildTextMessagePayload($activity);
break;
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildConferencePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatStartTime($activity),
'type' => 'MEETING',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'meetingOutcome' => $decorator->convertMeetingOutcome($activity),
'internalMeetingNotes' => $decorator->generateDescription($activity),
];
// Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.
if (! $activity->hasCrmProviderId()) {
if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {
$metadata['startTime'] = $decorator->formatStartTime($activity);
$metadata['endTime'] = $decorator->formatEndTime($activity);
}
$metadata['title'] = $decorator->generateTitle($activity);
}
// For now, this is just a "meetingOutcome" field for some customers.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function fetchCustomFieldData(Activity $activity, string $objectType): array
{
$payload = [];
$fieldDataRepository = app(FieldDataRepository::class);
$fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);
foreach ($fieldData as $data) {
// Add the field and value to the payload.
$payload += [
$data->getField()->getCrmProviderId() => $data->getValue(),
];
}
return $payload;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity
{
if (! $activity->hasCrmProviderId()) {
$this->createActivity($activity, $engagement, $metadata);
return $activity;
}
try {
$oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());
} catch (HubspotException $e) {
$this->logger->warning('[HubSpot] Engagement not found', [
'activity_id' => $activity->getUuid(),
'crm_provider_id' => $activity->getCrmProviderId(),
'exception' => $e->getMessage(),
]);
// potentially here we can log new activity if it is not found
return $activity;
}
$type = $oldEngagement['engagement']['type'] ?? null;
$bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';
$decorator = $this->getDecorateActivity();
if ($type !== 'TASK') {
$oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldDescription
);
$oldAssociations = $oldEngagement['associations'] ?? [];
$this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);
} else {
$oldTaskBody = $oldEngagement['metadata']['body'] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldTaskBody
);
$this->updateTask($activity, $engagement, $metadata);
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateNonTaskActivity(
Activity $activity,
array $engagement,
array $metadata,
array $oldAssociations
): void {
if ($activity->isTypeConference()) {
$this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);
} else {
$this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);
}
$this->updateActivityAssociations($activity, $oldAssociations);
$this->logCrmEngagementManipulation(
self::ACTION_UPDATE,
['crmId' => $activity->getCrmProviderId()],
$metadata,
);
}
private function updateActivityAssociations(Activity $activity, array $oldAssociations): void
{
$currentAssociations = $this->getActivityAssociations($activity);
$associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);
// No changes - exit
if (empty($associationChanges)) {
return;
}
$this->logger->info('[HubSpot] Updating associations', [
'team' => $this->team->getUuid(),
'activity' => $activity->getUuid(),
'associationChanges' => $associationChanges,
]);
$this->updateAssociations($activity, $associationChanges);
}
private function getAssociationDifferences(
Activity $activity,
array $oldAssociations,
array $currentAssociations
): array {
$objects = ['contactIds', 'companyIds', 'dealIds'];
$changes = [];
foreach ($objects as $object) {
$oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);
$currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);
$added = array_diff($currentIds, $oldIds);
$removed = array_diff($oldIds, $currentIds);
$type = match ($object) {
'contactIds' => 'contact',
'companyIds' => 'company',
'dealIds' => 'deal',
};
$shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);
if (! empty($removed) && ! $shouldIncludeRemoved) {
$this->logger->info('[HubSpot] Skipping association removal', [
'team' => $this->team->getUuid(),
'type' => $type,
'oldIds' => $oldIds,
'currentIds' => $currentIds,
'skippedRemovals' => array_values($removed),
]);
}
if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {
$changes[$type] = [
'added' => array_values($added),
'removed' => array_values($removed),
];
}
}
return $changes;
}
/**
* Determines whether removed associations should be synced to HubSpot.
*
* For contacts and companies, we always sync removals (original behavior).
* For deals, we only sync removals when:
* - There are new deals being added (replacement scenario), OR
* - The activity has an account associated (indicating intentional association management)
*
* This prevents accidentally removing deal associations that exist in HubSpot
* but not in Jiminny's database (e.g., deals manually added by users in HubSpot).
*/
private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool
{
// Contacts and companies: always sync removals
if ($type !== 'deal') {
return true;
}
// Deals: sync removals if we're adding new deals (replacement)
if (! empty($added)) {
return true;
}
// Deals: sync removals only if activity has an account (intentional management)
return $activity->hasAccount();
}
private function normalizeIds(array $ids): array
{
$stringIds = array_map('strval', $ids);
$uniqueIds = array_unique($stringIds);
sort($uniqueIds);
return $uniqueIds;
}
private function updateAssociations(Activity $activity, array $associationChanges): void
{
$objectType = $activity->isTypeConference() ? 'meeting' : 'call';
foreach ($associationChanges as $type => $changes) {
if (! empty($changes['added'])) {
$associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);
$payload = $this->payloadBuilder->buildAddAssociationPayload(
$activity->getCrmProviderId(),
$changes['added'],
$associationType
);
$this->getClient()->addAssociations($objectType, $type, $payload);
}
if (! empty($changes['removed'])) {
$payload = $this->payloadBuilder->buildRemoveAssociationPayload(
$activity->getCrmProviderId(),
$changes['removed']
);
$this->getClient()->removeAssociations($objectType, $type, $payload);
}
}
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response
{
// If already set by us/client, do not override it
if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {
$this->logger->info('[HubSpot] Meeting activity type already set', [
'meetingId' => $meetingId,
'activityType' => $engagement['activityType'],
]);
unset($engagement['activityType']);
}
$payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);
return $this->getClient()->updateMeeting($meetingId, $payload);
}
private function hasEngagementActivityType(string $engagementId): bool
{
try {
$engagementData = $this->getClient()->getEngagementData($engagementId);
if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {
return true;
}
return false;
} catch (\Exception $exception) {
Sentry::captureException($exception);
$this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [
'engagementId' => $engagementId,
'exception' => $exception->getMessage(),
]);
}
return true;
}
private function updateTask(Activity $activity, array $engagement, array $metadata): void
{
$this->getClient()->deleteEngagement($activity->getCrmProviderId());
$this->logCrmEngagementManipulation(
self::ACTION_DELETE,
['crmId' => $activity->getCrmProviderId()],
);
$this->createActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createActivity(Activity $activity, array $engagement, array $metadata): void
{
$associations = $this->getActivityAssociations($activity);
$associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);
if ($activity->isTypeConference()) {
try {
$hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);
$activityId = $hsEngagement->data->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
} else {
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
$activityId = $hsEngagement->data->engagement->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
}
}
private function convertActivityMeetingAssociationsV4(Activity $activity): array
{
$associations = [];
$contactIds = $this->getParticipantsIds($activity);
foreach ($contactIds as $contactId) {
$associations[] = $this->buildAssociation(
(int) $contactId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]
);
}
if ($activity->hasAccount()) {
$crmProviderId = $activity->getAccount()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]
);
}
}
if ($activity->hasOpportunity()) {
$crmProviderId = $activity->getOpportunity()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]
);
}
}
return $associations;
}
private function buildAssociation(int $id, array $categoryToTypeIds): array
{
$types = [];
foreach ($categoryToTypeIds as $category => $typeId) {
$types[] = $this->buildAssociationType($category, $typeId);
}
return [
'to' => ['id' => (string) $id],
'types' => $types,
];
}
private function buildAssociationType(string $category, int $typeId): array
{
return [
'associationCategory' => $category,
'associationTypeId' => $typeId,
];
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createMeetingV3(array $engagement, array $associations, array $metadata): Response
{
$payload = $this->payloadBuilder->getV3MeetingPayload(
engagement: $engagement,
metadata: $metadata,
associations: $associations
);
return $this->getClient()->createMeeting($payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
private function buildSoftphonePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'CALL',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'toNumber' => $decorator->formatCallToParticipant($activity),
'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),
'status' => $decorator->convertCallStatus($activity),
'durationMilliseconds' => (int) $activity->getDuration() * 1000,
'body' => $decorator->generateDescription($activity),
];
// For now, this is just a "disposition" field to store Call Outcome.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildTextMessagePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'NOTE',
];
$metadata = [
'body' => $decorator->generateDescription($activity),
];
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function getActivityAssociations(Activity $activity): array
{
return [
'contactIds' => $this->getParticipantsIds($activity),
'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],
'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],
'ownerIds' => [],
];
}
/**
* Get DecorateActivity instance. Can be overridden in tests for mocking.
*/
protected function getDecorateActivity(): DecorateActivity
{
return new DecorateActivity();
}
public function saveFollowupActivity(Activity $activity, array $fields): ?string
{
if (empty($fields['subject'])) {
return null;
}
$associations = $this->getActivityAssociations($activity);
if (empty($fields['timestamp']) === false) {
$formatTimestamp = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['timestamp']);
$timestamp = $formatTimestamp->getTimestamp();
} else {
$timestamp = strtotime('+1 day');
}
$reminder = null;
if (empty($fields['reminder']) === false) {
$formatReminder = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['reminder']);
$reminder = $formatReminder->getTimestamp() * 1000;
}
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $timestamp * 1000,
'type' => 'TASK',
];
$metadata = [
'subject' => $fields['subject'],
'body' => '',
'status' => 'NOT_STARTED',
'taskType' => 'CALL',
'reminders' => $reminder ? [$reminder] : [],
];
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
} catch (BadRequest $e) {
$this->logger->info('[HubSpot] Failed to create followup task', [
'activity' => $activity->getUuid(),
'engagement' => $engagement,
'metadata' => $metadata,
'error' => $e->getMessage(),
]);
return null;
}
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
// We don't actually create a corresponding activity object on our side yet.
return (string) $hsEngagement->data->engagement->id;
}
private function logCrmEngagementManipulation(
string $action,
array $engagementData,
array $data = [],
array $associations = [],
): void {
$payload = ['team' => $this->team->getUuid()];
if ($action === self::ACTION_CREATE) {
$payload['engagementData'] = $engagementData;
$payload['data'] = $data;
$payload['associations'] = $associations;
}
if ($action === self::ACTION_UPDATE) {
$payload['engagementId'] = $engagementData['crmId'];
$payload['data'] = $data;
}
if ($action === self::ACTION_DELETE) {
$payload['engagementId'] = $engagementData['crmId'];
}
$this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
8
1
3...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.2962101,"top":1.0,"width":0.03856383,"height":-0.019952059},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#12066 on JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.33477393,"top":1.0,"width":0.122340426,"height":-0.019952059},"on_screen":true,"help_text":"Pull request #12066 exists for current branch JY-20725-handle-HS-search-rate-limit, 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.5731383,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HandleHubspotRateLimitTest","depth":6,"bounds":{"left":0.5884308,"top":1.0,"width":0.076130316,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.66456115,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.67586434,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.6871675,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.7150931,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.72639626,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.73769945,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"8","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldDataRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DecorateActivity;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationCategoryV4;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationTypeIdV4;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse Sentry;\n\ntrait WriteCrmTrait\n{\n private const string ACTION_CREATE = 'create';\n private const string ACTION_UPDATE = 'update';\n private const string ACTION_DELETE = 'delete';\n\n /**\n * @inheritdoc\n */\n public function saveActivity(Activity $activity): Activity\n {\n switch ($activity->getCrmType()) {\n case Activity::TYPE_CONFERENCE:\n $activity = $this->buildConferencePayload($activity);\n\n break;\n case Activity::TYPE_SOFTPHONE:\n case Activity::TYPE_SOFTPHONE_INBOUND:\n $activity = $this->buildSoftphonePayload($activity);\n\n break;\n\n case Activity::TYPE_SMS_INBOUND:\n case Activity::TYPE_SMS_OUTBOUND:\n $activity = $this->buildTextMessagePayload($activity);\n\n break;\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildConferencePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatStartTime($activity),\n 'type' => 'MEETING',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'meetingOutcome' => $decorator->convertMeetingOutcome($activity),\n 'internalMeetingNotes' => $decorator->generateDescription($activity),\n ];\n\n // Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.\n if (! $activity->hasCrmProviderId()) {\n if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {\n $metadata['startTime'] = $decorator->formatStartTime($activity);\n $metadata['endTime'] = $decorator->formatEndTime($activity);\n }\n $metadata['title'] = $decorator->generateTitle($activity);\n }\n\n // For now, this is just a \"meetingOutcome\" field for some customers.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function fetchCustomFieldData(Activity $activity, string $objectType): array\n {\n $payload = [];\n\n $fieldDataRepository = app(FieldDataRepository::class);\n $fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);\n\n foreach ($fieldData as $data) {\n // Add the field and value to the payload.\n $payload += [\n $data->getField()->getCrmProviderId() => $data->getValue(),\n ];\n }\n\n return $payload;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity\n {\n if (! $activity->hasCrmProviderId()) {\n $this->createActivity($activity, $engagement, $metadata);\n\n return $activity;\n }\n\n try {\n $oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());\n } catch (HubspotException $e) {\n $this->logger->warning('[HubSpot] Engagement not found', [\n 'activity_id' => $activity->getUuid(),\n 'crm_provider_id' => $activity->getCrmProviderId(),\n 'exception' => $e->getMessage(),\n ]);\n\n // potentially here we can log new activity if it is not found\n\n return $activity;\n }\n\n $type = $oldEngagement['engagement']['type'] ?? null;\n $bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';\n\n $decorator = $this->getDecorateActivity();\n\n if ($type !== 'TASK') {\n $oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldDescription\n );\n\n $oldAssociations = $oldEngagement['associations'] ?? [];\n $this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);\n } else {\n $oldTaskBody = $oldEngagement['metadata']['body'] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldTaskBody\n );\n $this->updateTask($activity, $engagement, $metadata);\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateNonTaskActivity(\n Activity $activity,\n array $engagement,\n array $metadata,\n array $oldAssociations\n ): void {\n if ($activity->isTypeConference()) {\n $this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);\n } else {\n $this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);\n }\n\n $this->updateActivityAssociations($activity, $oldAssociations);\n\n $this->logCrmEngagementManipulation(\n self::ACTION_UPDATE,\n ['crmId' => $activity->getCrmProviderId()],\n $metadata,\n );\n }\n\n private function updateActivityAssociations(Activity $activity, array $oldAssociations): void\n {\n $currentAssociations = $this->getActivityAssociations($activity);\n $associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);\n\n // No changes - exit\n if (empty($associationChanges)) {\n return;\n }\n\n $this->logger->info('[HubSpot] Updating associations', [\n 'team' => $this->team->getUuid(),\n 'activity' => $activity->getUuid(),\n 'associationChanges' => $associationChanges,\n ]);\n\n $this->updateAssociations($activity, $associationChanges);\n }\n\n private function getAssociationDifferences(\n Activity $activity,\n array $oldAssociations,\n array $currentAssociations\n ): array {\n $objects = ['contactIds', 'companyIds', 'dealIds'];\n $changes = [];\n\n foreach ($objects as $object) {\n $oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);\n $currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);\n\n $added = array_diff($currentIds, $oldIds);\n $removed = array_diff($oldIds, $currentIds);\n\n $type = match ($object) {\n 'contactIds' => 'contact',\n 'companyIds' => 'company',\n 'dealIds' => 'deal',\n };\n\n $shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);\n\n if (! empty($removed) && ! $shouldIncludeRemoved) {\n $this->logger->info('[HubSpot] Skipping association removal', [\n 'team' => $this->team->getUuid(),\n 'type' => $type,\n 'oldIds' => $oldIds,\n 'currentIds' => $currentIds,\n 'skippedRemovals' => array_values($removed),\n ]);\n }\n\n if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {\n $changes[$type] = [\n 'added' => array_values($added),\n 'removed' => array_values($removed),\n ];\n }\n }\n\n return $changes;\n }\n\n /**\n * Determines whether removed associations should be synced to HubSpot.\n *\n * For contacts and companies, we always sync removals (original behavior).\n * For deals, we only sync removals when:\n * - There are new deals being added (replacement scenario), OR\n * - The activity has an account associated (indicating intentional association management)\n *\n * This prevents accidentally removing deal associations that exist in HubSpot\n * but not in Jiminny's database (e.g., deals manually added by users in HubSpot).\n */\n private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool\n {\n // Contacts and companies: always sync removals\n if ($type !== 'deal') {\n return true;\n }\n\n // Deals: sync removals if we're adding new deals (replacement)\n if (! empty($added)) {\n return true;\n }\n\n // Deals: sync removals only if activity has an account (intentional management)\n return $activity->hasAccount();\n }\n\n private function normalizeIds(array $ids): array\n {\n $stringIds = array_map('strval', $ids);\n $uniqueIds = array_unique($stringIds);\n sort($uniqueIds);\n\n return $uniqueIds;\n }\n\n private function updateAssociations(Activity $activity, array $associationChanges): void\n {\n $objectType = $activity->isTypeConference() ? 'meeting' : 'call';\n\n foreach ($associationChanges as $type => $changes) {\n if (! empty($changes['added'])) {\n $associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);\n $payload = $this->payloadBuilder->buildAddAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['added'],\n $associationType\n );\n $this->getClient()->addAssociations($objectType, $type, $payload);\n }\n\n if (! empty($changes['removed'])) {\n $payload = $this->payloadBuilder->buildRemoveAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['removed']\n );\n $this->getClient()->removeAssociations($objectType, $type, $payload);\n }\n }\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response\n {\n // If already set by us/client, do not override it\n if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {\n $this->logger->info('[HubSpot] Meeting activity type already set', [\n 'meetingId' => $meetingId,\n 'activityType' => $engagement['activityType'],\n ]);\n\n unset($engagement['activityType']);\n }\n\n $payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);\n\n return $this->getClient()->updateMeeting($meetingId, $payload);\n }\n\n private function hasEngagementActivityType(string $engagementId): bool\n {\n try {\n $engagementData = $this->getClient()->getEngagementData($engagementId);\n if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {\n return true;\n }\n\n return false;\n } catch (\\Exception $exception) {\n Sentry::captureException($exception);\n\n $this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [\n 'engagementId' => $engagementId,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n return true;\n }\n\n private function updateTask(Activity $activity, array $engagement, array $metadata): void\n {\n $this->getClient()->deleteEngagement($activity->getCrmProviderId());\n\n $this->logCrmEngagementManipulation(\n self::ACTION_DELETE,\n ['crmId' => $activity->getCrmProviderId()],\n );\n\n $this->createActivity($activity, $engagement, $metadata);\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createActivity(Activity $activity, array $engagement, array $metadata): void\n {\n $associations = $this->getActivityAssociations($activity);\n $associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);\n\n if ($activity->isTypeConference()) {\n try {\n $hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);\n $activityId = $hsEngagement->data->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n } else {\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n $activityId = $hsEngagement->data->engagement->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n }\n }\n\n private function convertActivityMeetingAssociationsV4(Activity $activity): array\n {\n $associations = [];\n\n $contactIds = $this->getParticipantsIds($activity);\n\n foreach ($contactIds as $contactId) {\n $associations[] = $this->buildAssociation(\n (int) $contactId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]\n );\n }\n\n if ($activity->hasAccount()) {\n $crmProviderId = $activity->getAccount()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]\n );\n }\n }\n\n if ($activity->hasOpportunity()) {\n $crmProviderId = $activity->getOpportunity()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]\n );\n }\n }\n\n return $associations;\n }\n\n private function buildAssociation(int $id, array $categoryToTypeIds): array\n {\n $types = [];\n\n foreach ($categoryToTypeIds as $category => $typeId) {\n $types[] = $this->buildAssociationType($category, $typeId);\n }\n\n return [\n 'to' => ['id' => (string) $id],\n 'types' => $types,\n ];\n }\n\n private function buildAssociationType(string $category, int $typeId): array\n {\n return [\n 'associationCategory' => $category,\n 'associationTypeId' => $typeId,\n ];\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createMeetingV3(array $engagement, array $associations, array $metadata): Response\n {\n $payload = $this->payloadBuilder->getV3MeetingPayload(\n engagement: $engagement,\n metadata: $metadata,\n associations: $associations\n );\n\n return $this->getClient()->createMeeting($payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n private function buildSoftphonePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'CALL',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'toNumber' => $decorator->formatCallToParticipant($activity),\n 'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),\n 'status' => $decorator->convertCallStatus($activity),\n 'durationMilliseconds' => (int) $activity->getDuration() * 1000,\n 'body' => $decorator->generateDescription($activity),\n ];\n\n // For now, this is just a \"disposition\" field to store Call Outcome.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildTextMessagePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'NOTE',\n ];\n\n $metadata = [\n 'body' => $decorator->generateDescription($activity),\n ];\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function getActivityAssociations(Activity $activity): array\n {\n return [\n 'contactIds' => $this->getParticipantsIds($activity),\n 'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],\n 'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],\n 'ownerIds' => [],\n ];\n }\n\n /**\n * Get DecorateActivity instance. Can be overridden in tests for mocking.\n */\n protected function getDecorateActivity(): DecorateActivity\n {\n return new DecorateActivity();\n }\n\n public function saveFollowupActivity(Activity $activity, array $fields): ?string\n {\n if (empty($fields['subject'])) {\n return null;\n }\n\n $associations = $this->getActivityAssociations($activity);\n\n if (empty($fields['timestamp']) === false) {\n $formatTimestamp = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['timestamp']);\n $timestamp = $formatTimestamp->getTimestamp();\n } else {\n $timestamp = strtotime('+1 day');\n }\n\n $reminder = null;\n if (empty($fields['reminder']) === false) {\n $formatReminder = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['reminder']);\n $reminder = $formatReminder->getTimestamp() * 1000;\n }\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $timestamp * 1000,\n 'type' => 'TASK',\n ];\n\n $metadata = [\n 'subject' => $fields['subject'],\n 'body' => '',\n 'status' => 'NOT_STARTED',\n 'taskType' => 'CALL',\n 'reminders' => $reminder ? [$reminder] : [],\n ];\n\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n } catch (BadRequest $e) {\n $this->logger->info('[HubSpot] Failed to create followup task', [\n 'activity' => $activity->getUuid(),\n 'engagement' => $engagement,\n 'metadata' => $metadata,\n 'error' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n // We don't actually create a corresponding activity object on our side yet.\n return (string) $hsEngagement->data->engagement->id;\n }\n\n private function logCrmEngagementManipulation(\n string $action,\n array $engagementData,\n array $data = [],\n array $associations = [],\n ): void {\n $payload = ['team' => $this->team->getUuid()];\n\n if ($action === self::ACTION_CREATE) {\n $payload['engagementData'] = $engagementData;\n $payload['data'] = $data;\n $payload['associations'] = $associations;\n }\n\n if ($action === self::ACTION_UPDATE) {\n $payload['engagementId'] = $engagementData['crmId'];\n $payload['data'] = $data;\n }\n\n if ($action === self::ACTION_DELETE) {\n $payload['engagementId'] = $engagementData['crmId'];\n }\n\n $this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);\n }\n}","depth":4,"bounds":{"left":0.39195478,"top":0.3735036,"width":0.31615692,"height":0.62649643},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldDataRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DecorateActivity;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationCategoryV4;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationTypeIdV4;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse Sentry;\n\ntrait WriteCrmTrait\n{\n private const string ACTION_CREATE = 'create';\n private const string ACTION_UPDATE = 'update';\n private const string ACTION_DELETE = 'delete';\n\n /**\n * @inheritdoc\n */\n public function saveActivity(Activity $activity): Activity\n {\n switch ($activity->getCrmType()) {\n case Activity::TYPE_CONFERENCE:\n $activity = $this->buildConferencePayload($activity);\n\n break;\n case Activity::TYPE_SOFTPHONE:\n case Activity::TYPE_SOFTPHONE_INBOUND:\n $activity = $this->buildSoftphonePayload($activity);\n\n break;\n\n case Activity::TYPE_SMS_INBOUND:\n case Activity::TYPE_SMS_OUTBOUND:\n $activity = $this->buildTextMessagePayload($activity);\n\n break;\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildConferencePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatStartTime($activity),\n 'type' => 'MEETING',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'meetingOutcome' => $decorator->convertMeetingOutcome($activity),\n 'internalMeetingNotes' => $decorator->generateDescription($activity),\n ];\n\n // Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.\n if (! $activity->hasCrmProviderId()) {\n if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {\n $metadata['startTime'] = $decorator->formatStartTime($activity);\n $metadata['endTime'] = $decorator->formatEndTime($activity);\n }\n $metadata['title'] = $decorator->generateTitle($activity);\n }\n\n // For now, this is just a \"meetingOutcome\" field for some customers.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function fetchCustomFieldData(Activity $activity, string $objectType): array\n {\n $payload = [];\n\n $fieldDataRepository = app(FieldDataRepository::class);\n $fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);\n\n foreach ($fieldData as $data) {\n // Add the field and value to the payload.\n $payload += [\n $data->getField()->getCrmProviderId() => $data->getValue(),\n ];\n }\n\n return $payload;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity\n {\n if (! $activity->hasCrmProviderId()) {\n $this->createActivity($activity, $engagement, $metadata);\n\n return $activity;\n }\n\n try {\n $oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());\n } catch (HubspotException $e) {\n $this->logger->warning('[HubSpot] Engagement not found', [\n 'activity_id' => $activity->getUuid(),\n 'crm_provider_id' => $activity->getCrmProviderId(),\n 'exception' => $e->getMessage(),\n ]);\n\n // potentially here we can log new activity if it is not found\n\n return $activity;\n }\n\n $type = $oldEngagement['engagement']['type'] ?? null;\n $bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';\n\n $decorator = $this->getDecorateActivity();\n\n if ($type !== 'TASK') {\n $oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldDescription\n );\n\n $oldAssociations = $oldEngagement['associations'] ?? [];\n $this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);\n } else {\n $oldTaskBody = $oldEngagement['metadata']['body'] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldTaskBody\n );\n $this->updateTask($activity, $engagement, $metadata);\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateNonTaskActivity(\n Activity $activity,\n array $engagement,\n array $metadata,\n array $oldAssociations\n ): void {\n if ($activity->isTypeConference()) {\n $this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);\n } else {\n $this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);\n }\n\n $this->updateActivityAssociations($activity, $oldAssociations);\n\n $this->logCrmEngagementManipulation(\n self::ACTION_UPDATE,\n ['crmId' => $activity->getCrmProviderId()],\n $metadata,\n );\n }\n\n private function updateActivityAssociations(Activity $activity, array $oldAssociations): void\n {\n $currentAssociations = $this->getActivityAssociations($activity);\n $associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);\n\n // No changes - exit\n if (empty($associationChanges)) {\n return;\n }\n\n $this->logger->info('[HubSpot] Updating associations', [\n 'team' => $this->team->getUuid(),\n 'activity' => $activity->getUuid(),\n 'associationChanges' => $associationChanges,\n ]);\n\n $this->updateAssociations($activity, $associationChanges);\n }\n\n private function getAssociationDifferences(\n Activity $activity,\n array $oldAssociations,\n array $currentAssociations\n ): array {\n $objects = ['contactIds', 'companyIds', 'dealIds'];\n $changes = [];\n\n foreach ($objects as $object) {\n $oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);\n $currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);\n\n $added = array_diff($currentIds, $oldIds);\n $removed = array_diff($oldIds, $currentIds);\n\n $type = match ($object) {\n 'contactIds' => 'contact',\n 'companyIds' => 'company',\n 'dealIds' => 'deal',\n };\n\n $shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);\n\n if (! empty($removed) && ! $shouldIncludeRemoved) {\n $this->logger->info('[HubSpot] Skipping association removal', [\n 'team' => $this->team->getUuid(),\n 'type' => $type,\n 'oldIds' => $oldIds,\n 'currentIds' => $currentIds,\n 'skippedRemovals' => array_values($removed),\n ]);\n }\n\n if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {\n $changes[$type] = [\n 'added' => array_values($added),\n 'removed' => array_values($removed),\n ];\n }\n }\n\n return $changes;\n }\n\n /**\n * Determines whether removed associations should be synced to HubSpot.\n *\n * For contacts and companies, we always sync removals (original behavior).\n * For deals, we only sync removals when:\n * - There are new deals being added (replacement scenario), OR\n * - The activity has an account associated (indicating intentional association management)\n *\n * This prevents accidentally removing deal associations that exist in HubSpot\n * but not in Jiminny's database (e.g., deals manually added by users in HubSpot).\n */\n private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool\n {\n // Contacts and companies: always sync removals\n if ($type !== 'deal') {\n return true;\n }\n\n // Deals: sync removals if we're adding new deals (replacement)\n if (! empty($added)) {\n return true;\n }\n\n // Deals: sync removals only if activity has an account (intentional management)\n return $activity->hasAccount();\n }\n\n private function normalizeIds(array $ids): array\n {\n $stringIds = array_map('strval', $ids);\n $uniqueIds = array_unique($stringIds);\n sort($uniqueIds);\n\n return $uniqueIds;\n }\n\n private function updateAssociations(Activity $activity, array $associationChanges): void\n {\n $objectType = $activity->isTypeConference() ? 'meeting' : 'call';\n\n foreach ($associationChanges as $type => $changes) {\n if (! empty($changes['added'])) {\n $associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);\n $payload = $this->payloadBuilder->buildAddAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['added'],\n $associationType\n );\n $this->getClient()->addAssociations($objectType, $type, $payload);\n }\n\n if (! empty($changes['removed'])) {\n $payload = $this->payloadBuilder->buildRemoveAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['removed']\n );\n $this->getClient()->removeAssociations($objectType, $type, $payload);\n }\n }\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response\n {\n // If already set by us/client, do not override it\n if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {\n $this->logger->info('[HubSpot] Meeting activity type already set', [\n 'meetingId' => $meetingId,\n 'activityType' => $engagement['activityType'],\n ]);\n\n unset($engagement['activityType']);\n }\n\n $payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);\n\n return $this->getClient()->updateMeeting($meetingId, $payload);\n }\n\n private function hasEngagementActivityType(string $engagementId): bool\n {\n try {\n $engagementData = $this->getClient()->getEngagementData($engagementId);\n if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {\n return true;\n }\n\n return false;\n } catch (\\Exception $exception) {\n Sentry::captureException($exception);\n\n $this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [\n 'engagementId' => $engagementId,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n return true;\n }\n\n private function updateTask(Activity $activity, array $engagement, array $metadata): void\n {\n $this->getClient()->deleteEngagement($activity->getCrmProviderId());\n\n $this->logCrmEngagementManipulation(\n self::ACTION_DELETE,\n ['crmId' => $activity->getCrmProviderId()],\n );\n\n $this->createActivity($activity, $engagement, $metadata);\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createActivity(Activity $activity, array $engagement, array $metadata): void\n {\n $associations = $this->getActivityAssociations($activity);\n $associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);\n\n if ($activity->isTypeConference()) {\n try {\n $hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);\n $activityId = $hsEngagement->data->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n } else {\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n $activityId = $hsEngagement->data->engagement->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n }\n }\n\n private function convertActivityMeetingAssociationsV4(Activity $activity): array\n {\n $associations = [];\n\n $contactIds = $this->getParticipantsIds($activity);\n\n foreach ($contactIds as $contactId) {\n $associations[] = $this->buildAssociation(\n (int) $contactId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]\n );\n }\n\n if ($activity->hasAccount()) {\n $crmProviderId = $activity->getAccount()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]\n );\n }\n }\n\n if ($activity->hasOpportunity()) {\n $crmProviderId = $activity->getOpportunity()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]\n );\n }\n }\n\n return $associations;\n }\n\n private function buildAssociation(int $id, array $categoryToTypeIds): array\n {\n $types = [];\n\n foreach ($categoryToTypeIds as $category => $typeId) {\n $types[] = $this->buildAssociationType($category, $typeId);\n }\n\n return [\n 'to' => ['id' => (string) $id],\n 'types' => $types,\n ];\n }\n\n private function buildAssociationType(string $category, int $typeId): array\n {\n return [\n 'associationCategory' => $category,\n 'associationTypeId' => $typeId,\n ];\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createMeetingV3(array $engagement, array $associations, array $metadata): Response\n {\n $payload = $this->payloadBuilder->getV3MeetingPayload(\n engagement: $engagement,\n metadata: $metadata,\n associations: $associations\n );\n\n return $this->getClient()->createMeeting($payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n private function buildSoftphonePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'CALL',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'toNumber' => $decorator->formatCallToParticipant($activity),\n 'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),\n 'status' => $decorator->convertCallStatus($activity),\n 'durationMilliseconds' => (int) $activity->getDuration() * 1000,\n 'body' => $decorator->generateDescription($activity),\n ];\n\n // For now, this is just a \"disposition\" field to store Call Outcome.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildTextMessagePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'NOTE',\n ];\n\n $metadata = [\n 'body' => $decorator->generateDescription($activity),\n ];\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function getActivityAssociations(Activity $activity): array\n {\n return [\n 'contactIds' => $this->getParticipantsIds($activity),\n 'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],\n 'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],\n 'ownerIds' => [],\n ];\n }\n\n /**\n * Get DecorateActivity instance. Can be overridden in tests for mocking.\n */\n protected function getDecorateActivity(): DecorateActivity\n {\n return new DecorateActivity();\n }\n\n public function saveFollowupActivity(Activity $activity, array $fields): ?string\n {\n if (empty($fields['subject'])) {\n return null;\n }\n\n $associations = $this->getActivityAssociations($activity);\n\n if (empty($fields['timestamp']) === false) {\n $formatTimestamp = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['timestamp']);\n $timestamp = $formatTimestamp->getTimestamp();\n } else {\n $timestamp = strtotime('+1 day');\n }\n\n $reminder = null;\n if (empty($fields['reminder']) === false) {\n $formatReminder = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['reminder']);\n $reminder = $formatReminder->getTimestamp() * 1000;\n }\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $timestamp * 1000,\n 'type' => 'TASK',\n ];\n\n $metadata = [\n 'subject' => $fields['subject'],\n 'body' => '',\n 'status' => 'NOT_STARTED',\n 'taskType' => 'CALL',\n 'reminders' => $reminder ? [$reminder] : [],\n ];\n\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n } catch (BadRequest $e) {\n $this->logger->info('[HubSpot] Failed to create followup task', [\n 'activity' => $activity->getUuid(),\n 'engagement' => $engagement,\n 'metadata' => $metadata,\n 'error' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n // We don't actually create a corresponding activity object on our side yet.\n return (string) $hsEngagement->data->engagement->id;\n }\n\n private function logCrmEngagementManipulation(\n string $action,\n array $engagementData,\n array $data = [],\n array $associations = [],\n ): void {\n $payload = ['team' => $this->team->getUuid()];\n\n if ($action === self::ACTION_CREATE) {\n $payload['engagementData'] = $engagementData;\n $payload['data'] = $data;\n $payload['associations'] = $associations;\n }\n\n if ($action === self::ACTION_UPDATE) {\n $payload['engagementId'] = $engagementData['crmId'];\n $payload['data'] = $data;\n }\n\n if ($action === self::ACTION_DELETE) {\n $payload['engagementId'] = $engagementData['crmId'];\n }\n\n $this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"8","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"on_screen":true,"role_description":"text"}]...
|
5722231885167079023
|
-2691170301924544595
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
8
6
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldDataRepository;
use Jiminny\Services\Crm\Hubspot\DecorateActivity;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationCategoryV4;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationTypeIdV4;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Http\Response;
use SevenShores\Hubspot\Exceptions\HubspotException;
use Sentry;
trait WriteCrmTrait
{
private const string ACTION_CREATE = 'create';
private const string ACTION_UPDATE = 'update';
private const string ACTION_DELETE = 'delete';
/**
* @inheritdoc
*/
public function saveActivity(Activity $activity): Activity
{
switch ($activity->getCrmType()) {
case Activity::TYPE_CONFERENCE:
$activity = $this->buildConferencePayload($activity);
break;
case Activity::TYPE_SOFTPHONE:
case Activity::TYPE_SOFTPHONE_INBOUND:
$activity = $this->buildSoftphonePayload($activity);
break;
case Activity::TYPE_SMS_INBOUND:
case Activity::TYPE_SMS_OUTBOUND:
$activity = $this->buildTextMessagePayload($activity);
break;
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildConferencePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatStartTime($activity),
'type' => 'MEETING',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'meetingOutcome' => $decorator->convertMeetingOutcome($activity),
'internalMeetingNotes' => $decorator->generateDescription($activity),
];
// Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.
if (! $activity->hasCrmProviderId()) {
if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {
$metadata['startTime'] = $decorator->formatStartTime($activity);
$metadata['endTime'] = $decorator->formatEndTime($activity);
}
$metadata['title'] = $decorator->generateTitle($activity);
}
// For now, this is just a "meetingOutcome" field for some customers.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function fetchCustomFieldData(Activity $activity, string $objectType): array
{
$payload = [];
$fieldDataRepository = app(FieldDataRepository::class);
$fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);
foreach ($fieldData as $data) {
// Add the field and value to the payload.
$payload += [
$data->getField()->getCrmProviderId() => $data->getValue(),
];
}
return $payload;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity
{
if (! $activity->hasCrmProviderId()) {
$this->createActivity($activity, $engagement, $metadata);
return $activity;
}
try {
$oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());
} catch (HubspotException $e) {
$this->logger->warning('[HubSpot] Engagement not found', [
'activity_id' => $activity->getUuid(),
'crm_provider_id' => $activity->getCrmProviderId(),
'exception' => $e->getMessage(),
]);
// potentially here we can log new activity if it is not found
return $activity;
}
$type = $oldEngagement['engagement']['type'] ?? null;
$bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';
$decorator = $this->getDecorateActivity();
if ($type !== 'TASK') {
$oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldDescription
);
$oldAssociations = $oldEngagement['associations'] ?? [];
$this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);
} else {
$oldTaskBody = $oldEngagement['metadata']['body'] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldTaskBody
);
$this->updateTask($activity, $engagement, $metadata);
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateNonTaskActivity(
Activity $activity,
array $engagement,
array $metadata,
array $oldAssociations
): void {
if ($activity->isTypeConference()) {
$this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);
} else {
$this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);
}
$this->updateActivityAssociations($activity, $oldAssociations);
$this->logCrmEngagementManipulation(
self::ACTION_UPDATE,
['crmId' => $activity->getCrmProviderId()],
$metadata,
);
}
private function updateActivityAssociations(Activity $activity, array $oldAssociations): void
{
$currentAssociations = $this->getActivityAssociations($activity);
$associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);
// No changes - exit
if (empty($associationChanges)) {
return;
}
$this->logger->info('[HubSpot] Updating associations', [
'team' => $this->team->getUuid(),
'activity' => $activity->getUuid(),
'associationChanges' => $associationChanges,
]);
$this->updateAssociations($activity, $associationChanges);
}
private function getAssociationDifferences(
Activity $activity,
array $oldAssociations,
array $currentAssociations
): array {
$objects = ['contactIds', 'companyIds', 'dealIds'];
$changes = [];
foreach ($objects as $object) {
$oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);
$currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);
$added = array_diff($currentIds, $oldIds);
$removed = array_diff($oldIds, $currentIds);
$type = match ($object) {
'contactIds' => 'contact',
'companyIds' => 'company',
'dealIds' => 'deal',
};
$shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);
if (! empty($removed) && ! $shouldIncludeRemoved) {
$this->logger->info('[HubSpot] Skipping association removal', [
'team' => $this->team->getUuid(),
'type' => $type,
'oldIds' => $oldIds,
'currentIds' => $currentIds,
'skippedRemovals' => array_values($removed),
]);
}
if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {
$changes[$type] = [
'added' => array_values($added),
'removed' => array_values($removed),
];
}
}
return $changes;
}
/**
* Determines whether removed associations should be synced to HubSpot.
*
* For contacts and companies, we always sync removals (original behavior).
* For deals, we only sync removals when:
* - There are new deals being added (replacement scenario), OR
* - The activity has an account associated (indicating intentional association management)
*
* This prevents accidentally removing deal associations that exist in HubSpot
* but not in Jiminny's database (e.g., deals manually added by users in HubSpot).
*/
private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool
{
// Contacts and companies: always sync removals
if ($type !== 'deal') {
return true;
}
// Deals: sync removals if we're adding new deals (replacement)
if (! empty($added)) {
return true;
}
// Deals: sync removals only if activity has an account (intentional management)
return $activity->hasAccount();
}
private function normalizeIds(array $ids): array
{
$stringIds = array_map('strval', $ids);
$uniqueIds = array_unique($stringIds);
sort($uniqueIds);
return $uniqueIds;
}
private function updateAssociations(Activity $activity, array $associationChanges): void
{
$objectType = $activity->isTypeConference() ? 'meeting' : 'call';
foreach ($associationChanges as $type => $changes) {
if (! empty($changes['added'])) {
$associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);
$payload = $this->payloadBuilder->buildAddAssociationPayload(
$activity->getCrmProviderId(),
$changes['added'],
$associationType
);
$this->getClient()->addAssociations($objectType, $type, $payload);
}
if (! empty($changes['removed'])) {
$payload = $this->payloadBuilder->buildRemoveAssociationPayload(
$activity->getCrmProviderId(),
$changes['removed']
);
$this->getClient()->removeAssociations($objectType, $type, $payload);
}
}
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response
{
// If already set by us/client, do not override it
if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {
$this->logger->info('[HubSpot] Meeting activity type already set', [
'meetingId' => $meetingId,
'activityType' => $engagement['activityType'],
]);
unset($engagement['activityType']);
}
$payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);
return $this->getClient()->updateMeeting($meetingId, $payload);
}
private function hasEngagementActivityType(string $engagementId): bool
{
try {
$engagementData = $this->getClient()->getEngagementData($engagementId);
if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {
return true;
}
return false;
} catch (\Exception $exception) {
Sentry::captureException($exception);
$this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [
'engagementId' => $engagementId,
'exception' => $exception->getMessage(),
]);
}
return true;
}
private function updateTask(Activity $activity, array $engagement, array $metadata): void
{
$this->getClient()->deleteEngagement($activity->getCrmProviderId());
$this->logCrmEngagementManipulation(
self::ACTION_DELETE,
['crmId' => $activity->getCrmProviderId()],
);
$this->createActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createActivity(Activity $activity, array $engagement, array $metadata): void
{
$associations = $this->getActivityAssociations($activity);
$associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);
if ($activity->isTypeConference()) {
try {
$hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);
$activityId = $hsEngagement->data->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
} else {
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
$activityId = $hsEngagement->data->engagement->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
}
}
private function convertActivityMeetingAssociationsV4(Activity $activity): array
{
$associations = [];
$contactIds = $this->getParticipantsIds($activity);
foreach ($contactIds as $contactId) {
$associations[] = $this->buildAssociation(
(int) $contactId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]
);
}
if ($activity->hasAccount()) {
$crmProviderId = $activity->getAccount()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]
);
}
}
if ($activity->hasOpportunity()) {
$crmProviderId = $activity->getOpportunity()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]
);
}
}
return $associations;
}
private function buildAssociation(int $id, array $categoryToTypeIds): array
{
$types = [];
foreach ($categoryToTypeIds as $category => $typeId) {
$types[] = $this->buildAssociationType($category, $typeId);
}
return [
'to' => ['id' => (string) $id],
'types' => $types,
];
}
private function buildAssociationType(string $category, int $typeId): array
{
return [
'associationCategory' => $category,
'associationTypeId' => $typeId,
];
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createMeetingV3(array $engagement, array $associations, array $metadata): Response
{
$payload = $this->payloadBuilder->getV3MeetingPayload(
engagement: $engagement,
metadata: $metadata,
associations: $associations
);
return $this->getClient()->createMeeting($payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
private function buildSoftphonePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'CALL',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'toNumber' => $decorator->formatCallToParticipant($activity),
'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),
'status' => $decorator->convertCallStatus($activity),
'durationMilliseconds' => (int) $activity->getDuration() * 1000,
'body' => $decorator->generateDescription($activity),
];
// For now, this is just a "disposition" field to store Call Outcome.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildTextMessagePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'NOTE',
];
$metadata = [
'body' => $decorator->generateDescription($activity),
];
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function getActivityAssociations(Activity $activity): array
{
return [
'contactIds' => $this->getParticipantsIds($activity),
'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],
'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],
'ownerIds' => [],
];
}
/**
* Get DecorateActivity instance. Can be overridden in tests for mocking.
*/
protected function getDecorateActivity(): DecorateActivity
{
return new DecorateActivity();
}
public function saveFollowupActivity(Activity $activity, array $fields): ?string
{
if (empty($fields['subject'])) {
return null;
}
$associations = $this->getActivityAssociations($activity);
if (empty($fields['timestamp']) === false) {
$formatTimestamp = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['timestamp']);
$timestamp = $formatTimestamp->getTimestamp();
} else {
$timestamp = strtotime('+1 day');
}
$reminder = null;
if (empty($fields['reminder']) === false) {
$formatReminder = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['reminder']);
$reminder = $formatReminder->getTimestamp() * 1000;
}
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $timestamp * 1000,
'type' => 'TASK',
];
$metadata = [
'subject' => $fields['subject'],
'body' => '',
'status' => 'NOT_STARTED',
'taskType' => 'CALL',
'reminders' => $reminder ? [$reminder] : [],
];
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
} catch (BadRequest $e) {
$this->logger->info('[HubSpot] Failed to create followup task', [
'activity' => $activity->getUuid(),
'engagement' => $engagement,
'metadata' => $metadata,
'error' => $e->getMessage(),
]);
return null;
}
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
// We don't actually create a corresponding activity object on our side yet.
return (string) $hsEngagement->data->engagement->id;
}
private function logCrmEngagementManipulation(
string $action,
array $engagementData,
array $data = [],
array $associations = [],
): void {
$payload = ['team' => $this->team->getUuid()];
if ($action === self::ACTION_CREATE) {
$payload['engagementData'] = $engagementData;
$payload['data'] = $data;
$payload['associations'] = $associations;
}
if ($action === self::ACTION_UPDATE) {
$payload['engagementId'] = $engagementData['crmId'];
$payload['data'] = $data;
}
if ($action === self::ACTION_DELETE) {
$payload['engagementId'] = $engagementData['crmId'];
}
$this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
8
1
3...
|
45180
|
NULL
|
NULL
|
NULL
|
|
45183
|
1621
|
27
|
2026-05-14T14:23:54.902821+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768634902_m1.jpg...
|
PhpStorm
|
faVsco.js – WriteCrmTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
8
6
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldDataRepository;
use Jiminny\Services\Crm\Hubspot\DecorateActivity;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationCategoryV4;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationTypeIdV4;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Http\Response;
use SevenShores\Hubspot\Exceptions\HubspotException;
use Sentry;
trait WriteCrmTrait
{
private const string ACTION_CREATE = 'create';
private const string ACTION_UPDATE = 'update';
private const string ACTION_DELETE = 'delete';
/**
* @inheritdoc
*/
public function saveActivity(Activity $activity): Activity
{
switch ($activity->getCrmType()) {
case Activity::TYPE_CONFERENCE:
$activity = $this->buildConferencePayload($activity);
break;
case Activity::TYPE_SOFTPHONE:
case Activity::TYPE_SOFTPHONE_INBOUND:
$activity = $this->buildSoftphonePayload($activity);
break;
case Activity::TYPE_SMS_INBOUND:
case Activity::TYPE_SMS_OUTBOUND:
$activity = $this->buildTextMessagePayload($activity);
break;
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildConferencePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatStartTime($activity),
'type' => 'MEETING',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'meetingOutcome' => $decorator->convertMeetingOutcome($activity),
'internalMeetingNotes' => $decorator->generateDescription($activity),
];
// Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.
if (! $activity->hasCrmProviderId()) {
if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {
$metadata['startTime'] = $decorator->formatStartTime($activity);
$metadata['endTime'] = $decorator->formatEndTime($activity);
}
$metadata['title'] = $decorator->generateTitle($activity);
}
// For now, this is just a "meetingOutcome" field for some customers.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function fetchCustomFieldData(Activity $activity, string $objectType): array
{
$payload = [];
$fieldDataRepository = app(FieldDataRepository::class);
$fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);
foreach ($fieldData as $data) {
// Add the field and value to the payload.
$payload += [
$data->getField()->getCrmProviderId() => $data->getValue(),
];
}
return $payload;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity
{
if (! $activity->hasCrmProviderId()) {
$this->createActivity($activity, $engagement, $metadata);
return $activity;
}
try {
$oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());
} catch (HubspotException $e) {
$this->logger->warning('[HubSpot] Engagement not found', [
'activity_id' => $activity->getUuid(),
'crm_provider_id' => $activity->getCrmProviderId(),
'exception' => $e->getMessage(),
]);
// potentially here we can log new activity if it is not found
return $activity;
}
$type = $oldEngagement['engagement']['type'] ?? null;
$bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';
$decorator = $this->getDecorateActivity();
if ($type !== 'TASK') {
$oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldDescription
);
$oldAssociations = $oldEngagement['associations'] ?? [];
$this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);
} else {
$oldTaskBody = $oldEngagement['metadata']['body'] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldTaskBody
);
$this->updateTask($activity, $engagement, $metadata);
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateNonTaskActivity(
Activity $activity,
array $engagement,
array $metadata,
array $oldAssociations
): void {
if ($activity->isTypeConference()) {
$this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);
} else {
$this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);
}
$this->updateActivityAssociations($activity, $oldAssociations);
$this->logCrmEngagementManipulation(
self::ACTION_UPDATE,
['crmId' => $activity->getCrmProviderId()],
$metadata,
);
}
private function updateActivityAssociations(Activity $activity, array $oldAssociations): void
{
$currentAssociations = $this->getActivityAssociations($activity);
$associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);
// No changes - exit
if (empty($associationChanges)) {
return;
}
$this->logger->info('[HubSpot] Updating associations', [
'team' => $this->team->getUuid(),
'activity' => $activity->getUuid(),
'associationChanges' => $associationChanges,
]);
$this->updateAssociations($activity, $associationChanges);
}
private function getAssociationDifferences(
Activity $activity,
array $oldAssociations,
array $currentAssociations
): array {
$objects = ['contactIds', 'companyIds', 'dealIds'];
$changes = [];
foreach ($objects as $object) {
$oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);
$currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);
$added = array_diff($currentIds, $oldIds);
$removed = array_diff($oldIds, $currentIds);
$type = match ($object) {
'contactIds' => 'contact',
'companyIds' => 'company',
'dealIds' => 'deal',
};
$shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);
if (! empty($removed) && ! $shouldIncludeRemoved) {
$this->logger->info('[HubSpot] Skipping association removal', [
'team' => $this->team->getUuid(),
'type' => $type,
'oldIds' => $oldIds,
'currentIds' => $currentIds,
'skippedRemovals' => array_values($removed),
]);
}
if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {
$changes[$type] = [
'added' => array_values($added),
'removed' => array_values($removed),
];
}
}
return $changes;
}
/**
* Determines whether removed associations should be synced to HubSpot.
*
* For contacts and companies, we always sync removals (original behavior).
* For deals, we only sync removals when:
* - There are new deals being added (replacement scenario), OR
* - The activity has an account associated (indicating intentional association management)
*
* This prevents accidentally removing deal associations that exist in HubSpot
* but not in Jiminny's database (e.g., deals manually added by users in HubSpot).
*/
private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool
{
// Contacts and companies: always sync removals
if ($type !== 'deal') {
return true;
}
// Deals: sync removals if we're adding new deals (replacement)
if (! empty($added)) {
return true;
}
// Deals: sync removals only if activity has an account (intentional management)
return $activity->hasAccount();
}
private function normalizeIds(array $ids): array
{
$stringIds = array_map('strval', $ids);
$uniqueIds = array_unique($stringIds);
sort($uniqueIds);
return $uniqueIds;
}
private function updateAssociations(Activity $activity, array $associationChanges): void
{
$objectType = $activity->isTypeConference() ? 'meeting' : 'call';
foreach ($associationChanges as $type => $changes) {
if (! empty($changes['added'])) {
$associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);
$payload = $this->payloadBuilder->buildAddAssociationPayload(
$activity->getCrmProviderId(),
$changes['added'],
$associationType
);
$this->getClient()->addAssociations($objectType, $type, $payload);
}
if (! empty($changes['removed'])) {
$payload = $this->payloadBuilder->buildRemoveAssociationPayload(
$activity->getCrmProviderId(),
$changes['removed']
);
$this->getClient()->removeAssociations($objectType, $type, $payload);
}
}
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response
{
// If already set by us/client, do not override it
if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {
$this->logger->info('[HubSpot] Meeting activity type already set', [
'meetingId' => $meetingId,
'activityType' => $engagement['activityType'],
]);
unset($engagement['activityType']);
}
$payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);
return $this->getClient()->updateMeeting($meetingId, $payload);
}
private function hasEngagementActivityType(string $engagementId): bool
{
try {
$engagementData = $this->getClient()->getEngagementData($engagementId);
if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {
return true;
}
return false;
} catch (\Exception $exception) {
Sentry::captureException($exception);
$this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [
'engagementId' => $engagementId,
'exception' => $exception->getMessage(),
]);
}
return true;
}
private function updateTask(Activity $activity, array $engagement, array $metadata): void
{
$this->getClient()->deleteEngagement($activity->getCrmProviderId());
$this->logCrmEngagementManipulation(
self::ACTION_DELETE,
['crmId' => $activity->getCrmProviderId()],
);
$this->createActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createActivity(Activity $activity, array $engagement, array $metadata): void
{
$associations = $this->getActivityAssociations($activity);
$associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);
if ($activity->isTypeConference()) {
try {
$hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);
$activityId = $hsEngagement->data->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
} else {
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
$activityId = $hsEngagement->data->engagement->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
}
}
private function convertActivityMeetingAssociationsV4(Activity $activity): array
{
$associations = [];
$contactIds = $this->getParticipantsIds($activity);
foreach ($contactIds as $contactId) {
$associations[] = $this->buildAssociation(
(int) $contactId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]
);
}
if ($activity->hasAccount()) {
$crmProviderId = $activity->getAccount()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]
);
}
}
if ($activity->hasOpportunity()) {
$crmProviderId = $activity->getOpportunity()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]
);
}
}
return $associations;
}
private function buildAssociation(int $id, array $categoryToTypeIds): array
{
$types = [];
foreach ($categoryToTypeIds as $category => $typeId) {
$types[] = $this->buildAssociationType($category, $typeId);
}
return [
'to' => ['id' => (string) $id],
'types' => $types,
];
}
private function buildAssociationType(string $category, int $typeId): array
{
return [
'associationCategory' => $category,
'associationTypeId' => $typeId,
];
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createMeetingV3(array $engagement, array $associations, array $metadata): Response
{
$payload = $this->payloadBuilder->getV3MeetingPayload(
engagement: $engagement,
metadata: $metadata,
associations: $associations
);
return $this->getClient()->createMeeting($payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
private function buildSoftphonePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'CALL',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'toNumber' => $decorator->formatCallToParticipant($activity),
'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),
'status' => $decorator->convertCallStatus($activity),
'durationMilliseconds' => (int) $activity->getDuration() * 1000,
'body' => $decorator->generateDescription($activity),
];
// For now, this is just a "disposition" field to store Call Outcome.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildTextMessagePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'NOTE',
];
$metadata = [
'body' => $decorator->generateDescription($activity),
];
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function getActivityAssociations(Activity $activity): array
{
return [
'contactIds' => $this->getParticipantsIds($activity),
'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],
'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],
'ownerIds' => [],
];
}
/**
* Get DecorateActivity instance. Can be overridden in tests for mocking.
*/
protected function getDecorateActivity(): DecorateActivity
{
return new DecorateActivity();
}
public function saveFollowupActivity(Activity $activity, array $fields): ?string
{
if (empty($fields['subject'])) {
return null;
}
$associations = $this->getActivityAssociations($activity);
if (empty($fields['timestamp']) === false) {
$formatTimestamp = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['timestamp']);
$timestamp = $formatTimestamp->getTimestamp();
} else {
$timestamp = strtotime('+1 day');
}
$reminder = null;
if (empty($fields['reminder']) === false) {
$formatReminder = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['reminder']);
$reminder = $formatReminder->getTimestamp() * 1000;
}
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $timestamp * 1000,
'type' => 'TASK',
];
$metadata = [
'subject' => $fields['subject'],
'body' => '',
'status' => 'NOT_STARTED',
'taskType' => 'CALL',
'reminders' => $reminder ? [$reminder] : [],
];
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
} catch (BadRequest $e) {
$this->logger->info('[HubSpot] Failed to create followup task', [
'activity' => $activity->getUuid(),
'engagement' => $engagement,
'metadata' => $metadata,
'error' => $e->getMessage(),
]);
return null;
}
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
// We don't actually create a corresponding activity object on our side yet.
return (string) $hsEngagement->data->engagement->id;
}
private function logCrmEngagementManipulation(
string $action,
array $engagementData,
array $data = [],
array $associations = [],
): void {
$payload = ['team' => $this->team->getUuid()];
if ($action === self::ACTION_CREATE) {
$payload['engagementData'] = $engagementData;
$payload['data'] = $data;
$payload['associations'] = $associations;
}
if ($action === self::ACTION_UPDATE) {
$payload['engagementId'] = $engagementData['crmId'];
$payload['data'] = $data;
}
if ($action === self::ACTION_DELETE) {
$payload['engagementId'] = $engagementData['crmId'];
}
$this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
8
1
3
4
Previous Highlighted Error
Next Highlighted Error
SELECT
# DISTINCT
opp.id as opp_id, opp.uuid, opp.name,
# COUNT(dr.id) AS deal_risk_count,
# dr.id,
# cfd.value,
usr.name AS owner_name,
opp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,
usr.uuid as owner_uuid,
usr.photo_path as owner_photo,
usr.id AS owner_id,
jt.name as owner_job,
opp.stage_id, opp.stage_updated_at,
acc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,
rt.business_process_id AS pipeline_id
FROM opportunities opp
LEFT JOIN record_types rt ON opp.record_type_id = rt.id
LEFT JOIN users usr ON opp.user_id = usr.id
LEFT JOIN accounts acc ON opp.account_id = acc.id
LEFT JOIN job_titles jt ON usr.job_title_id = jt.id
# LEFT JOIN group_deal_risk_types gdrt ON gdrt.group_id = usr.group_id
# LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id AND dr.group_deal_risk_type_id = gdrt.id
LEFT JOIN crm_field_data cfd ON (cfd.object_id = opp.id and cfd.crm_field_id = 66814)
WHERE opp.user_id IS NOT NULL
AND opp.deleted_at IS NULL
AND opp.is_closed = 1
AND opp.is_won = 0
AND opp.close_date >= '2024-01-01 00:00:00'
AND opp.close_date <= '2024-12-31 23:59:59'
AND usr.team_id = 1
# and opp.id = 4823179
GROUP BY opp.id, cfd.value
ORDER BY
# cfd.value
CAST(cfd.value AS UNSIGNED)
# owner_name
# (SELECT COUNT(dr.id)
# FROM deal_risks dr
# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
# WHERE dr.opportunity_id = opp.id
# AND dr.is_active = 1
# AND gdrt.is_enabled = 1)
DESC
LIMIT 25
# OFFSET 0
;
select * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';
select * from crm_layout_entities where crm_layout_id = 1493;
SELECT * FROM opportunities WHERE id = 4584045;
SELECT * FROM crm_field_data WHERE object_id = 4584045;
SELECT * FROM opportunities where team_id = 1 and crm_provider_id = '0061K00000lfb9IQAQ'
SELECT
opp.id as opportunity_id,
opp.name,
COUNT(dr.id) as risk_count
FROM opportunities opp
LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id
WHERE opp.id IN (
6069574,8900938,9507638,9524799,9660490,9662824,9705675,9749758,4158460,4391812,6450439,4448422,5118945,5590675,4584045,7228149,9002408,9165534,9446720,9641778,9665149,9703344,9709280,5747948,4158491,6182565,6263970,5798120,5111315,4536978,6062352,6548383,6072095,6548225,4480986,5011422,6548381,8760540,8917554,9509986,9514392,9569009,9569011,9578490,9713604,5784397,7276612,7288405,8994421,9118219,9608148,9818911,4510535,6479693,5958049,6271674,6550448,4158331,4158483,6126571,6171615,6540943,4897466,5190896,5796182,5932762,8572433,8723698,8892697,9711001,9789264,4549188,6100831,6170064,6260799,6263653,6449936,6530871,6538978,7777651,8059269,8319918,8787049,8901150,9263153,9453207,9514738,9696700,9791062,5752018,6421452,7439134,7878923,9354763,9369285,9514396,9582506,9889949,9890216,4094311,4158495,4158496,6098128,5585661,3872564,6442149,5891604,6164746,6199593,6583474,6519684,9018490,9809006,4496897,5041324,5829430,6198319,6253504,6555763,7242914,7931055,8024125,8797814,8058559,8673347,8892695,8994420,9616219,9714970,9722004,9809439,9818918,6523177,8134147,9002915,9711422,9892713,9901719,9954210,9978435,5800810,6243518,6416114,6222251,6411974,6512456,5791953,6545606,9914780,5805540,6238986,6463838,6547680,9767049,9809437,9810885,9890855,6673493,9902036,4335521,6379871,6503799,6546077,8018765,9907556,9958433,9905855,9916179,9946741,9957877,5563344,6271838,6450815,7641128,7762567,7780592,8684810,8685786,8685787,8685788
)
GROUP BY opp.id;
SELECT COUNT(dr.id)
FROM deal_risks dr
JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
WHERE dr.opportunity_id = 5563344
# AND gdrt.group_id = usr.group_id
EXPLAIN SELECT
opp.id as opp_id, opp.uuid, opp.name,
cfd.value,
cfv.sequence,
cfv.value,
# MAX(cfd.value) AS max_cfd_value,
usr.name AS owner_name,
opp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,
usr.uuid as owner_uuid,
usr.photo_path as owner_photo,
usr.id AS owner_id,
jt.name as owner_job,
opp.stage_id, opp.stage_updated_at,
acc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,
rt.business_process_id AS pipeline_id
FROM opportunities opp
LEFT JOIN record_types rt ON opp.record_type_id = rt.id
LEFT JOIN users usr ON opp.user_id = usr.id
LEFT JOIN accounts acc ON opp.account_id = acc.id
LEFT JOIN job_titles jt ON usr.job_title_id = jt.id
LEFT JOIN crm_field_data cfd ON cfd.id = (SELECT sub_cfd.id
FROM crm_field_data sub_cfd
WHERE sub_cfd.object_id = opp.id
AND sub_cfd.crm_field_id = 66810
ORDER BY sub_cfd.updated_at DESC
LIMIT 1)
LEFT JOIN crm_field_values cfv ON cfv.crm_field_id = 66810 AND cfv.value = cfd.value
WHERE opp.user_id IS NOT NULL
AND opp.deleted_at IS NULL
AND opp.is_closed = 0
# AND opp.is_closed = 1
# AND opp.is_won = 0
AND opp.close_date >= '2024-01-01 00:00:00'
AND opp.close_date <= '2024-12-31 23:59:59'
AND usr.team_id = 1
# and opp.id = 4823179
# GROUP BY opp.id
ORDER BY
cfv.sequence DESC,
# opp.name
# owner_name
cfd.value
# CAST(cfd.value AS UNSIGNED)
# CAST(MAX(cfd.value) AS UNSIGNED)
# (SELECT COUNT(dr.id)
# FROM deal_risks dr
# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
# WHERE dr.opportunity_id = opp.id
# AND dr.is_active = 1 AND gdrt.is_enabled = 1)
DESC
# LIMIT 75, 25
;
SELECT * FROM crm_field_values WHERE crm_field_id = 66810;
SELECT f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value, fd.updated_at, fd.id, fd.created_at
FROM crm_fields f
INNER JOIN crm_field_data fd ON fd.crm_field_id = f.id
WHERE (f.crm_configuration_id = 1)
AND (f.object_type = 'opportunity')
# AND (fd.object_id IN (4158331,4158483,4158495,4158496,6069574,6263970,6171615,6540943,6545606,5829430,6198319,6263653,6548381,7242914,8797814,7228149,7439134,7878923,8134147,9002915,9118219,9165534,9354763,9369285,9446720))
AND (fd.object_id IN (4158460))
AND (f.crm_provider_id IN ('ForecastCategoryName'))
ORDER BY fd.object_id ASC, fd.updated_at DESC;
select * from crm_layouts where crm_configuration_id = 1;
select cf.* from crm_fields cf
join crm_layout_entities cle on cf.id = cle.crm_field_id
where crm_layout_id = 1493;
SELECT * FROM opportunities WHERE id = 4158460;
SELECT * FROM crm_field_data WHERE object_id = 4158460;
select * from users where team_id = 1;
SELECT * FROM users WHERE id = 7160;
select * from role_user where user_id = 3248;
select * from crm_field_values where crm_field_id = 66824;
SELECT * FROM users WHERE id = 23470;
select * from crm_configurations;
# [PASSWORD_DOTS]
select * from teams where id = 1038; # 23521, 966
select * from users where team_id = 1038;
select * from crm_configurations where id = 966;
SELECT * FROM activities WHERE uuid_to_bin('e8dc7c34-31f6-4430-bd81-2d9d8f00dd07') = uuid; # 54965762
SELECT * FROM activities WHERE uuid_to_bin('4b5727c0-69d1-428f-b8d9-b649055166e2') = uuid; # 54965764
SELECT * FROM participants WHERE activity_id = 54965764;
SELECT id, uuid, title, recording_state FROM activities WHERE uuid_to_bin('355f105d-5606-4379-b0c5-d91daf59f4ce') = uuid; # 54964075
SELECT * FROM participants WHERE activity_id = 54964075;
select f.id, slug, title, tf.team_id from team_features tf JOIN features f on tf.feature_id = f.id
where tf.team_id = 1038;
# [PASSWORD_DOTS] PD [PASSWORD_DOTS]
select * from teams where id = 1029;
SELECT * FROM crm_configurations WHERE id = 957;
SELECT * FROM activities WHERE uuid_to_bin('581a33cb-4343-44bd-ac0c-4a5cee71c5ec') = uuid; # 54963423
SELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433
SELECT * FROM activities WHERE uuid_to_bin('8b6bb2f7-e7a7-4aec-8806-fbfea7093ccd') = uuid; # 54963421
# [PASSWORD_DOTS] Close [PASSWORD_DOTS]
select * from teams where id = 1031;
SELECT * FROM crm_configurations WHERE id = 959;
SELECT * FROM activities WHERE uuid_to_bin('59c517e0-96c9-4dad-bfeb-b7bef72f8725') = uuid; # 54963517
SELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433
SELECT * FROM activities WHERE uuid_to_bin('d68311e3-ac34-49bd-bf04-88323a7e5352') = uuid; # 54966009
# [PASSWORD_DOTS] SF [PASSWORD_DOTS]
SELECT * FROM activities WHERE uuid_to_bin('2bd4cfa3-4eb5-4c77-84a7-9ed46a21c988') = uuid;
select * from roles;
select * from permissions;
select * from permission_role where permission_id = 136;
select * from migrations order by id desc;
select * from teams where id IN (1, 1037);
select * from crm_layouts where crm_configuration_id = 1;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 1493;
SELECT * FROM crm_fields WHERE id IN (1652,1661,66799,66814,66821,66836,66843,66846,66864,84752,182306,323580,380378);
select * from features;
select * from team_features where feature_id = 33;
select * from opportunities;
select * from teams;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1052 and sa.provider = 'hubspot';
SELECT * FROM accounts where id = 11512582;
select * from activities where account_id = 11512582;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.054166667,"top":0.027777778,"width":0.08055556,"height":0.035555556},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#12066 on JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.13472222,"top":0.027777778,"width":0.25555557,"height":0.035555556},"on_screen":true,"help_text":"Pull request #12066 exists for current branch JY-20725-handle-HS-search-rate-limit, 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.6326389,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HandleHubspotRateLimitTest","depth":6,"bounds":{"left":0.6645833,"top":0.027777778,"width":0.15902779,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.82361114,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.8472222,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.87083334,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.9291667,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9527778,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9763889,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"8","depth":4,"bounds":{"left":0.78055555,"top":0.2777778,"width":0.016666668,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.80138886,"top":0.2777778,"width":0.016666668,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.8215278,"top":0.27555555,"width":0.015277778,"height":0.025555555},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.8368056,"top":0.27555555,"width":0.014583333,"height":0.025555555},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldDataRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DecorateActivity;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationCategoryV4;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationTypeIdV4;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse Sentry;\n\ntrait WriteCrmTrait\n{\n private const string ACTION_CREATE = 'create';\n private const string ACTION_UPDATE = 'update';\n private const string ACTION_DELETE = 'delete';\n\n /**\n * @inheritdoc\n */\n public function saveActivity(Activity $activity): Activity\n {\n switch ($activity->getCrmType()) {\n case Activity::TYPE_CONFERENCE:\n $activity = $this->buildConferencePayload($activity);\n\n break;\n case Activity::TYPE_SOFTPHONE:\n case Activity::TYPE_SOFTPHONE_INBOUND:\n $activity = $this->buildSoftphonePayload($activity);\n\n break;\n\n case Activity::TYPE_SMS_INBOUND:\n case Activity::TYPE_SMS_OUTBOUND:\n $activity = $this->buildTextMessagePayload($activity);\n\n break;\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildConferencePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatStartTime($activity),\n 'type' => 'MEETING',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'meetingOutcome' => $decorator->convertMeetingOutcome($activity),\n 'internalMeetingNotes' => $decorator->generateDescription($activity),\n ];\n\n // Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.\n if (! $activity->hasCrmProviderId()) {\n if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {\n $metadata['startTime'] = $decorator->formatStartTime($activity);\n $metadata['endTime'] = $decorator->formatEndTime($activity);\n }\n $metadata['title'] = $decorator->generateTitle($activity);\n }\n\n // For now, this is just a \"meetingOutcome\" field for some customers.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function fetchCustomFieldData(Activity $activity, string $objectType): array\n {\n $payload = [];\n\n $fieldDataRepository = app(FieldDataRepository::class);\n $fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);\n\n foreach ($fieldData as $data) {\n // Add the field and value to the payload.\n $payload += [\n $data->getField()->getCrmProviderId() => $data->getValue(),\n ];\n }\n\n return $payload;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity\n {\n if (! $activity->hasCrmProviderId()) {\n $this->createActivity($activity, $engagement, $metadata);\n\n return $activity;\n }\n\n try {\n $oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());\n } catch (HubspotException $e) {\n $this->logger->warning('[HubSpot] Engagement not found', [\n 'activity_id' => $activity->getUuid(),\n 'crm_provider_id' => $activity->getCrmProviderId(),\n 'exception' => $e->getMessage(),\n ]);\n\n // potentially here we can log new activity if it is not found\n\n return $activity;\n }\n\n $type = $oldEngagement['engagement']['type'] ?? null;\n $bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';\n\n $decorator = $this->getDecorateActivity();\n\n if ($type !== 'TASK') {\n $oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldDescription\n );\n\n $oldAssociations = $oldEngagement['associations'] ?? [];\n $this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);\n } else {\n $oldTaskBody = $oldEngagement['metadata']['body'] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldTaskBody\n );\n $this->updateTask($activity, $engagement, $metadata);\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateNonTaskActivity(\n Activity $activity,\n array $engagement,\n array $metadata,\n array $oldAssociations\n ): void {\n if ($activity->isTypeConference()) {\n $this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);\n } else {\n $this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);\n }\n\n $this->updateActivityAssociations($activity, $oldAssociations);\n\n $this->logCrmEngagementManipulation(\n self::ACTION_UPDATE,\n ['crmId' => $activity->getCrmProviderId()],\n $metadata,\n );\n }\n\n private function updateActivityAssociations(Activity $activity, array $oldAssociations): void\n {\n $currentAssociations = $this->getActivityAssociations($activity);\n $associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);\n\n // No changes - exit\n if (empty($associationChanges)) {\n return;\n }\n\n $this->logger->info('[HubSpot] Updating associations', [\n 'team' => $this->team->getUuid(),\n 'activity' => $activity->getUuid(),\n 'associationChanges' => $associationChanges,\n ]);\n\n $this->updateAssociations($activity, $associationChanges);\n }\n\n private function getAssociationDifferences(\n Activity $activity,\n array $oldAssociations,\n array $currentAssociations\n ): array {\n $objects = ['contactIds', 'companyIds', 'dealIds'];\n $changes = [];\n\n foreach ($objects as $object) {\n $oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);\n $currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);\n\n $added = array_diff($currentIds, $oldIds);\n $removed = array_diff($oldIds, $currentIds);\n\n $type = match ($object) {\n 'contactIds' => 'contact',\n 'companyIds' => 'company',\n 'dealIds' => 'deal',\n };\n\n $shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);\n\n if (! empty($removed) && ! $shouldIncludeRemoved) {\n $this->logger->info('[HubSpot] Skipping association removal', [\n 'team' => $this->team->getUuid(),\n 'type' => $type,\n 'oldIds' => $oldIds,\n 'currentIds' => $currentIds,\n 'skippedRemovals' => array_values($removed),\n ]);\n }\n\n if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {\n $changes[$type] = [\n 'added' => array_values($added),\n 'removed' => array_values($removed),\n ];\n }\n }\n\n return $changes;\n }\n\n /**\n * Determines whether removed associations should be synced to HubSpot.\n *\n * For contacts and companies, we always sync removals (original behavior).\n * For deals, we only sync removals when:\n * - There are new deals being added (replacement scenario), OR\n * - The activity has an account associated (indicating intentional association management)\n *\n * This prevents accidentally removing deal associations that exist in HubSpot\n * but not in Jiminny's database (e.g., deals manually added by users in HubSpot).\n */\n private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool\n {\n // Contacts and companies: always sync removals\n if ($type !== 'deal') {\n return true;\n }\n\n // Deals: sync removals if we're adding new deals (replacement)\n if (! empty($added)) {\n return true;\n }\n\n // Deals: sync removals only if activity has an account (intentional management)\n return $activity->hasAccount();\n }\n\n private function normalizeIds(array $ids): array\n {\n $stringIds = array_map('strval', $ids);\n $uniqueIds = array_unique($stringIds);\n sort($uniqueIds);\n\n return $uniqueIds;\n }\n\n private function updateAssociations(Activity $activity, array $associationChanges): void\n {\n $objectType = $activity->isTypeConference() ? 'meeting' : 'call';\n\n foreach ($associationChanges as $type => $changes) {\n if (! empty($changes['added'])) {\n $associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);\n $payload = $this->payloadBuilder->buildAddAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['added'],\n $associationType\n );\n $this->getClient()->addAssociations($objectType, $type, $payload);\n }\n\n if (! empty($changes['removed'])) {\n $payload = $this->payloadBuilder->buildRemoveAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['removed']\n );\n $this->getClient()->removeAssociations($objectType, $type, $payload);\n }\n }\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response\n {\n // If already set by us/client, do not override it\n if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {\n $this->logger->info('[HubSpot] Meeting activity type already set', [\n 'meetingId' => $meetingId,\n 'activityType' => $engagement['activityType'],\n ]);\n\n unset($engagement['activityType']);\n }\n\n $payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);\n\n return $this->getClient()->updateMeeting($meetingId, $payload);\n }\n\n private function hasEngagementActivityType(string $engagementId): bool\n {\n try {\n $engagementData = $this->getClient()->getEngagementData($engagementId);\n if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {\n return true;\n }\n\n return false;\n } catch (\\Exception $exception) {\n Sentry::captureException($exception);\n\n $this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [\n 'engagementId' => $engagementId,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n return true;\n }\n\n private function updateTask(Activity $activity, array $engagement, array $metadata): void\n {\n $this->getClient()->deleteEngagement($activity->getCrmProviderId());\n\n $this->logCrmEngagementManipulation(\n self::ACTION_DELETE,\n ['crmId' => $activity->getCrmProviderId()],\n );\n\n $this->createActivity($activity, $engagement, $metadata);\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createActivity(Activity $activity, array $engagement, array $metadata): void\n {\n $associations = $this->getActivityAssociations($activity);\n $associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);\n\n if ($activity->isTypeConference()) {\n try {\n $hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);\n $activityId = $hsEngagement->data->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n } else {\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n $activityId = $hsEngagement->data->engagement->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n }\n }\n\n private function convertActivityMeetingAssociationsV4(Activity $activity): array\n {\n $associations = [];\n\n $contactIds = $this->getParticipantsIds($activity);\n\n foreach ($contactIds as $contactId) {\n $associations[] = $this->buildAssociation(\n (int) $contactId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]\n );\n }\n\n if ($activity->hasAccount()) {\n $crmProviderId = $activity->getAccount()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]\n );\n }\n }\n\n if ($activity->hasOpportunity()) {\n $crmProviderId = $activity->getOpportunity()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]\n );\n }\n }\n\n return $associations;\n }\n\n private function buildAssociation(int $id, array $categoryToTypeIds): array\n {\n $types = [];\n\n foreach ($categoryToTypeIds as $category => $typeId) {\n $types[] = $this->buildAssociationType($category, $typeId);\n }\n\n return [\n 'to' => ['id' => (string) $id],\n 'types' => $types,\n ];\n }\n\n private function buildAssociationType(string $category, int $typeId): array\n {\n return [\n 'associationCategory' => $category,\n 'associationTypeId' => $typeId,\n ];\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createMeetingV3(array $engagement, array $associations, array $metadata): Response\n {\n $payload = $this->payloadBuilder->getV3MeetingPayload(\n engagement: $engagement,\n metadata: $metadata,\n associations: $associations\n );\n\n return $this->getClient()->createMeeting($payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n private function buildSoftphonePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'CALL',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'toNumber' => $decorator->formatCallToParticipant($activity),\n 'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),\n 'status' => $decorator->convertCallStatus($activity),\n 'durationMilliseconds' => (int) $activity->getDuration() * 1000,\n 'body' => $decorator->generateDescription($activity),\n ];\n\n // For now, this is just a \"disposition\" field to store Call Outcome.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildTextMessagePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'NOTE',\n ];\n\n $metadata = [\n 'body' => $decorator->generateDescription($activity),\n ];\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function getActivityAssociations(Activity $activity): array\n {\n return [\n 'contactIds' => $this->getParticipantsIds($activity),\n 'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],\n 'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],\n 'ownerIds' => [],\n ];\n }\n\n /**\n * Get DecorateActivity instance. Can be overridden in tests for mocking.\n */\n protected function getDecorateActivity(): DecorateActivity\n {\n return new DecorateActivity();\n }\n\n public function saveFollowupActivity(Activity $activity, array $fields): ?string\n {\n if (empty($fields['subject'])) {\n return null;\n }\n\n $associations = $this->getActivityAssociations($activity);\n\n if (empty($fields['timestamp']) === false) {\n $formatTimestamp = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['timestamp']);\n $timestamp = $formatTimestamp->getTimestamp();\n } else {\n $timestamp = strtotime('+1 day');\n }\n\n $reminder = null;\n if (empty($fields['reminder']) === false) {\n $formatReminder = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['reminder']);\n $reminder = $formatReminder->getTimestamp() * 1000;\n }\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $timestamp * 1000,\n 'type' => 'TASK',\n ];\n\n $metadata = [\n 'subject' => $fields['subject'],\n 'body' => '',\n 'status' => 'NOT_STARTED',\n 'taskType' => 'CALL',\n 'reminders' => $reminder ? [$reminder] : [],\n ];\n\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n } catch (BadRequest $e) {\n $this->logger->info('[HubSpot] Failed to create followup task', [\n 'activity' => $activity->getUuid(),\n 'engagement' => $engagement,\n 'metadata' => $metadata,\n 'error' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n // We don't actually create a corresponding activity object on our side yet.\n return (string) $hsEngagement->data->engagement->id;\n }\n\n private function logCrmEngagementManipulation(\n string $action,\n array $engagementData,\n array $data = [],\n array $associations = [],\n ): void {\n $payload = ['team' => $this->team->getUuid()];\n\n if ($action === self::ACTION_CREATE) {\n $payload['engagementData'] = $engagementData;\n $payload['data'] = $data;\n $payload['associations'] = $associations;\n }\n\n if ($action === self::ACTION_UPDATE) {\n $payload['engagementId'] = $engagementData['crmId'];\n $payload['data'] = $data;\n }\n\n if ($action === self::ACTION_DELETE) {\n $payload['engagementId'] = $engagementData['crmId'];\n }\n\n $this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldDataRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DecorateActivity;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationCategoryV4;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationTypeIdV4;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse Sentry;\n\ntrait WriteCrmTrait\n{\n private const string ACTION_CREATE = 'create';\n private const string ACTION_UPDATE = 'update';\n private const string ACTION_DELETE = 'delete';\n\n /**\n * @inheritdoc\n */\n public function saveActivity(Activity $activity): Activity\n {\n switch ($activity->getCrmType()) {\n case Activity::TYPE_CONFERENCE:\n $activity = $this->buildConferencePayload($activity);\n\n break;\n case Activity::TYPE_SOFTPHONE:\n case Activity::TYPE_SOFTPHONE_INBOUND:\n $activity = $this->buildSoftphonePayload($activity);\n\n break;\n\n case Activity::TYPE_SMS_INBOUND:\n case Activity::TYPE_SMS_OUTBOUND:\n $activity = $this->buildTextMessagePayload($activity);\n\n break;\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildConferencePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatStartTime($activity),\n 'type' => 'MEETING',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'meetingOutcome' => $decorator->convertMeetingOutcome($activity),\n 'internalMeetingNotes' => $decorator->generateDescription($activity),\n ];\n\n // Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.\n if (! $activity->hasCrmProviderId()) {\n if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {\n $metadata['startTime'] = $decorator->formatStartTime($activity);\n $metadata['endTime'] = $decorator->formatEndTime($activity);\n }\n $metadata['title'] = $decorator->generateTitle($activity);\n }\n\n // For now, this is just a \"meetingOutcome\" field for some customers.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function fetchCustomFieldData(Activity $activity, string $objectType): array\n {\n $payload = [];\n\n $fieldDataRepository = app(FieldDataRepository::class);\n $fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);\n\n foreach ($fieldData as $data) {\n // Add the field and value to the payload.\n $payload += [\n $data->getField()->getCrmProviderId() => $data->getValue(),\n ];\n }\n\n return $payload;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity\n {\n if (! $activity->hasCrmProviderId()) {\n $this->createActivity($activity, $engagement, $metadata);\n\n return $activity;\n }\n\n try {\n $oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());\n } catch (HubspotException $e) {\n $this->logger->warning('[HubSpot] Engagement not found', [\n 'activity_id' => $activity->getUuid(),\n 'crm_provider_id' => $activity->getCrmProviderId(),\n 'exception' => $e->getMessage(),\n ]);\n\n // potentially here we can log new activity if it is not found\n\n return $activity;\n }\n\n $type = $oldEngagement['engagement']['type'] ?? null;\n $bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';\n\n $decorator = $this->getDecorateActivity();\n\n if ($type !== 'TASK') {\n $oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldDescription\n );\n\n $oldAssociations = $oldEngagement['associations'] ?? [];\n $this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);\n } else {\n $oldTaskBody = $oldEngagement['metadata']['body'] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldTaskBody\n );\n $this->updateTask($activity, $engagement, $metadata);\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateNonTaskActivity(\n Activity $activity,\n array $engagement,\n array $metadata,\n array $oldAssociations\n ): void {\n if ($activity->isTypeConference()) {\n $this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);\n } else {\n $this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);\n }\n\n $this->updateActivityAssociations($activity, $oldAssociations);\n\n $this->logCrmEngagementManipulation(\n self::ACTION_UPDATE,\n ['crmId' => $activity->getCrmProviderId()],\n $metadata,\n );\n }\n\n private function updateActivityAssociations(Activity $activity, array $oldAssociations): void\n {\n $currentAssociations = $this->getActivityAssociations($activity);\n $associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);\n\n // No changes - exit\n if (empty($associationChanges)) {\n return;\n }\n\n $this->logger->info('[HubSpot] Updating associations', [\n 'team' => $this->team->getUuid(),\n 'activity' => $activity->getUuid(),\n 'associationChanges' => $associationChanges,\n ]);\n\n $this->updateAssociations($activity, $associationChanges);\n }\n\n private function getAssociationDifferences(\n Activity $activity,\n array $oldAssociations,\n array $currentAssociations\n ): array {\n $objects = ['contactIds', 'companyIds', 'dealIds'];\n $changes = [];\n\n foreach ($objects as $object) {\n $oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);\n $currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);\n\n $added = array_diff($currentIds, $oldIds);\n $removed = array_diff($oldIds, $currentIds);\n\n $type = match ($object) {\n 'contactIds' => 'contact',\n 'companyIds' => 'company',\n 'dealIds' => 'deal',\n };\n\n $shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);\n\n if (! empty($removed) && ! $shouldIncludeRemoved) {\n $this->logger->info('[HubSpot] Skipping association removal', [\n 'team' => $this->team->getUuid(),\n 'type' => $type,\n 'oldIds' => $oldIds,\n 'currentIds' => $currentIds,\n 'skippedRemovals' => array_values($removed),\n ]);\n }\n\n if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {\n $changes[$type] = [\n 'added' => array_values($added),\n 'removed' => array_values($removed),\n ];\n }\n }\n\n return $changes;\n }\n\n /**\n * Determines whether removed associations should be synced to HubSpot.\n *\n * For contacts and companies, we always sync removals (original behavior).\n * For deals, we only sync removals when:\n * - There are new deals being added (replacement scenario), OR\n * - The activity has an account associated (indicating intentional association management)\n *\n * This prevents accidentally removing deal associations that exist in HubSpot\n * but not in Jiminny's database (e.g., deals manually added by users in HubSpot).\n */\n private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool\n {\n // Contacts and companies: always sync removals\n if ($type !== 'deal') {\n return true;\n }\n\n // Deals: sync removals if we're adding new deals (replacement)\n if (! empty($added)) {\n return true;\n }\n\n // Deals: sync removals only if activity has an account (intentional management)\n return $activity->hasAccount();\n }\n\n private function normalizeIds(array $ids): array\n {\n $stringIds = array_map('strval', $ids);\n $uniqueIds = array_unique($stringIds);\n sort($uniqueIds);\n\n return $uniqueIds;\n }\n\n private function updateAssociations(Activity $activity, array $associationChanges): void\n {\n $objectType = $activity->isTypeConference() ? 'meeting' : 'call';\n\n foreach ($associationChanges as $type => $changes) {\n if (! empty($changes['added'])) {\n $associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);\n $payload = $this->payloadBuilder->buildAddAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['added'],\n $associationType\n );\n $this->getClient()->addAssociations($objectType, $type, $payload);\n }\n\n if (! empty($changes['removed'])) {\n $payload = $this->payloadBuilder->buildRemoveAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['removed']\n );\n $this->getClient()->removeAssociations($objectType, $type, $payload);\n }\n }\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response\n {\n // If already set by us/client, do not override it\n if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {\n $this->logger->info('[HubSpot] Meeting activity type already set', [\n 'meetingId' => $meetingId,\n 'activityType' => $engagement['activityType'],\n ]);\n\n unset($engagement['activityType']);\n }\n\n $payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);\n\n return $this->getClient()->updateMeeting($meetingId, $payload);\n }\n\n private function hasEngagementActivityType(string $engagementId): bool\n {\n try {\n $engagementData = $this->getClient()->getEngagementData($engagementId);\n if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {\n return true;\n }\n\n return false;\n } catch (\\Exception $exception) {\n Sentry::captureException($exception);\n\n $this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [\n 'engagementId' => $engagementId,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n return true;\n }\n\n private function updateTask(Activity $activity, array $engagement, array $metadata): void\n {\n $this->getClient()->deleteEngagement($activity->getCrmProviderId());\n\n $this->logCrmEngagementManipulation(\n self::ACTION_DELETE,\n ['crmId' => $activity->getCrmProviderId()],\n );\n\n $this->createActivity($activity, $engagement, $metadata);\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createActivity(Activity $activity, array $engagement, array $metadata): void\n {\n $associations = $this->getActivityAssociations($activity);\n $associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);\n\n if ($activity->isTypeConference()) {\n try {\n $hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);\n $activityId = $hsEngagement->data->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n } else {\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n $activityId = $hsEngagement->data->engagement->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n }\n }\n\n private function convertActivityMeetingAssociationsV4(Activity $activity): array\n {\n $associations = [];\n\n $contactIds = $this->getParticipantsIds($activity);\n\n foreach ($contactIds as $contactId) {\n $associations[] = $this->buildAssociation(\n (int) $contactId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]\n );\n }\n\n if ($activity->hasAccount()) {\n $crmProviderId = $activity->getAccount()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]\n );\n }\n }\n\n if ($activity->hasOpportunity()) {\n $crmProviderId = $activity->getOpportunity()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]\n );\n }\n }\n\n return $associations;\n }\n\n private function buildAssociation(int $id, array $categoryToTypeIds): array\n {\n $types = [];\n\n foreach ($categoryToTypeIds as $category => $typeId) {\n $types[] = $this->buildAssociationType($category, $typeId);\n }\n\n return [\n 'to' => ['id' => (string) $id],\n 'types' => $types,\n ];\n }\n\n private function buildAssociationType(string $category, int $typeId): array\n {\n return [\n 'associationCategory' => $category,\n 'associationTypeId' => $typeId,\n ];\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createMeetingV3(array $engagement, array $associations, array $metadata): Response\n {\n $payload = $this->payloadBuilder->getV3MeetingPayload(\n engagement: $engagement,\n metadata: $metadata,\n associations: $associations\n );\n\n return $this->getClient()->createMeeting($payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n private function buildSoftphonePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'CALL',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'toNumber' => $decorator->formatCallToParticipant($activity),\n 'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),\n 'status' => $decorator->convertCallStatus($activity),\n 'durationMilliseconds' => (int) $activity->getDuration() * 1000,\n 'body' => $decorator->generateDescription($activity),\n ];\n\n // For now, this is just a \"disposition\" field to store Call Outcome.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildTextMessagePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'NOTE',\n ];\n\n $metadata = [\n 'body' => $decorator->generateDescription($activity),\n ];\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function getActivityAssociations(Activity $activity): array\n {\n return [\n 'contactIds' => $this->getParticipantsIds($activity),\n 'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],\n 'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],\n 'ownerIds' => [],\n ];\n }\n\n /**\n * Get DecorateActivity instance. Can be overridden in tests for mocking.\n */\n protected function getDecorateActivity(): DecorateActivity\n {\n return new DecorateActivity();\n }\n\n public function saveFollowupActivity(Activity $activity, array $fields): ?string\n {\n if (empty($fields['subject'])) {\n return null;\n }\n\n $associations = $this->getActivityAssociations($activity);\n\n if (empty($fields['timestamp']) === false) {\n $formatTimestamp = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['timestamp']);\n $timestamp = $formatTimestamp->getTimestamp();\n } else {\n $timestamp = strtotime('+1 day');\n }\n\n $reminder = null;\n if (empty($fields['reminder']) === false) {\n $formatReminder = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['reminder']);\n $reminder = $formatReminder->getTimestamp() * 1000;\n }\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $timestamp * 1000,\n 'type' => 'TASK',\n ];\n\n $metadata = [\n 'subject' => $fields['subject'],\n 'body' => '',\n 'status' => 'NOT_STARTED',\n 'taskType' => 'CALL',\n 'reminders' => $reminder ? [$reminder] : [],\n ];\n\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n } catch (BadRequest $e) {\n $this->logger->info('[HubSpot] Failed to create followup task', [\n 'activity' => $activity->getUuid(),\n 'engagement' => $engagement,\n 'metadata' => $metadata,\n 'error' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n // We don't actually create a corresponding activity object on our side yet.\n return (string) $hsEngagement->data->engagement->id;\n }\n\n private function logCrmEngagementManipulation(\n string $action,\n array $engagementData,\n array $data = [],\n array $associations = [],\n ): void {\n $payload = ['team' => $this->team->getUuid()];\n\n if ($action === self::ACTION_CREATE) {\n $payload['engagementData'] = $engagementData;\n $payload['data'] = $data;\n $payload['associations'] = $associations;\n }\n\n if ($action === self::ACTION_UPDATE) {\n $payload['engagementId'] = $engagementData['crmId'];\n $payload['data'] = $data;\n }\n\n if ($action === self::ACTION_DELETE) {\n $payload['engagementId'] = $engagementData['crmId'];\n }\n\n $this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.85486114,"top":0.31,"width":0.018055556,"height":0.026666667},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"bounds":{"left":0.87291664,"top":0.31,"width":0.018055556,"height":0.026666667},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"bounds":{"left":0.9138889,"top":0.31,"width":0.059027776,"height":0.026666667},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"8","depth":4,"bounds":{"left":0.8645833,"top":0.34444445,"width":0.016666668,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.8854167,"top":0.34444445,"width":0.015277778,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.9048611,"top":0.34444445,"width":0.016666668,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"4","depth":4,"bounds":{"left":0.92569447,"top":0.34444445,"width":0.016666668,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.9458333,"top":0.3422222,"width":0.015277778,"height":0.025555555},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.9611111,"top":0.3422222,"width":0.014583333,"height":0.025555555},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"SELECT\n# DISTINCT\nopp.id as opp_id, opp.uuid, opp.name,\n# COUNT(dr.id) AS deal_risk_count,\n\n# dr.id,\n# cfd.value,\nusr.name AS owner_name,\nopp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,\nusr.uuid as owner_uuid,\nusr.photo_path as owner_photo,\nusr.id AS owner_id,\njt.name as owner_job,\nopp.stage_id, opp.stage_updated_at,\nacc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,\nrt.business_process_id AS pipeline_id\nFROM opportunities opp\nLEFT JOIN record_types rt ON opp.record_type_id = rt.id\nLEFT JOIN users usr ON opp.user_id = usr.id\nLEFT JOIN accounts acc ON opp.account_id = acc.id\nLEFT JOIN job_titles jt ON usr.job_title_id = jt.id\n\n# LEFT JOIN group_deal_risk_types gdrt ON gdrt.group_id = usr.group_id\n# LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id AND dr.group_deal_risk_type_id = gdrt.id\n\nLEFT JOIN crm_field_data cfd ON (cfd.object_id = opp.id and cfd.crm_field_id = 66814)\n\nWHERE opp.user_id IS NOT NULL\nAND opp.deleted_at IS NULL\nAND opp.is_closed = 1\nAND opp.is_won = 0\nAND opp.close_date >= '2024-01-01 00:00:00'\nAND opp.close_date <= '2024-12-31 23:59:59'\nAND usr.team_id = 1\n\n# and opp.id = 4823179\n\nGROUP BY opp.id, cfd.value\nORDER BY\n# cfd.value\n CAST(cfd.value AS UNSIGNED)\n# owner_name\n# (SELECT COUNT(dr.id)\n# FROM deal_risks dr\n# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n# WHERE dr.opportunity_id = opp.id\n# AND dr.is_active = 1\n# AND gdrt.is_enabled = 1)\nDESC\nLIMIT 25\n# OFFSET 0\n;\n\nselect * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';\nselect * from crm_layout_entities where crm_layout_id = 1493;\n\nSELECT * FROM opportunities WHERE id = 4584045;\nSELECT * FROM crm_field_data WHERE object_id = 4584045;\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id = '0061K00000lfb9IQAQ'\n\nSELECT\n opp.id as opportunity_id,\n opp.name,\n COUNT(dr.id) as risk_count\nFROM opportunities opp\nLEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id\nWHERE opp.id IN (\n 6069574,8900938,9507638,9524799,9660490,9662824,9705675,9749758,4158460,4391812,6450439,4448422,5118945,5590675,4584045,7228149,9002408,9165534,9446720,9641778,9665149,9703344,9709280,5747948,4158491,6182565,6263970,5798120,5111315,4536978,6062352,6548383,6072095,6548225,4480986,5011422,6548381,8760540,8917554,9509986,9514392,9569009,9569011,9578490,9713604,5784397,7276612,7288405,8994421,9118219,9608148,9818911,4510535,6479693,5958049,6271674,6550448,4158331,4158483,6126571,6171615,6540943,4897466,5190896,5796182,5932762,8572433,8723698,8892697,9711001,9789264,4549188,6100831,6170064,6260799,6263653,6449936,6530871,6538978,7777651,8059269,8319918,8787049,8901150,9263153,9453207,9514738,9696700,9791062,5752018,6421452,7439134,7878923,9354763,9369285,9514396,9582506,9889949,9890216,4094311,4158495,4158496,6098128,5585661,3872564,6442149,5891604,6164746,6199593,6583474,6519684,9018490,9809006,4496897,5041324,5829430,6198319,6253504,6555763,7242914,7931055,8024125,8797814,8058559,8673347,8892695,8994420,9616219,9714970,9722004,9809439,9818918,6523177,8134147,9002915,9711422,9892713,9901719,9954210,9978435,5800810,6243518,6416114,6222251,6411974,6512456,5791953,6545606,9914780,5805540,6238986,6463838,6547680,9767049,9809437,9810885,9890855,6673493,9902036,4335521,6379871,6503799,6546077,8018765,9907556,9958433,9905855,9916179,9946741,9957877,5563344,6271838,6450815,7641128,7762567,7780592,8684810,8685786,8685787,8685788\n)\nGROUP BY opp.id;\n\nSELECT COUNT(dr.id)\n FROM deal_risks dr\n JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n WHERE dr.opportunity_id = 5563344\n# AND gdrt.group_id = usr.group_id\n\n\n\nEXPLAIN SELECT\n opp.id as opp_id, opp.uuid, opp.name,\n\ncfd.value,\ncfv.sequence,\ncfv.value,\n# MAX(cfd.value) AS max_cfd_value,\nusr.name AS owner_name,\nopp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,\nusr.uuid as owner_uuid,\nusr.photo_path as owner_photo,\nusr.id AS owner_id,\njt.name as owner_job,\nopp.stage_id, opp.stage_updated_at,\nacc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,\nrt.business_process_id AS pipeline_id\nFROM opportunities opp\n\nLEFT JOIN record_types rt ON opp.record_type_id = rt.id\nLEFT JOIN users usr ON opp.user_id = usr.id\nLEFT JOIN accounts acc ON opp.account_id = acc.id\nLEFT JOIN job_titles jt ON usr.job_title_id = jt.id\n\nLEFT JOIN crm_field_data cfd ON cfd.id = (SELECT sub_cfd.id\n FROM crm_field_data sub_cfd\n WHERE sub_cfd.object_id = opp.id\n AND sub_cfd.crm_field_id = 66810\n ORDER BY sub_cfd.updated_at DESC\n LIMIT 1)\n\nLEFT JOIN crm_field_values cfv ON cfv.crm_field_id = 66810 AND cfv.value = cfd.value\n\nWHERE opp.user_id IS NOT NULL\nAND opp.deleted_at IS NULL\nAND opp.is_closed = 0\n# AND opp.is_closed = 1\n# AND opp.is_won = 0\nAND opp.close_date >= '2024-01-01 00:00:00'\nAND opp.close_date <= '2024-12-31 23:59:59'\nAND usr.team_id = 1\n\n# and opp.id = 4823179\n\n# GROUP BY opp.id\nORDER BY\n cfv.sequence DESC,\n# opp.name\n# owner_name\n cfd.value\n# CAST(cfd.value AS UNSIGNED)\n# CAST(MAX(cfd.value) AS UNSIGNED)\n# (SELECT COUNT(dr.id)\n# FROM deal_risks dr\n# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n# WHERE dr.opportunity_id = opp.id\n# AND dr.is_active = 1 AND gdrt.is_enabled = 1)\nDESC\n# LIMIT 75, 25\n;\n\n\n\nSELECT * FROM crm_field_values WHERE crm_field_id = 66810;\n\nSELECT f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value, fd.updated_at, fd.id, fd.created_at\nFROM crm_fields f\nINNER JOIN crm_field_data fd ON fd.crm_field_id = f.id\n WHERE (f.crm_configuration_id = 1)\n AND (f.object_type = 'opportunity')\n# AND (fd.object_id IN (4158331,4158483,4158495,4158496,6069574,6263970,6171615,6540943,6545606,5829430,6198319,6263653,6548381,7242914,8797814,7228149,7439134,7878923,8134147,9002915,9118219,9165534,9354763,9369285,9446720))\n AND (fd.object_id IN (4158460))\n AND (f.crm_provider_id IN ('ForecastCategoryName'))\n ORDER BY fd.object_id ASC, fd.updated_at DESC;\n\nselect * from crm_layouts where crm_configuration_id = 1;\nselect cf.* from crm_fields cf\njoin crm_layout_entities cle on cf.id = cle.crm_field_id\nwhere crm_layout_id = 1493;\n\nSELECT * FROM opportunities WHERE id = 4158460;\nSELECT * FROM crm_field_data WHERE object_id = 4158460;\n\nselect * from users where team_id = 1;\nSELECT * FROM users WHERE id = 7160;\nselect * from role_user where user_id = 3248;\n\nselect * from crm_field_values where crm_field_id = 66824;\n\nSELECT * FROM users WHERE id = 23470;\nselect * from crm_configurations;\n\n# ******************************************\nselect * from teams where id = 1038; # 23521, 966\nselect * from users where team_id = 1038;\nselect * from crm_configurations where id = 966;\nSELECT * FROM activities WHERE uuid_to_bin('e8dc7c34-31f6-4430-bd81-2d9d8f00dd07') = uuid; # 54965762\nSELECT * FROM activities WHERE uuid_to_bin('4b5727c0-69d1-428f-b8d9-b649055166e2') = uuid; # 54965764\nSELECT * FROM participants WHERE activity_id = 54965764;\n\nSELECT id, uuid, title, recording_state FROM activities WHERE uuid_to_bin('355f105d-5606-4379-b0c5-d91daf59f4ce') = uuid; # 54964075\nSELECT * FROM participants WHERE activity_id = 54964075;\nselect f.id, slug, title, tf.team_id from team_features tf JOIN features f on tf.feature_id = f.id\n where tf.team_id = 1038;\n\n\n# ****************************************** PD *************************\nselect * from teams where id = 1029;\nSELECT * FROM crm_configurations WHERE id = 957;\nSELECT * FROM activities WHERE uuid_to_bin('581a33cb-4343-44bd-ac0c-4a5cee71c5ec') = uuid; # 54963423\nSELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433\n\nSELECT * FROM activities WHERE uuid_to_bin('8b6bb2f7-e7a7-4aec-8806-fbfea7093ccd') = uuid; # 54963421\n\n# ****************************************** Close *************************\nselect * from teams where id = 1031;\nSELECT * FROM crm_configurations WHERE id = 959;\nSELECT * FROM activities WHERE uuid_to_bin('59c517e0-96c9-4dad-bfeb-b7bef72f8725') = uuid; # 54963517\n\n\nSELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433\n\nSELECT * FROM activities WHERE uuid_to_bin('d68311e3-ac34-49bd-bf04-88323a7e5352') = uuid; # 54966009\n\n# ****************************************** SF *************************\nSELECT * FROM activities WHERE uuid_to_bin('2bd4cfa3-4eb5-4c77-84a7-9ed46a21c988') = uuid;\n\nselect * from roles;\nselect * from permissions;\nselect * from permission_role where permission_id = 136;\n\nselect * from migrations order by id desc;\n\nselect * from teams where id IN (1, 1037);\nselect * from crm_layouts where crm_configuration_id = 1;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1493;\nSELECT * FROM crm_fields WHERE id IN (1652,1661,66799,66814,66821,66836,66843,66846,66864,84752,182306,323580,380378);\n\nselect * from features;\nselect * from team_features where feature_id = 33;\nselect * from opportunities;\n\nselect * from teams;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1052 and sa.provider = 'hubspot';\n\nSELECT * FROM accounts where id = 11512582;\nselect * from activities where account_id = 11512582;","depth":4,"on_screen":true,"value":"SELECT\n# DISTINCT\nopp.id as opp_id, opp.uuid, opp.name,\n# COUNT(dr.id) AS deal_risk_count,\n\n# dr.id,\n# cfd.value,\nusr.name AS owner_name,\nopp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,\nusr.uuid as owner_uuid,\nusr.photo_path as owner_photo,\nusr.id AS owner_id,\njt.name as owner_job,\nopp.stage_id, opp.stage_updated_at,\nacc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,\nrt.business_process_id AS pipeline_id\nFROM opportunities opp\nLEFT JOIN record_types rt ON opp.record_type_id = rt.id\nLEFT JOIN users usr ON opp.user_id = usr.id\nLEFT JOIN accounts acc ON opp.account_id = acc.id\nLEFT JOIN job_titles jt ON usr.job_title_id = jt.id\n\n# LEFT JOIN group_deal_risk_types gdrt ON gdrt.group_id = usr.group_id\n# LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id AND dr.group_deal_risk_type_id = gdrt.id\n\nLEFT JOIN crm_field_data cfd ON (cfd.object_id = opp.id and cfd.crm_field_id = 66814)\n\nWHERE opp.user_id IS NOT NULL\nAND opp.deleted_at IS NULL\nAND opp.is_closed = 1\nAND opp.is_won = 0\nAND opp.close_date >= '2024-01-01 00:00:00'\nAND opp.close_date <= '2024-12-31 23:59:59'\nAND usr.team_id = 1\n\n# and opp.id = 4823179\n\nGROUP BY opp.id, cfd.value\nORDER BY\n# cfd.value\n CAST(cfd.value AS UNSIGNED)\n# owner_name\n# (SELECT COUNT(dr.id)\n# FROM deal_risks dr\n# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n# WHERE dr.opportunity_id = opp.id\n# AND dr.is_active = 1\n# AND gdrt.is_enabled = 1)\nDESC\nLIMIT 25\n# OFFSET 0\n;\n\nselect * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';\nselect * from crm_layout_entities where crm_layout_id = 1493;\n\nSELECT * FROM opportunities WHERE id = 4584045;\nSELECT * FROM crm_field_data WHERE object_id = 4584045;\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id = '0061K00000lfb9IQAQ'\n\nSELECT\n opp.id as opportunity_id,\n opp.name,\n COUNT(dr.id) as risk_count\nFROM opportunities opp\nLEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id\nWHERE opp.id IN (\n 6069574,8900938,9507638,9524799,9660490,9662824,9705675,9749758,4158460,4391812,6450439,4448422,5118945,5590675,4584045,7228149,9002408,9165534,9446720,9641778,9665149,9703344,9709280,5747948,4158491,6182565,6263970,5798120,5111315,4536978,6062352,6548383,6072095,6548225,4480986,5011422,6548381,8760540,8917554,9509986,9514392,9569009,9569011,9578490,9713604,5784397,7276612,7288405,8994421,9118219,9608148,9818911,4510535,6479693,5958049,6271674,6550448,4158331,4158483,6126571,6171615,6540943,4897466,5190896,5796182,5932762,8572433,8723698,8892697,9711001,9789264,4549188,6100831,6170064,6260799,6263653,6449936,6530871,6538978,7777651,8059269,8319918,8787049,8901150,9263153,9453207,9514738,9696700,9791062,5752018,6421452,7439134,7878923,9354763,9369285,9514396,9582506,9889949,9890216,4094311,4158495,4158496,6098128,5585661,3872564,6442149,5891604,6164746,6199593,6583474,6519684,9018490,9809006,4496897,5041324,5829430,6198319,6253504,6555763,7242914,7931055,8024125,8797814,8058559,8673347,8892695,8994420,9616219,9714970,9722004,9809439,9818918,6523177,8134147,9002915,9711422,9892713,9901719,9954210,9978435,5800810,6243518,6416114,6222251,6411974,6512456,5791953,6545606,9914780,5805540,6238986,6463838,6547680,9767049,9809437,9810885,9890855,6673493,9902036,4335521,6379871,6503799,6546077,8018765,9907556,9958433,9905855,9916179,9946741,9957877,5563344,6271838,6450815,7641128,7762567,7780592,8684810,8685786,8685787,8685788\n)\nGROUP BY opp.id;\n\nSELECT COUNT(dr.id)\n FROM deal_risks dr\n JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n WHERE dr.opportunity_id = 5563344\n# AND gdrt.group_id = usr.group_id\n\n\n\nEXPLAIN SELECT\n opp.id as opp_id, opp.uuid, opp.name,\n\ncfd.value,\ncfv.sequence,\ncfv.value,\n# MAX(cfd.value) AS max_cfd_value,\nusr.name AS owner_name,\nopp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,\nusr.uuid as owner_uuid,\nusr.photo_path as owner_photo,\nusr.id AS owner_id,\njt.name as owner_job,\nopp.stage_id, opp.stage_updated_at,\nacc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,\nrt.business_process_id AS pipeline_id\nFROM opportunities opp\n\nLEFT JOIN record_types rt ON opp.record_type_id = rt.id\nLEFT JOIN users usr ON opp.user_id = usr.id\nLEFT JOIN accounts acc ON opp.account_id = acc.id\nLEFT JOIN job_titles jt ON usr.job_title_id = jt.id\n\nLEFT JOIN crm_field_data cfd ON cfd.id = (SELECT sub_cfd.id\n FROM crm_field_data sub_cfd\n WHERE sub_cfd.object_id = opp.id\n AND sub_cfd.crm_field_id = 66810\n ORDER BY sub_cfd.updated_at DESC\n LIMIT 1)\n\nLEFT JOIN crm_field_values cfv ON cfv.crm_field_id = 66810 AND cfv.value = cfd.value\n\nWHERE opp.user_id IS NOT NULL\nAND opp.deleted_at IS NULL\nAND opp.is_closed = 0\n# AND opp.is_closed = 1\n# AND opp.is_won = 0\nAND opp.close_date >= '2024-01-01 00:00:00'\nAND opp.close_date <= '2024-12-31 23:59:59'\nAND usr.team_id = 1\n\n# and opp.id = 4823179\n\n# GROUP BY opp.id\nORDER BY\n cfv.sequence DESC,\n# opp.name\n# owner_name\n cfd.value\n# CAST(cfd.value AS UNSIGNED)\n# CAST(MAX(cfd.value) AS UNSIGNED)\n# (SELECT COUNT(dr.id)\n# FROM deal_risks dr\n# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n# WHERE dr.opportunity_id = opp.id\n# AND dr.is_active = 1 AND gdrt.is_enabled = 1)\nDESC\n# LIMIT 75, 25\n;\n\n\n\nSELECT * FROM crm_field_values WHERE crm_field_id = 66810;\n\nSELECT f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value, fd.updated_at, fd.id, fd.created_at\nFROM crm_fields f\nINNER JOIN crm_field_data fd ON fd.crm_field_id = f.id\n WHERE (f.crm_configuration_id = 1)\n AND (f.object_type = 'opportunity')\n# AND (fd.object_id IN (4158331,4158483,4158495,4158496,6069574,6263970,6171615,6540943,6545606,5829430,6198319,6263653,6548381,7242914,8797814,7228149,7439134,7878923,8134147,9002915,9118219,9165534,9354763,9369285,9446720))\n AND (fd.object_id IN (4158460))\n AND (f.crm_provider_id IN ('ForecastCategoryName'))\n ORDER BY fd.object_id ASC, fd.updated_at DESC;\n\nselect * from crm_layouts where crm_configuration_id = 1;\nselect cf.* from crm_fields cf\njoin crm_layout_entities cle on cf.id = cle.crm_field_id\nwhere crm_layout_id = 1493;\n\nSELECT * FROM opportunities WHERE id = 4158460;\nSELECT * FROM crm_field_data WHERE object_id = 4158460;\n\nselect * from users where team_id = 1;\nSELECT * FROM users WHERE id = 7160;\nselect * from role_user where user_id = 3248;\n\nselect * from crm_field_values where crm_field_id = 66824;\n\nSELECT * FROM users WHERE id = 23470;\nselect * from crm_configurations;\n\n# ******************************************\nselect * from teams where id = 1038; # 23521, 966\nselect * from users where team_id = 1038;\nselect * from crm_configurations where id = 966;\nSELECT * FROM activities WHERE uuid_to_bin('e8dc7c34-31f6-4430-bd81-2d9d8f00dd07') = uuid; # 54965762\nSELECT * FROM activities WHERE uuid_to_bin('4b5727c0-69d1-428f-b8d9-b649055166e2') = uuid; # 54965764\nSELECT * FROM participants WHERE activity_id = 54965764;\n\nSELECT id, uuid, title, recording_state FROM activities WHERE uuid_to_bin('355f105d-5606-4379-b0c5-d91daf59f4ce') = uuid; # 54964075\nSELECT * FROM participants WHERE activity_id = 54964075;\nselect f.id, slug, title, tf.team_id from team_features tf JOIN features f on tf.feature_id = f.id\n where tf.team_id = 1038;\n\n\n# ****************************************** PD *************************\nselect * from teams where id = 1029;\nSELECT * FROM crm_configurations WHERE id = 957;\nSELECT * FROM activities WHERE uuid_to_bin('581a33cb-4343-44bd-ac0c-4a5cee71c5ec') = uuid; # 54963423\nSELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433\n\nSELECT * FROM activities WHERE uuid_to_bin('8b6bb2f7-e7a7-4aec-8806-fbfea7093ccd') = uuid; # 54963421\n\n# ****************************************** Close *************************\nselect * from teams where id = 1031;\nSELECT * FROM crm_configurations WHERE id = 959;\nSELECT * FROM activities WHERE uuid_to_bin('59c517e0-96c9-4dad-bfeb-b7bef72f8725') = uuid; # 54963517\n\n\nSELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433\n\nSELECT * FROM activities WHERE uuid_to_bin('d68311e3-ac34-49bd-bf04-88323a7e5352') = uuid; # 54966009\n\n# ****************************************** SF *************************\nSELECT * FROM activities WHERE uuid_to_bin('2bd4cfa3-4eb5-4c77-84a7-9ed46a21c988') = uuid;\n\nselect * from roles;\nselect * from permissions;\nselect * from permission_role where permission_id = 136;\n\nselect * from migrations order by id desc;\n\nselect * from teams where id IN (1, 1037);\nselect * from crm_layouts where crm_configuration_id = 1;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1493;\nSELECT * FROM crm_fields WHERE id IN (1652,1661,66799,66814,66821,66836,66843,66846,66864,84752,182306,323580,380378);\n\nselect * from features;\nselect * from team_features where feature_id = 33;\nselect * from opportunities;\n\nselect * from teams;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1052 and sa.provider = 'hubspot';\n\nSELECT * FROM accounts where id = 11512582;\nselect * from activities where account_id = 11512582;","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.025,"top":0.06666667,"width":0.050694443,"height":0.034444444},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3382584868951134281
|
6551498163460912845
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
8
6
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldDataRepository;
use Jiminny\Services\Crm\Hubspot\DecorateActivity;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationCategoryV4;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationTypeIdV4;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Http\Response;
use SevenShores\Hubspot\Exceptions\HubspotException;
use Sentry;
trait WriteCrmTrait
{
private const string ACTION_CREATE = 'create';
private const string ACTION_UPDATE = 'update';
private const string ACTION_DELETE = 'delete';
/**
* @inheritdoc
*/
public function saveActivity(Activity $activity): Activity
{
switch ($activity->getCrmType()) {
case Activity::TYPE_CONFERENCE:
$activity = $this->buildConferencePayload($activity);
break;
case Activity::TYPE_SOFTPHONE:
case Activity::TYPE_SOFTPHONE_INBOUND:
$activity = $this->buildSoftphonePayload($activity);
break;
case Activity::TYPE_SMS_INBOUND:
case Activity::TYPE_SMS_OUTBOUND:
$activity = $this->buildTextMessagePayload($activity);
break;
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildConferencePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatStartTime($activity),
'type' => 'MEETING',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'meetingOutcome' => $decorator->convertMeetingOutcome($activity),
'internalMeetingNotes' => $decorator->generateDescription($activity),
];
// Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.
if (! $activity->hasCrmProviderId()) {
if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {
$metadata['startTime'] = $decorator->formatStartTime($activity);
$metadata['endTime'] = $decorator->formatEndTime($activity);
}
$metadata['title'] = $decorator->generateTitle($activity);
}
// For now, this is just a "meetingOutcome" field for some customers.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function fetchCustomFieldData(Activity $activity, string $objectType): array
{
$payload = [];
$fieldDataRepository = app(FieldDataRepository::class);
$fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);
foreach ($fieldData as $data) {
// Add the field and value to the payload.
$payload += [
$data->getField()->getCrmProviderId() => $data->getValue(),
];
}
return $payload;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity
{
if (! $activity->hasCrmProviderId()) {
$this->createActivity($activity, $engagement, $metadata);
return $activity;
}
try {
$oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());
} catch (HubspotException $e) {
$this->logger->warning('[HubSpot] Engagement not found', [
'activity_id' => $activity->getUuid(),
'crm_provider_id' => $activity->getCrmProviderId(),
'exception' => $e->getMessage(),
]);
// potentially here we can log new activity if it is not found
return $activity;
}
$type = $oldEngagement['engagement']['type'] ?? null;
$bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';
$decorator = $this->getDecorateActivity();
if ($type !== 'TASK') {
$oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldDescription
);
$oldAssociations = $oldEngagement['associations'] ?? [];
$this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);
} else {
$oldTaskBody = $oldEngagement['metadata']['body'] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldTaskBody
);
$this->updateTask($activity, $engagement, $metadata);
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateNonTaskActivity(
Activity $activity,
array $engagement,
array $metadata,
array $oldAssociations
): void {
if ($activity->isTypeConference()) {
$this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);
} else {
$this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);
}
$this->updateActivityAssociations($activity, $oldAssociations);
$this->logCrmEngagementManipulation(
self::ACTION_UPDATE,
['crmId' => $activity->getCrmProviderId()],
$metadata,
);
}
private function updateActivityAssociations(Activity $activity, array $oldAssociations): void
{
$currentAssociations = $this->getActivityAssociations($activity);
$associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);
// No changes - exit
if (empty($associationChanges)) {
return;
}
$this->logger->info('[HubSpot] Updating associations', [
'team' => $this->team->getUuid(),
'activity' => $activity->getUuid(),
'associationChanges' => $associationChanges,
]);
$this->updateAssociations($activity, $associationChanges);
}
private function getAssociationDifferences(
Activity $activity,
array $oldAssociations,
array $currentAssociations
): array {
$objects = ['contactIds', 'companyIds', 'dealIds'];
$changes = [];
foreach ($objects as $object) {
$oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);
$currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);
$added = array_diff($currentIds, $oldIds);
$removed = array_diff($oldIds, $currentIds);
$type = match ($object) {
'contactIds' => 'contact',
'companyIds' => 'company',
'dealIds' => 'deal',
};
$shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);
if (! empty($removed) && ! $shouldIncludeRemoved) {
$this->logger->info('[HubSpot] Skipping association removal', [
'team' => $this->team->getUuid(),
'type' => $type,
'oldIds' => $oldIds,
'currentIds' => $currentIds,
'skippedRemovals' => array_values($removed),
]);
}
if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {
$changes[$type] = [
'added' => array_values($added),
'removed' => array_values($removed),
];
}
}
return $changes;
}
/**
* Determines whether removed associations should be synced to HubSpot.
*
* For contacts and companies, we always sync removals (original behavior).
* For deals, we only sync removals when:
* - There are new deals being added (replacement scenario), OR
* - The activity has an account associated (indicating intentional association management)
*
* This prevents accidentally removing deal associations that exist in HubSpot
* but not in Jiminny's database (e.g., deals manually added by users in HubSpot).
*/
private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool
{
// Contacts and companies: always sync removals
if ($type !== 'deal') {
return true;
}
// Deals: sync removals if we're adding new deals (replacement)
if (! empty($added)) {
return true;
}
// Deals: sync removals only if activity has an account (intentional management)
return $activity->hasAccount();
}
private function normalizeIds(array $ids): array
{
$stringIds = array_map('strval', $ids);
$uniqueIds = array_unique($stringIds);
sort($uniqueIds);
return $uniqueIds;
}
private function updateAssociations(Activity $activity, array $associationChanges): void
{
$objectType = $activity->isTypeConference() ? 'meeting' : 'call';
foreach ($associationChanges as $type => $changes) {
if (! empty($changes['added'])) {
$associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);
$payload = $this->payloadBuilder->buildAddAssociationPayload(
$activity->getCrmProviderId(),
$changes['added'],
$associationType
);
$this->getClient()->addAssociations($objectType, $type, $payload);
}
if (! empty($changes['removed'])) {
$payload = $this->payloadBuilder->buildRemoveAssociationPayload(
$activity->getCrmProviderId(),
$changes['removed']
);
$this->getClient()->removeAssociations($objectType, $type, $payload);
}
}
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response
{
// If already set by us/client, do not override it
if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {
$this->logger->info('[HubSpot] Meeting activity type already set', [
'meetingId' => $meetingId,
'activityType' => $engagement['activityType'],
]);
unset($engagement['activityType']);
}
$payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);
return $this->getClient()->updateMeeting($meetingId, $payload);
}
private function hasEngagementActivityType(string $engagementId): bool
{
try {
$engagementData = $this->getClient()->getEngagementData($engagementId);
if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {
return true;
}
return false;
} catch (\Exception $exception) {
Sentry::captureException($exception);
$this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [
'engagementId' => $engagementId,
'exception' => $exception->getMessage(),
]);
}
return true;
}
private function updateTask(Activity $activity, array $engagement, array $metadata): void
{
$this->getClient()->deleteEngagement($activity->getCrmProviderId());
$this->logCrmEngagementManipulation(
self::ACTION_DELETE,
['crmId' => $activity->getCrmProviderId()],
);
$this->createActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createActivity(Activity $activity, array $engagement, array $metadata): void
{
$associations = $this->getActivityAssociations($activity);
$associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);
if ($activity->isTypeConference()) {
try {
$hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);
$activityId = $hsEngagement->data->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
} else {
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
$activityId = $hsEngagement->data->engagement->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
}
}
private function convertActivityMeetingAssociationsV4(Activity $activity): array
{
$associations = [];
$contactIds = $this->getParticipantsIds($activity);
foreach ($contactIds as $contactId) {
$associations[] = $this->buildAssociation(
(int) $contactId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]
);
}
if ($activity->hasAccount()) {
$crmProviderId = $activity->getAccount()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]
);
}
}
if ($activity->hasOpportunity()) {
$crmProviderId = $activity->getOpportunity()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]
);
}
}
return $associations;
}
private function buildAssociation(int $id, array $categoryToTypeIds): array
{
$types = [];
foreach ($categoryToTypeIds as $category => $typeId) {
$types[] = $this->buildAssociationType($category, $typeId);
}
return [
'to' => ['id' => (string) $id],
'types' => $types,
];
}
private function buildAssociationType(string $category, int $typeId): array
{
return [
'associationCategory' => $category,
'associationTypeId' => $typeId,
];
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createMeetingV3(array $engagement, array $associations, array $metadata): Response
{
$payload = $this->payloadBuilder->getV3MeetingPayload(
engagement: $engagement,
metadata: $metadata,
associations: $associations
);
return $this->getClient()->createMeeting($payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
private function buildSoftphonePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'CALL',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'toNumber' => $decorator->formatCallToParticipant($activity),
'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),
'status' => $decorator->convertCallStatus($activity),
'durationMilliseconds' => (int) $activity->getDuration() * 1000,
'body' => $decorator->generateDescription($activity),
];
// For now, this is just a "disposition" field to store Call Outcome.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildTextMessagePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'NOTE',
];
$metadata = [
'body' => $decorator->generateDescription($activity),
];
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function getActivityAssociations(Activity $activity): array
{
return [
'contactIds' => $this->getParticipantsIds($activity),
'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],
'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],
'ownerIds' => [],
];
}
/**
* Get DecorateActivity instance. Can be overridden in tests for mocking.
*/
protected function getDecorateActivity(): DecorateActivity
{
return new DecorateActivity();
}
public function saveFollowupActivity(Activity $activity, array $fields): ?string
{
if (empty($fields['subject'])) {
return null;
}
$associations = $this->getActivityAssociations($activity);
if (empty($fields['timestamp']) === false) {
$formatTimestamp = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['timestamp']);
$timestamp = $formatTimestamp->getTimestamp();
} else {
$timestamp = strtotime('+1 day');
}
$reminder = null;
if (empty($fields['reminder']) === false) {
$formatReminder = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['reminder']);
$reminder = $formatReminder->getTimestamp() * 1000;
}
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $timestamp * 1000,
'type' => 'TASK',
];
$metadata = [
'subject' => $fields['subject'],
'body' => '',
'status' => 'NOT_STARTED',
'taskType' => 'CALL',
'reminders' => $reminder ? [$reminder] : [],
];
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
} catch (BadRequest $e) {
$this->logger->info('[HubSpot] Failed to create followup task', [
'activity' => $activity->getUuid(),
'engagement' => $engagement,
'metadata' => $metadata,
'error' => $e->getMessage(),
]);
return null;
}
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
// We don't actually create a corresponding activity object on our side yet.
return (string) $hsEngagement->data->engagement->id;
}
private function logCrmEngagementManipulation(
string $action,
array $engagementData,
array $data = [],
array $associations = [],
): void {
$payload = ['team' => $this->team->getUuid()];
if ($action === self::ACTION_CREATE) {
$payload['engagementData'] = $engagementData;
$payload['data'] = $data;
$payload['associations'] = $associations;
}
if ($action === self::ACTION_UPDATE) {
$payload['engagementId'] = $engagementData['crmId'];
$payload['data'] = $data;
}
if ($action === self::ACTION_DELETE) {
$payload['engagementId'] = $engagementData['crmId'];
}
$this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
8
1
3
4
Previous Highlighted Error
Next Highlighted Error
SELECT
# DISTINCT
opp.id as opp_id, opp.uuid, opp.name,
# COUNT(dr.id) AS deal_risk_count,
# dr.id,
# cfd.value,
usr.name AS owner_name,
opp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,
usr.uuid as owner_uuid,
usr.photo_path as owner_photo,
usr.id AS owner_id,
jt.name as owner_job,
opp.stage_id, opp.stage_updated_at,
acc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,
rt.business_process_id AS pipeline_id
FROM opportunities opp
LEFT JOIN record_types rt ON opp.record_type_id = rt.id
LEFT JOIN users usr ON opp.user_id = usr.id
LEFT JOIN accounts acc ON opp.account_id = acc.id
LEFT JOIN job_titles jt ON usr.job_title_id = jt.id
# LEFT JOIN group_deal_risk_types gdrt ON gdrt.group_id = usr.group_id
# LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id AND dr.group_deal_risk_type_id = gdrt.id
LEFT JOIN crm_field_data cfd ON (cfd.object_id = opp.id and cfd.crm_field_id = 66814)
WHERE opp.user_id IS NOT NULL
AND opp.deleted_at IS NULL
AND opp.is_closed = 1
AND opp.is_won = 0
AND opp.close_date >= '2024-01-01 00:00:00'
AND opp.close_date <= '2024-12-31 23:59:59'
AND usr.team_id = 1
# and opp.id = 4823179
GROUP BY opp.id, cfd.value
ORDER BY
# cfd.value
CAST(cfd.value AS UNSIGNED)
# owner_name
# (SELECT COUNT(dr.id)
# FROM deal_risks dr
# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
# WHERE dr.opportunity_id = opp.id
# AND dr.is_active = 1
# AND gdrt.is_enabled = 1)
DESC
LIMIT 25
# OFFSET 0
;
select * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';
select * from crm_layout_entities where crm_layout_id = 1493;
SELECT * FROM opportunities WHERE id = 4584045;
SELECT * FROM crm_field_data WHERE object_id = 4584045;
SELECT * FROM opportunities where team_id = 1 and crm_provider_id = '0061K00000lfb9IQAQ'
SELECT
opp.id as opportunity_id,
opp.name,
COUNT(dr.id) as risk_count
FROM opportunities opp
LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id
WHERE opp.id IN (
6069574,8900938,9507638,9524799,9660490,9662824,9705675,9749758,4158460,4391812,6450439,4448422,5118945,5590675,4584045,7228149,9002408,9165534,9446720,9641778,9665149,9703344,9709280,5747948,4158491,6182565,6263970,5798120,5111315,4536978,6062352,6548383,6072095,6548225,4480986,5011422,6548381,8760540,8917554,9509986,9514392,9569009,9569011,9578490,9713604,5784397,7276612,7288405,8994421,9118219,9608148,9818911,4510535,6479693,5958049,6271674,6550448,4158331,4158483,6126571,6171615,6540943,4897466,5190896,5796182,5932762,8572433,8723698,8892697,9711001,9789264,4549188,6100831,6170064,6260799,6263653,6449936,6530871,6538978,7777651,8059269,8319918,8787049,8901150,9263153,9453207,9514738,9696700,9791062,5752018,6421452,7439134,7878923,9354763,9369285,9514396,9582506,9889949,9890216,4094311,4158495,4158496,6098128,5585661,3872564,6442149,5891604,6164746,6199593,6583474,6519684,9018490,9809006,4496897,5041324,5829430,6198319,6253504,6555763,7242914,7931055,8024125,8797814,8058559,8673347,8892695,8994420,9616219,9714970,9722004,9809439,9818918,6523177,8134147,9002915,9711422,9892713,9901719,9954210,9978435,5800810,6243518,6416114,6222251,6411974,6512456,5791953,6545606,9914780,5805540,6238986,6463838,6547680,9767049,9809437,9810885,9890855,6673493,9902036,4335521,6379871,6503799,6546077,8018765,9907556,9958433,9905855,9916179,9946741,9957877,5563344,6271838,6450815,7641128,7762567,7780592,8684810,8685786,8685787,8685788
)
GROUP BY opp.id;
SELECT COUNT(dr.id)
FROM deal_risks dr
JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
WHERE dr.opportunity_id = 5563344
# AND gdrt.group_id = usr.group_id
EXPLAIN SELECT
opp.id as opp_id, opp.uuid, opp.name,
cfd.value,
cfv.sequence,
cfv.value,
# MAX(cfd.value) AS max_cfd_value,
usr.name AS owner_name,
opp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,
usr.uuid as owner_uuid,
usr.photo_path as owner_photo,
usr.id AS owner_id,
jt.name as owner_job,
opp.stage_id, opp.stage_updated_at,
acc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,
rt.business_process_id AS pipeline_id
FROM opportunities opp
LEFT JOIN record_types rt ON opp.record_type_id = rt.id
LEFT JOIN users usr ON opp.user_id = usr.id
LEFT JOIN accounts acc ON opp.account_id = acc.id
LEFT JOIN job_titles jt ON usr.job_title_id = jt.id
LEFT JOIN crm_field_data cfd ON cfd.id = (SELECT sub_cfd.id
FROM crm_field_data sub_cfd
WHERE sub_cfd.object_id = opp.id
AND sub_cfd.crm_field_id = 66810
ORDER BY sub_cfd.updated_at DESC
LIMIT 1)
LEFT JOIN crm_field_values cfv ON cfv.crm_field_id = 66810 AND cfv.value = cfd.value
WHERE opp.user_id IS NOT NULL
AND opp.deleted_at IS NULL
AND opp.is_closed = 0
# AND opp.is_closed = 1
# AND opp.is_won = 0
AND opp.close_date >= '2024-01-01 00:00:00'
AND opp.close_date <= '2024-12-31 23:59:59'
AND usr.team_id = 1
# and opp.id = 4823179
# GROUP BY opp.id
ORDER BY
cfv.sequence DESC,
# opp.name
# owner_name
cfd.value
# CAST(cfd.value AS UNSIGNED)
# CAST(MAX(cfd.value) AS UNSIGNED)
# (SELECT COUNT(dr.id)
# FROM deal_risks dr
# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
# WHERE dr.opportunity_id = opp.id
# AND dr.is_active = 1 AND gdrt.is_enabled = 1)
DESC
# LIMIT 75, 25
;
SELECT * FROM crm_field_values WHERE crm_field_id = 66810;
SELECT f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value, fd.updated_at, fd.id, fd.created_at
FROM crm_fields f
INNER JOIN crm_field_data fd ON fd.crm_field_id = f.id
WHERE (f.crm_configuration_id = 1)
AND (f.object_type = 'opportunity')
# AND (fd.object_id IN (4158331,4158483,4158495,4158496,6069574,6263970,6171615,6540943,6545606,5829430,6198319,6263653,6548381,7242914,8797814,7228149,7439134,7878923,8134147,9002915,9118219,9165534,9354763,9369285,9446720))
AND (fd.object_id IN (4158460))
AND (f.crm_provider_id IN ('ForecastCategoryName'))
ORDER BY fd.object_id ASC, fd.updated_at DESC;
select * from crm_layouts where crm_configuration_id = 1;
select cf.* from crm_fields cf
join crm_layout_entities cle on cf.id = cle.crm_field_id
where crm_layout_id = 1493;
SELECT * FROM opportunities WHERE id = 4158460;
SELECT * FROM crm_field_data WHERE object_id = 4158460;
select * from users where team_id = 1;
SELECT * FROM users WHERE id = 7160;
select * from role_user where user_id = 3248;
select * from crm_field_values where crm_field_id = 66824;
SELECT * FROM users WHERE id = 23470;
select * from crm_configurations;
# [PASSWORD_DOTS]
select * from teams where id = 1038; # 23521, 966
select * from users where team_id = 1038;
select * from crm_configurations where id = 966;
SELECT * FROM activities WHERE uuid_to_bin('e8dc7c34-31f6-4430-bd81-2d9d8f00dd07') = uuid; # 54965762
SELECT * FROM activities WHERE uuid_to_bin('4b5727c0-69d1-428f-b8d9-b649055166e2') = uuid; # 54965764
SELECT * FROM participants WHERE activity_id = 54965764;
SELECT id, uuid, title, recording_state FROM activities WHERE uuid_to_bin('355f105d-5606-4379-b0c5-d91daf59f4ce') = uuid; # 54964075
SELECT * FROM participants WHERE activity_id = 54964075;
select f.id, slug, title, tf.team_id from team_features tf JOIN features f on tf.feature_id = f.id
where tf.team_id = 1038;
# [PASSWORD_DOTS] PD [PASSWORD_DOTS]
select * from teams where id = 1029;
SELECT * FROM crm_configurations WHERE id = 957;
SELECT * FROM activities WHERE uuid_to_bin('581a33cb-4343-44bd-ac0c-4a5cee71c5ec') = uuid; # 54963423
SELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433
SELECT * FROM activities WHERE uuid_to_bin('8b6bb2f7-e7a7-4aec-8806-fbfea7093ccd') = uuid; # 54963421
# [PASSWORD_DOTS] Close [PASSWORD_DOTS]
select * from teams where id = 1031;
SELECT * FROM crm_configurations WHERE id = 959;
SELECT * FROM activities WHERE uuid_to_bin('59c517e0-96c9-4dad-bfeb-b7bef72f8725') = uuid; # 54963517
SELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433
SELECT * FROM activities WHERE uuid_to_bin('d68311e3-ac34-49bd-bf04-88323a7e5352') = uuid; # 54966009
# [PASSWORD_DOTS] SF [PASSWORD_DOTS]
SELECT * FROM activities WHERE uuid_to_bin('2bd4cfa3-4eb5-4c77-84a7-9ed46a21c988') = uuid;
select * from roles;
select * from permissions;
select * from permission_role where permission_id = 136;
select * from migrations order by id desc;
select * from teams where id IN (1, 1037);
select * from crm_layouts where crm_configuration_id = 1;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 1493;
SELECT * FROM crm_fields WHERE id IN (1652,1661,66799,66814,66821,66836,66843,66846,66864,84752,182306,323580,380378);
select * from features;
select * from team_features where feature_id = 33;
select * from opportunities;
select * from teams;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1052 and sa.provider = 'hubspot';
SELECT * FROM accounts where id = 11512582;
select * from activities where account_id = 11512582;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
45181
|
NULL
|
NULL
|
NULL
|
|
45185
|
1622
|
16
|
2026-05-14T14:24:05.350072+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768645350_m2.jpg...
|
PhpStorm
|
faVsco.js – WriteCrmTrait.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
8
6
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldDataRepository;
use Jiminny\Services\Crm\Hubspot\DecorateActivity;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationCategoryV4;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationTypeIdV4;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Http\Response;
use SevenShores\Hubspot\Exceptions\HubspotException;
use Sentry;
trait WriteCrmTrait
{
private const string ACTION_CREATE = 'create';
private const string ACTION_UPDATE = 'update';
private const string ACTION_DELETE = 'delete';
/**
* @inheritdoc
*/
public function saveActivity(Activity $activity): Activity
{
switch ($activity->getCrmType()) {
case Activity::TYPE_CONFERENCE:
$activity = $this->buildConferencePayload($activity);
break;
case Activity::TYPE_SOFTPHONE:
case Activity::TYPE_SOFTPHONE_INBOUND:
$activity = $this->buildSoftphonePayload($activity);
break;
case Activity::TYPE_SMS_INBOUND:
case Activity::TYPE_SMS_OUTBOUND:
$activity = $this->buildTextMessagePayload($activity);
break;
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildConferencePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatStartTime($activity),
'type' => 'MEETING',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'meetingOutcome' => $decorator->convertMeetingOutcome($activity),
'internalMeetingNotes' => $decorator->generateDescription($activity),
];
// Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.
if (! $activity->hasCrmProviderId()) {
if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {
$metadata['startTime'] = $decorator->formatStartTime($activity);
$metadata['endTime'] = $decorator->formatEndTime($activity);
}
$metadata['title'] = $decorator->generateTitle($activity);
}
// For now, this is just a "meetingOutcome" field for some customers.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function fetchCustomFieldData(Activity $activity, string $objectType): array
{
$payload = [];
$fieldDataRepository = app(FieldDataRepository::class);
$fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);
foreach ($fieldData as $data) {
// Add the field and value to the payload.
$payload += [
$data->getField()->getCrmProviderId() => $data->getValue(),
];
}
return $payload;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity
{
if (! $activity->hasCrmProviderId()) {
$this->createActivity($activity, $engagement, $metadata);
return $activity;
}
try {
$oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());
} catch (HubspotException $e) {
$this->logger->warning('[HubSpot] Engagement not found', [
'activity_id' => $activity->getUuid(),
'crm_provider_id' => $activity->getCrmProviderId(),
'exception' => $e->getMessage(),
]);
// potentially here we can log new activity if it is not found
return $activity;
}
$type = $oldEngagement['engagement']['type'] ?? null;
$bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';
$decorator = $this->getDecorateActivity();
if ($type !== 'TASK') {
$oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldDescription
);
$oldAssociations = $oldEngagement['associations'] ?? [];
$this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);
} else {
$oldTaskBody = $oldEngagement['metadata']['body'] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldTaskBody
);
$this->updateTask($activity, $engagement, $metadata);
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateNonTaskActivity(
Activity $activity,
array $engagement,
array $metadata,
array $oldAssociations
): void {
if ($activity->isTypeConference()) {
$this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);
} else {
$this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);
}
$this->updateActivityAssociations($activity, $oldAssociations);
$this->logCrmEngagementManipulation(
self::ACTION_UPDATE,
['crmId' => $activity->getCrmProviderId()],
$metadata,
);
}
private function updateActivityAssociations(Activity $activity, array $oldAssociations): void
{
$currentAssociations = $this->getActivityAssociations($activity);
$associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);
// No changes - exit
if (empty($associationChanges)) {
return;
}
$this->logger->info('[HubSpot] Updating associations', [
'team' => $this->team->getUuid(),
'activity' => $activity->getUuid(),
'associationChanges' => $associationChanges,
]);
$this->updateAssociations($activity, $associationChanges);
}
private function getAssociationDifferences(
Activity $activity,
array $oldAssociations,
array $currentAssociations
): array {
$objects = ['contactIds', 'companyIds', 'dealIds'];
$changes = [];
foreach ($objects as $object) {
$oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);
$currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);
$added = array_diff($currentIds, $oldIds);
$removed = array_diff($oldIds, $currentIds);
$type = match ($object) {
'contactIds' => 'contact',
'companyIds' => 'company',
'dealIds' => 'deal',
};
$shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);
if (! empty($removed) && ! $shouldIncludeRemoved) {
$this->logger->info('[HubSpot] Skipping association removal', [
'team' => $this->team->getUuid(),
'type' => $type,
'oldIds' => $oldIds,
'currentIds' => $currentIds,
'skippedRemovals' => array_values($removed),
]);
}
if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {
$changes[$type] = [
'added' => array_values($added),
'removed' => array_values($removed),
];
}
}
return $changes;
}
/**
* Determines whether removed associations should be synced to HubSpot.
*
* For contacts and companies, we always sync removals (original behavior).
* For deals, we only sync removals when:
* - There are new deals being added (replacement scenario), OR
* - The activity has an account associated (indicating intentional association management)
*
* This prevents accidentally removing deal associations that exist in HubSpot
* but not in Jiminny's database (e.g., deals manually added by users in HubSpot).
*/
private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool
{
// Contacts and companies: always sync removals
if ($type !== 'deal') {
return true;
}
// Deals: sync removals if we're adding new deals (replacement)
if (! empty($added)) {
return true;
}
// Deals: sync removals only if activity has an account (intentional management)
return $activity->hasAccount();
}
private function normalizeIds(array $ids): array
{
$stringIds = array_map('strval', $ids);
$uniqueIds = array_unique($stringIds);
sort($uniqueIds);
return $uniqueIds;
}
private function updateAssociations(Activity $activity, array $associationChanges): void
{
$objectType = $activity->isTypeConference() ? 'meeting' : 'call';
foreach ($associationChanges as $type => $changes) {
if (! empty($changes['added'])) {
$associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);
$payload = $this->payloadBuilder->buildAddAssociationPayload(
$activity->getCrmProviderId(),
$changes['added'],
$associationType
);
$this->getClient()->addAssociations($objectType, $type, $payload);
}
if (! empty($changes['removed'])) {
$payload = $this->payloadBuilder->buildRemoveAssociationPayload(
$activity->getCrmProviderId(),
$changes['removed']
);
$this->getClient()->removeAssociations($objectType, $type, $payload);
}
}
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response
{
// If already set by us/client, do not override it
if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {
$this->logger->info('[HubSpot] Meeting activity type already set', [
'meetingId' => $meetingId,
'activityType' => $engagement['activityType'],
]);
unset($engagement['activityType']);
}
$payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);
return $this->getClient()->updateMeeting($meetingId, $payload);
}
private function hasEngagementActivityType(string $engagementId): bool
{
try {
$engagementData = $this->getClient()->getEngagementData($engagementId);
if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {
return true;
}
return false;
} catch (\Exception $exception) {
Sentry::captureException($exception);
$this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [
'engagementId' => $engagementId,
'exception' => $exception->getMessage(),
]);
}
return true;
}
private function updateTask(Activity $activity, array $engagement, array $metadata): void
{
$this->getClient()->deleteEngagement($activity->getCrmProviderId());
$this->logCrmEngagementManipulation(
self::ACTION_DELETE,
['crmId' => $activity->getCrmProviderId()],
);
$this->createActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createActivity(Activity $activity, array $engagement, array $metadata): void
{
$associations = $this->getActivityAssociations($activity);
$associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);
if ($activity->isTypeConference()) {
try {
$hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);
$activityId = $hsEngagement->data->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
} else {
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
$activityId = $hsEngagement->data->engagement->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
}
}
private function convertActivityMeetingAssociationsV4(Activity $activity): array
{
$associations = [];
$contactIds = $this->getParticipantsIds($activity);
foreach ($contactIds as $contactId) {
$associations[] = $this->buildAssociation(
(int) $contactId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]
);
}
if ($activity->hasAccount()) {
$crmProviderId = $activity->getAccount()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]
);
}
}
if ($activity->hasOpportunity()) {
$crmProviderId = $activity->getOpportunity()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]
);
}
}
return $associations;
}
private function buildAssociation(int $id, array $categoryToTypeIds): array
{
$types = [];
foreach ($categoryToTypeIds as $category => $typeId) {
$types[] = $this->buildAssociationType($category, $typeId);
}
return [
'to' => ['id' => (string) $id],
'types' => $types,
];
}
private function buildAssociationType(string $category, int $typeId): array
{
return [
'associationCategory' => $category,
'associationTypeId' => $typeId,
];
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createMeetingV3(array $engagement, array $associations, array $metadata): Response
{
$payload = $this->payloadBuilder->getV3MeetingPayload(
engagement: $engagement,
metadata: $metadata,
associations: $associations
);
return $this->getClient()->createMeeting($payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
private function buildSoftphonePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'CALL',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'toNumber' => $decorator->formatCallToParticipant($activity),
'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),
'status' => $decorator->convertCallStatus($activity),
'durationMilliseconds' => (int) $activity->getDuration() * 1000,
'body' => $decorator->generateDescription($activity),
];
// For now, this is just a "disposition" field to store Call Outcome.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildTextMessagePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'NOTE',
];
$metadata = [
'body' => $decorator->generateDescription($activity),
];
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function getActivityAssociations(Activity $activity): array
{
return [
'contactIds' => $this->getParticipantsIds($activity),
'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],
'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],
'ownerIds' => [],
];
}
/**
* Get DecorateActivity instance. Can be overridden in tests for mocking.
*/
protected function getDecorateActivity(): DecorateActivity
{
return new DecorateActivity();
}
public function saveFollowupActivity(Activity $activity, array $fields): ?string
{
if (empty($fields['subject'])) {
return null;
}
$associations = $this->getActivityAssociations($activity);
if (empty($fields['timestamp']) === false) {
$formatTimestamp = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['timestamp']);
$timestamp = $formatTimestamp->getTimestamp();
} else {
$timestamp = strtotime('+1 day');
}
$reminder = null;
if (empty($fields['reminder']) === false) {
$formatReminder = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['reminder']);
$reminder = $formatReminder->getTimestamp() * 1000;
}
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $timestamp * 1000,
'type' => 'TASK',
];
$metadata = [
'subject' => $fields['subject'],
'body' => '',
'status' => 'NOT_STARTED',
'taskType' => 'CALL',
'reminders' => $reminder ? [$reminder] : [],
];
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
} catch (BadRequest $e) {
$this->logger->info('[HubSpot] Failed to create followup task', [
'activity' => $activity->getUuid(),
'engagement' => $engagement,
'metadata' => $metadata,
'error' => $e->getMessage(),
]);
return null;
}
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
// We don't actually create a corresponding activity object on our side yet.
return (string) $hsEngagement->data->engagement->id;
}
private function logCrmEngagementManipulation(
string $action,
array $engagementData,
array $data = [],
array $associations = [],
): void {
$payload = ['team' => $this->team->getUuid()];
if ($action === self::ACTION_CREATE) {
$payload['engagementData'] = $engagementData;
$payload['data'] = $data;
$payload['associations'] = $associations;
}
if ($action === self::ACTION_UPDATE) {
$payload['engagementId'] = $engagementData['crmId'];
$payload['data'] = $data;
}
if ($action === self::ACTION_DELETE) {
$payload['engagementId'] = $engagementData['crmId'];
}
$this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.2962101,"top":1.0,"width":0.03856383,"height":-0.019952059},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#12066 on JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.33477393,"top":1.0,"width":0.122340426,"height":-0.019952059},"on_screen":true,"help_text":"Pull request #12066 exists for current branch JY-20725-handle-HS-search-rate-limit, 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.5731383,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HandleHubspotRateLimitTest","depth":6,"bounds":{"left":0.5884308,"top":1.0,"width":0.076130316,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.66456115,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.67586434,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.6871675,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.7150931,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.72639626,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.73769945,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"8","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldDataRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DecorateActivity;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationCategoryV4;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationTypeIdV4;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse Sentry;\n\ntrait WriteCrmTrait\n{\n private const string ACTION_CREATE = 'create';\n private const string ACTION_UPDATE = 'update';\n private const string ACTION_DELETE = 'delete';\n\n /**\n * @inheritdoc\n */\n public function saveActivity(Activity $activity): Activity\n {\n switch ($activity->getCrmType()) {\n case Activity::TYPE_CONFERENCE:\n $activity = $this->buildConferencePayload($activity);\n\n break;\n case Activity::TYPE_SOFTPHONE:\n case Activity::TYPE_SOFTPHONE_INBOUND:\n $activity = $this->buildSoftphonePayload($activity);\n\n break;\n\n case Activity::TYPE_SMS_INBOUND:\n case Activity::TYPE_SMS_OUTBOUND:\n $activity = $this->buildTextMessagePayload($activity);\n\n break;\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildConferencePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatStartTime($activity),\n 'type' => 'MEETING',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'meetingOutcome' => $decorator->convertMeetingOutcome($activity),\n 'internalMeetingNotes' => $decorator->generateDescription($activity),\n ];\n\n // Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.\n if (! $activity->hasCrmProviderId()) {\n if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {\n $metadata['startTime'] = $decorator->formatStartTime($activity);\n $metadata['endTime'] = $decorator->formatEndTime($activity);\n }\n $metadata['title'] = $decorator->generateTitle($activity);\n }\n\n // For now, this is just a \"meetingOutcome\" field for some customers.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function fetchCustomFieldData(Activity $activity, string $objectType): array\n {\n $payload = [];\n\n $fieldDataRepository = app(FieldDataRepository::class);\n $fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);\n\n foreach ($fieldData as $data) {\n // Add the field and value to the payload.\n $payload += [\n $data->getField()->getCrmProviderId() => $data->getValue(),\n ];\n }\n\n return $payload;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity\n {\n if (! $activity->hasCrmProviderId()) {\n $this->createActivity($activity, $engagement, $metadata);\n\n return $activity;\n }\n\n try {\n $oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());\n } catch (HubspotException $e) {\n $this->logger->warning('[HubSpot] Engagement not found', [\n 'activity_id' => $activity->getUuid(),\n 'crm_provider_id' => $activity->getCrmProviderId(),\n 'exception' => $e->getMessage(),\n ]);\n\n // potentially here we can log new activity if it is not found\n\n return $activity;\n }\n\n $type = $oldEngagement['engagement']['type'] ?? null;\n $bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';\n\n $decorator = $this->getDecorateActivity();\n\n if ($type !== 'TASK') {\n $oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldDescription\n );\n\n $oldAssociations = $oldEngagement['associations'] ?? [];\n $this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);\n } else {\n $oldTaskBody = $oldEngagement['metadata']['body'] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldTaskBody\n );\n $this->updateTask($activity, $engagement, $metadata);\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateNonTaskActivity(\n Activity $activity,\n array $engagement,\n array $metadata,\n array $oldAssociations\n ): void {\n if ($activity->isTypeConference()) {\n $this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);\n } else {\n $this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);\n }\n\n $this->updateActivityAssociations($activity, $oldAssociations);\n\n $this->logCrmEngagementManipulation(\n self::ACTION_UPDATE,\n ['crmId' => $activity->getCrmProviderId()],\n $metadata,\n );\n }\n\n private function updateActivityAssociations(Activity $activity, array $oldAssociations): void\n {\n $currentAssociations = $this->getActivityAssociations($activity);\n $associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);\n\n // No changes - exit\n if (empty($associationChanges)) {\n return;\n }\n\n $this->logger->info('[HubSpot] Updating associations', [\n 'team' => $this->team->getUuid(),\n 'activity' => $activity->getUuid(),\n 'associationChanges' => $associationChanges,\n ]);\n\n $this->updateAssociations($activity, $associationChanges);\n }\n\n private function getAssociationDifferences(\n Activity $activity,\n array $oldAssociations,\n array $currentAssociations\n ): array {\n $objects = ['contactIds', 'companyIds', 'dealIds'];\n $changes = [];\n\n foreach ($objects as $object) {\n $oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);\n $currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);\n\n $added = array_diff($currentIds, $oldIds);\n $removed = array_diff($oldIds, $currentIds);\n\n $type = match ($object) {\n 'contactIds' => 'contact',\n 'companyIds' => 'company',\n 'dealIds' => 'deal',\n };\n\n $shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);\n\n if (! empty($removed) && ! $shouldIncludeRemoved) {\n $this->logger->info('[HubSpot] Skipping association removal', [\n 'team' => $this->team->getUuid(),\n 'type' => $type,\n 'oldIds' => $oldIds,\n 'currentIds' => $currentIds,\n 'skippedRemovals' => array_values($removed),\n ]);\n }\n\n if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {\n $changes[$type] = [\n 'added' => array_values($added),\n 'removed' => array_values($removed),\n ];\n }\n }\n\n return $changes;\n }\n\n /**\n * Determines whether removed associations should be synced to HubSpot.\n *\n * For contacts and companies, we always sync removals (original behavior).\n * For deals, we only sync removals when:\n * - There are new deals being added (replacement scenario), OR\n * - The activity has an account associated (indicating intentional association management)\n *\n * This prevents accidentally removing deal associations that exist in HubSpot\n * but not in Jiminny's database (e.g., deals manually added by users in HubSpot).\n */\n private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool\n {\n // Contacts and companies: always sync removals\n if ($type !== 'deal') {\n return true;\n }\n\n // Deals: sync removals if we're adding new deals (replacement)\n if (! empty($added)) {\n return true;\n }\n\n // Deals: sync removals only if activity has an account (intentional management)\n return $activity->hasAccount();\n }\n\n private function normalizeIds(array $ids): array\n {\n $stringIds = array_map('strval', $ids);\n $uniqueIds = array_unique($stringIds);\n sort($uniqueIds);\n\n return $uniqueIds;\n }\n\n private function updateAssociations(Activity $activity, array $associationChanges): void\n {\n $objectType = $activity->isTypeConference() ? 'meeting' : 'call';\n\n foreach ($associationChanges as $type => $changes) {\n if (! empty($changes['added'])) {\n $associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);\n $payload = $this->payloadBuilder->buildAddAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['added'],\n $associationType\n );\n $this->getClient()->addAssociations($objectType, $type, $payload);\n }\n\n if (! empty($changes['removed'])) {\n $payload = $this->payloadBuilder->buildRemoveAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['removed']\n );\n $this->getClient()->removeAssociations($objectType, $type, $payload);\n }\n }\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response\n {\n // If already set by us/client, do not override it\n if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {\n $this->logger->info('[HubSpot] Meeting activity type already set', [\n 'meetingId' => $meetingId,\n 'activityType' => $engagement['activityType'],\n ]);\n\n unset($engagement['activityType']);\n }\n\n $payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);\n\n return $this->getClient()->updateMeeting($meetingId, $payload);\n }\n\n private function hasEngagementActivityType(string $engagementId): bool\n {\n try {\n $engagementData = $this->getClient()->getEngagementData($engagementId);\n if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {\n return true;\n }\n\n return false;\n } catch (\\Exception $exception) {\n Sentry::captureException($exception);\n\n $this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [\n 'engagementId' => $engagementId,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n return true;\n }\n\n private function updateTask(Activity $activity, array $engagement, array $metadata): void\n {\n $this->getClient()->deleteEngagement($activity->getCrmProviderId());\n\n $this->logCrmEngagementManipulation(\n self::ACTION_DELETE,\n ['crmId' => $activity->getCrmProviderId()],\n );\n\n $this->createActivity($activity, $engagement, $metadata);\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createActivity(Activity $activity, array $engagement, array $metadata): void\n {\n $associations = $this->getActivityAssociations($activity);\n $associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);\n\n if ($activity->isTypeConference()) {\n try {\n $hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);\n $activityId = $hsEngagement->data->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n } else {\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n $activityId = $hsEngagement->data->engagement->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n }\n }\n\n private function convertActivityMeetingAssociationsV4(Activity $activity): array\n {\n $associations = [];\n\n $contactIds = $this->getParticipantsIds($activity);\n\n foreach ($contactIds as $contactId) {\n $associations[] = $this->buildAssociation(\n (int) $contactId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]\n );\n }\n\n if ($activity->hasAccount()) {\n $crmProviderId = $activity->getAccount()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]\n );\n }\n }\n\n if ($activity->hasOpportunity()) {\n $crmProviderId = $activity->getOpportunity()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]\n );\n }\n }\n\n return $associations;\n }\n\n private function buildAssociation(int $id, array $categoryToTypeIds): array\n {\n $types = [];\n\n foreach ($categoryToTypeIds as $category => $typeId) {\n $types[] = $this->buildAssociationType($category, $typeId);\n }\n\n return [\n 'to' => ['id' => (string) $id],\n 'types' => $types,\n ];\n }\n\n private function buildAssociationType(string $category, int $typeId): array\n {\n return [\n 'associationCategory' => $category,\n 'associationTypeId' => $typeId,\n ];\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createMeetingV3(array $engagement, array $associations, array $metadata): Response\n {\n $payload = $this->payloadBuilder->getV3MeetingPayload(\n engagement: $engagement,\n metadata: $metadata,\n associations: $associations\n );\n\n return $this->getClient()->createMeeting($payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n private function buildSoftphonePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'CALL',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'toNumber' => $decorator->formatCallToParticipant($activity),\n 'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),\n 'status' => $decorator->convertCallStatus($activity),\n 'durationMilliseconds' => (int) $activity->getDuration() * 1000,\n 'body' => $decorator->generateDescription($activity),\n ];\n\n // For now, this is just a \"disposition\" field to store Call Outcome.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildTextMessagePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'NOTE',\n ];\n\n $metadata = [\n 'body' => $decorator->generateDescription($activity),\n ];\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function getActivityAssociations(Activity $activity): array\n {\n return [\n 'contactIds' => $this->getParticipantsIds($activity),\n 'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],\n 'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],\n 'ownerIds' => [],\n ];\n }\n\n /**\n * Get DecorateActivity instance. Can be overridden in tests for mocking.\n */\n protected function getDecorateActivity(): DecorateActivity\n {\n return new DecorateActivity();\n }\n\n public function saveFollowupActivity(Activity $activity, array $fields): ?string\n {\n if (empty($fields['subject'])) {\n return null;\n }\n\n $associations = $this->getActivityAssociations($activity);\n\n if (empty($fields['timestamp']) === false) {\n $formatTimestamp = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['timestamp']);\n $timestamp = $formatTimestamp->getTimestamp();\n } else {\n $timestamp = strtotime('+1 day');\n }\n\n $reminder = null;\n if (empty($fields['reminder']) === false) {\n $formatReminder = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['reminder']);\n $reminder = $formatReminder->getTimestamp() * 1000;\n }\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $timestamp * 1000,\n 'type' => 'TASK',\n ];\n\n $metadata = [\n 'subject' => $fields['subject'],\n 'body' => '',\n 'status' => 'NOT_STARTED',\n 'taskType' => 'CALL',\n 'reminders' => $reminder ? [$reminder] : [],\n ];\n\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n } catch (BadRequest $e) {\n $this->logger->info('[HubSpot] Failed to create followup task', [\n 'activity' => $activity->getUuid(),\n 'engagement' => $engagement,\n 'metadata' => $metadata,\n 'error' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n // We don't actually create a corresponding activity object on our side yet.\n return (string) $hsEngagement->data->engagement->id;\n }\n\n private function logCrmEngagementManipulation(\n string $action,\n array $engagementData,\n array $data = [],\n array $associations = [],\n ): void {\n $payload = ['team' => $this->team->getUuid()];\n\n if ($action === self::ACTION_CREATE) {\n $payload['engagementData'] = $engagementData;\n $payload['data'] = $data;\n $payload['associations'] = $associations;\n }\n\n if ($action === self::ACTION_UPDATE) {\n $payload['engagementId'] = $engagementData['crmId'];\n $payload['data'] = $data;\n }\n\n if ($action === self::ACTION_DELETE) {\n $payload['engagementId'] = $engagementData['crmId'];\n }\n\n $this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);\n }\n}","depth":4,"bounds":{"left":0.39261967,"top":0.14844373,"width":0.31615692,"height":0.8515563},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldDataRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DecorateActivity;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationCategoryV4;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationTypeIdV4;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse Sentry;\n\ntrait WriteCrmTrait\n{\n private const string ACTION_CREATE = 'create';\n private const string ACTION_UPDATE = 'update';\n private const string ACTION_DELETE = 'delete';\n\n /**\n * @inheritdoc\n */\n public function saveActivity(Activity $activity): Activity\n {\n switch ($activity->getCrmType()) {\n case Activity::TYPE_CONFERENCE:\n $activity = $this->buildConferencePayload($activity);\n\n break;\n case Activity::TYPE_SOFTPHONE:\n case Activity::TYPE_SOFTPHONE_INBOUND:\n $activity = $this->buildSoftphonePayload($activity);\n\n break;\n\n case Activity::TYPE_SMS_INBOUND:\n case Activity::TYPE_SMS_OUTBOUND:\n $activity = $this->buildTextMessagePayload($activity);\n\n break;\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildConferencePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatStartTime($activity),\n 'type' => 'MEETING',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'meetingOutcome' => $decorator->convertMeetingOutcome($activity),\n 'internalMeetingNotes' => $decorator->generateDescription($activity),\n ];\n\n // Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.\n if (! $activity->hasCrmProviderId()) {\n if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {\n $metadata['startTime'] = $decorator->formatStartTime($activity);\n $metadata['endTime'] = $decorator->formatEndTime($activity);\n }\n $metadata['title'] = $decorator->generateTitle($activity);\n }\n\n // For now, this is just a \"meetingOutcome\" field for some customers.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function fetchCustomFieldData(Activity $activity, string $objectType): array\n {\n $payload = [];\n\n $fieldDataRepository = app(FieldDataRepository::class);\n $fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);\n\n foreach ($fieldData as $data) {\n // Add the field and value to the payload.\n $payload += [\n $data->getField()->getCrmProviderId() => $data->getValue(),\n ];\n }\n\n return $payload;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity\n {\n if (! $activity->hasCrmProviderId()) {\n $this->createActivity($activity, $engagement, $metadata);\n\n return $activity;\n }\n\n try {\n $oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());\n } catch (HubspotException $e) {\n $this->logger->warning('[HubSpot] Engagement not found', [\n 'activity_id' => $activity->getUuid(),\n 'crm_provider_id' => $activity->getCrmProviderId(),\n 'exception' => $e->getMessage(),\n ]);\n\n // potentially here we can log new activity if it is not found\n\n return $activity;\n }\n\n $type = $oldEngagement['engagement']['type'] ?? null;\n $bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';\n\n $decorator = $this->getDecorateActivity();\n\n if ($type !== 'TASK') {\n $oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldDescription\n );\n\n $oldAssociations = $oldEngagement['associations'] ?? [];\n $this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);\n } else {\n $oldTaskBody = $oldEngagement['metadata']['body'] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldTaskBody\n );\n $this->updateTask($activity, $engagement, $metadata);\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateNonTaskActivity(\n Activity $activity,\n array $engagement,\n array $metadata,\n array $oldAssociations\n ): void {\n if ($activity->isTypeConference()) {\n $this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);\n } else {\n $this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);\n }\n\n $this->updateActivityAssociations($activity, $oldAssociations);\n\n $this->logCrmEngagementManipulation(\n self::ACTION_UPDATE,\n ['crmId' => $activity->getCrmProviderId()],\n $metadata,\n );\n }\n\n private function updateActivityAssociations(Activity $activity, array $oldAssociations): void\n {\n $currentAssociations = $this->getActivityAssociations($activity);\n $associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);\n\n // No changes - exit\n if (empty($associationChanges)) {\n return;\n }\n\n $this->logger->info('[HubSpot] Updating associations', [\n 'team' => $this->team->getUuid(),\n 'activity' => $activity->getUuid(),\n 'associationChanges' => $associationChanges,\n ]);\n\n $this->updateAssociations($activity, $associationChanges);\n }\n\n private function getAssociationDifferences(\n Activity $activity,\n array $oldAssociations,\n array $currentAssociations\n ): array {\n $objects = ['contactIds', 'companyIds', 'dealIds'];\n $changes = [];\n\n foreach ($objects as $object) {\n $oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);\n $currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);\n\n $added = array_diff($currentIds, $oldIds);\n $removed = array_diff($oldIds, $currentIds);\n\n $type = match ($object) {\n 'contactIds' => 'contact',\n 'companyIds' => 'company',\n 'dealIds' => 'deal',\n };\n\n $shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);\n\n if (! empty($removed) && ! $shouldIncludeRemoved) {\n $this->logger->info('[HubSpot] Skipping association removal', [\n 'team' => $this->team->getUuid(),\n 'type' => $type,\n 'oldIds' => $oldIds,\n 'currentIds' => $currentIds,\n 'skippedRemovals' => array_values($removed),\n ]);\n }\n\n if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {\n $changes[$type] = [\n 'added' => array_values($added),\n 'removed' => array_values($removed),\n ];\n }\n }\n\n return $changes;\n }\n\n /**\n * Determines whether removed associations should be synced to HubSpot.\n *\n * For contacts and companies, we always sync removals (original behavior).\n * For deals, we only sync removals when:\n * - There are new deals being added (replacement scenario), OR\n * - The activity has an account associated (indicating intentional association management)\n *\n * This prevents accidentally removing deal associations that exist in HubSpot\n * but not in Jiminny's database (e.g., deals manually added by users in HubSpot).\n */\n private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool\n {\n // Contacts and companies: always sync removals\n if ($type !== 'deal') {\n return true;\n }\n\n // Deals: sync removals if we're adding new deals (replacement)\n if (! empty($added)) {\n return true;\n }\n\n // Deals: sync removals only if activity has an account (intentional management)\n return $activity->hasAccount();\n }\n\n private function normalizeIds(array $ids): array\n {\n $stringIds = array_map('strval', $ids);\n $uniqueIds = array_unique($stringIds);\n sort($uniqueIds);\n\n return $uniqueIds;\n }\n\n private function updateAssociations(Activity $activity, array $associationChanges): void\n {\n $objectType = $activity->isTypeConference() ? 'meeting' : 'call';\n\n foreach ($associationChanges as $type => $changes) {\n if (! empty($changes['added'])) {\n $associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);\n $payload = $this->payloadBuilder->buildAddAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['added'],\n $associationType\n );\n $this->getClient()->addAssociations($objectType, $type, $payload);\n }\n\n if (! empty($changes['removed'])) {\n $payload = $this->payloadBuilder->buildRemoveAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['removed']\n );\n $this->getClient()->removeAssociations($objectType, $type, $payload);\n }\n }\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response\n {\n // If already set by us/client, do not override it\n if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {\n $this->logger->info('[HubSpot] Meeting activity type already set', [\n 'meetingId' => $meetingId,\n 'activityType' => $engagement['activityType'],\n ]);\n\n unset($engagement['activityType']);\n }\n\n $payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);\n\n return $this->getClient()->updateMeeting($meetingId, $payload);\n }\n\n private function hasEngagementActivityType(string $engagementId): bool\n {\n try {\n $engagementData = $this->getClient()->getEngagementData($engagementId);\n if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {\n return true;\n }\n\n return false;\n } catch (\\Exception $exception) {\n Sentry::captureException($exception);\n\n $this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [\n 'engagementId' => $engagementId,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n return true;\n }\n\n private function updateTask(Activity $activity, array $engagement, array $metadata): void\n {\n $this->getClient()->deleteEngagement($activity->getCrmProviderId());\n\n $this->logCrmEngagementManipulation(\n self::ACTION_DELETE,\n ['crmId' => $activity->getCrmProviderId()],\n );\n\n $this->createActivity($activity, $engagement, $metadata);\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createActivity(Activity $activity, array $engagement, array $metadata): void\n {\n $associations = $this->getActivityAssociations($activity);\n $associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);\n\n if ($activity->isTypeConference()) {\n try {\n $hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);\n $activityId = $hsEngagement->data->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n } else {\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n $activityId = $hsEngagement->data->engagement->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n }\n }\n\n private function convertActivityMeetingAssociationsV4(Activity $activity): array\n {\n $associations = [];\n\n $contactIds = $this->getParticipantsIds($activity);\n\n foreach ($contactIds as $contactId) {\n $associations[] = $this->buildAssociation(\n (int) $contactId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]\n );\n }\n\n if ($activity->hasAccount()) {\n $crmProviderId = $activity->getAccount()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]\n );\n }\n }\n\n if ($activity->hasOpportunity()) {\n $crmProviderId = $activity->getOpportunity()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]\n );\n }\n }\n\n return $associations;\n }\n\n private function buildAssociation(int $id, array $categoryToTypeIds): array\n {\n $types = [];\n\n foreach ($categoryToTypeIds as $category => $typeId) {\n $types[] = $this->buildAssociationType($category, $typeId);\n }\n\n return [\n 'to' => ['id' => (string) $id],\n 'types' => $types,\n ];\n }\n\n private function buildAssociationType(string $category, int $typeId): array\n {\n return [\n 'associationCategory' => $category,\n 'associationTypeId' => $typeId,\n ];\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createMeetingV3(array $engagement, array $associations, array $metadata): Response\n {\n $payload = $this->payloadBuilder->getV3MeetingPayload(\n engagement: $engagement,\n metadata: $metadata,\n associations: $associations\n );\n\n return $this->getClient()->createMeeting($payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n private function buildSoftphonePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'CALL',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'toNumber' => $decorator->formatCallToParticipant($activity),\n 'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),\n 'status' => $decorator->convertCallStatus($activity),\n 'durationMilliseconds' => (int) $activity->getDuration() * 1000,\n 'body' => $decorator->generateDescription($activity),\n ];\n\n // For now, this is just a \"disposition\" field to store Call Outcome.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildTextMessagePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'NOTE',\n ];\n\n $metadata = [\n 'body' => $decorator->generateDescription($activity),\n ];\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function getActivityAssociations(Activity $activity): array\n {\n return [\n 'contactIds' => $this->getParticipantsIds($activity),\n 'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],\n 'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],\n 'ownerIds' => [],\n ];\n }\n\n /**\n * Get DecorateActivity instance. Can be overridden in tests for mocking.\n */\n protected function getDecorateActivity(): DecorateActivity\n {\n return new DecorateActivity();\n }\n\n public function saveFollowupActivity(Activity $activity, array $fields): ?string\n {\n if (empty($fields['subject'])) {\n return null;\n }\n\n $associations = $this->getActivityAssociations($activity);\n\n if (empty($fields['timestamp']) === false) {\n $formatTimestamp = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['timestamp']);\n $timestamp = $formatTimestamp->getTimestamp();\n } else {\n $timestamp = strtotime('+1 day');\n }\n\n $reminder = null;\n if (empty($fields['reminder']) === false) {\n $formatReminder = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['reminder']);\n $reminder = $formatReminder->getTimestamp() * 1000;\n }\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $timestamp * 1000,\n 'type' => 'TASK',\n ];\n\n $metadata = [\n 'subject' => $fields['subject'],\n 'body' => '',\n 'status' => 'NOT_STARTED',\n 'taskType' => 'CALL',\n 'reminders' => $reminder ? [$reminder] : [],\n ];\n\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n } catch (BadRequest $e) {\n $this->logger->info('[HubSpot] Failed to create followup task', [\n 'activity' => $activity->getUuid(),\n 'engagement' => $engagement,\n 'metadata' => $metadata,\n 'error' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n // We don't actually create a corresponding activity object on our side yet.\n return (string) $hsEngagement->data->engagement->id;\n }\n\n private function logCrmEngagementManipulation(\n string $action,\n array $engagementData,\n array $data = [],\n array $associations = [],\n ): void {\n $payload = ['team' => $this->team->getUuid()];\n\n if ($action === self::ACTION_CREATE) {\n $payload['engagementData'] = $engagementData;\n $payload['data'] = $data;\n $payload['associations'] = $associations;\n }\n\n if ($action === self::ACTION_UPDATE) {\n $payload['engagementId'] = $engagementData['crmId'];\n $payload['data'] = $data;\n }\n\n if ($action === self::ACTION_DELETE) {\n $payload['engagementId'] = $engagementData['crmId'];\n }\n\n $this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
60362959092589173
|
-2691170164485591123
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
8
6
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldDataRepository;
use Jiminny\Services\Crm\Hubspot\DecorateActivity;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationCategoryV4;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationTypeIdV4;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Http\Response;
use SevenShores\Hubspot\Exceptions\HubspotException;
use Sentry;
trait WriteCrmTrait
{
private const string ACTION_CREATE = 'create';
private const string ACTION_UPDATE = 'update';
private const string ACTION_DELETE = 'delete';
/**
* @inheritdoc
*/
public function saveActivity(Activity $activity): Activity
{
switch ($activity->getCrmType()) {
case Activity::TYPE_CONFERENCE:
$activity = $this->buildConferencePayload($activity);
break;
case Activity::TYPE_SOFTPHONE:
case Activity::TYPE_SOFTPHONE_INBOUND:
$activity = $this->buildSoftphonePayload($activity);
break;
case Activity::TYPE_SMS_INBOUND:
case Activity::TYPE_SMS_OUTBOUND:
$activity = $this->buildTextMessagePayload($activity);
break;
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildConferencePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatStartTime($activity),
'type' => 'MEETING',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'meetingOutcome' => $decorator->convertMeetingOutcome($activity),
'internalMeetingNotes' => $decorator->generateDescription($activity),
];
// Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.
if (! $activity->hasCrmProviderId()) {
if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {
$metadata['startTime'] = $decorator->formatStartTime($activity);
$metadata['endTime'] = $decorator->formatEndTime($activity);
}
$metadata['title'] = $decorator->generateTitle($activity);
}
// For now, this is just a "meetingOutcome" field for some customers.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function fetchCustomFieldData(Activity $activity, string $objectType): array
{
$payload = [];
$fieldDataRepository = app(FieldDataRepository::class);
$fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);
foreach ($fieldData as $data) {
// Add the field and value to the payload.
$payload += [
$data->getField()->getCrmProviderId() => $data->getValue(),
];
}
return $payload;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity
{
if (! $activity->hasCrmProviderId()) {
$this->createActivity($activity, $engagement, $metadata);
return $activity;
}
try {
$oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());
} catch (HubspotException $e) {
$this->logger->warning('[HubSpot] Engagement not found', [
'activity_id' => $activity->getUuid(),
'crm_provider_id' => $activity->getCrmProviderId(),
'exception' => $e->getMessage(),
]);
// potentially here we can log new activity if it is not found
return $activity;
}
$type = $oldEngagement['engagement']['type'] ?? null;
$bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';
$decorator = $this->getDecorateActivity();
if ($type !== 'TASK') {
$oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldDescription
);
$oldAssociations = $oldEngagement['associations'] ?? [];
$this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);
} else {
$oldTaskBody = $oldEngagement['metadata']['body'] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldTaskBody
);
$this->updateTask($activity, $engagement, $metadata);
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateNonTaskActivity(
Activity $activity,
array $engagement,
array $metadata,
array $oldAssociations
): void {
if ($activity->isTypeConference()) {
$this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);
} else {
$this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);
}
$this->updateActivityAssociations($activity, $oldAssociations);
$this->logCrmEngagementManipulation(
self::ACTION_UPDATE,
['crmId' => $activity->getCrmProviderId()],
$metadata,
);
}
private function updateActivityAssociations(Activity $activity, array $oldAssociations): void
{
$currentAssociations = $this->getActivityAssociations($activity);
$associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);
// No changes - exit
if (empty($associationChanges)) {
return;
}
$this->logger->info('[HubSpot] Updating associations', [
'team' => $this->team->getUuid(),
'activity' => $activity->getUuid(),
'associationChanges' => $associationChanges,
]);
$this->updateAssociations($activity, $associationChanges);
}
private function getAssociationDifferences(
Activity $activity,
array $oldAssociations,
array $currentAssociations
): array {
$objects = ['contactIds', 'companyIds', 'dealIds'];
$changes = [];
foreach ($objects as $object) {
$oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);
$currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);
$added = array_diff($currentIds, $oldIds);
$removed = array_diff($oldIds, $currentIds);
$type = match ($object) {
'contactIds' => 'contact',
'companyIds' => 'company',
'dealIds' => 'deal',
};
$shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);
if (! empty($removed) && ! $shouldIncludeRemoved) {
$this->logger->info('[HubSpot] Skipping association removal', [
'team' => $this->team->getUuid(),
'type' => $type,
'oldIds' => $oldIds,
'currentIds' => $currentIds,
'skippedRemovals' => array_values($removed),
]);
}
if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {
$changes[$type] = [
'added' => array_values($added),
'removed' => array_values($removed),
];
}
}
return $changes;
}
/**
* Determines whether removed associations should be synced to HubSpot.
*
* For contacts and companies, we always sync removals (original behavior).
* For deals, we only sync removals when:
* - There are new deals being added (replacement scenario), OR
* - The activity has an account associated (indicating intentional association management)
*
* This prevents accidentally removing deal associations that exist in HubSpot
* but not in Jiminny's database (e.g., deals manually added by users in HubSpot).
*/
private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool
{
// Contacts and companies: always sync removals
if ($type !== 'deal') {
return true;
}
// Deals: sync removals if we're adding new deals (replacement)
if (! empty($added)) {
return true;
}
// Deals: sync removals only if activity has an account (intentional management)
return $activity->hasAccount();
}
private function normalizeIds(array $ids): array
{
$stringIds = array_map('strval', $ids);
$uniqueIds = array_unique($stringIds);
sort($uniqueIds);
return $uniqueIds;
}
private function updateAssociations(Activity $activity, array $associationChanges): void
{
$objectType = $activity->isTypeConference() ? 'meeting' : 'call';
foreach ($associationChanges as $type => $changes) {
if (! empty($changes['added'])) {
$associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);
$payload = $this->payloadBuilder->buildAddAssociationPayload(
$activity->getCrmProviderId(),
$changes['added'],
$associationType
);
$this->getClient()->addAssociations($objectType, $type, $payload);
}
if (! empty($changes['removed'])) {
$payload = $this->payloadBuilder->buildRemoveAssociationPayload(
$activity->getCrmProviderId(),
$changes['removed']
);
$this->getClient()->removeAssociations($objectType, $type, $payload);
}
}
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response
{
// If already set by us/client, do not override it
if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {
$this->logger->info('[HubSpot] Meeting activity type already set', [
'meetingId' => $meetingId,
'activityType' => $engagement['activityType'],
]);
unset($engagement['activityType']);
}
$payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);
return $this->getClient()->updateMeeting($meetingId, $payload);
}
private function hasEngagementActivityType(string $engagementId): bool
{
try {
$engagementData = $this->getClient()->getEngagementData($engagementId);
if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {
return true;
}
return false;
} catch (\Exception $exception) {
Sentry::captureException($exception);
$this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [
'engagementId' => $engagementId,
'exception' => $exception->getMessage(),
]);
}
return true;
}
private function updateTask(Activity $activity, array $engagement, array $metadata): void
{
$this->getClient()->deleteEngagement($activity->getCrmProviderId());
$this->logCrmEngagementManipulation(
self::ACTION_DELETE,
['crmId' => $activity->getCrmProviderId()],
);
$this->createActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createActivity(Activity $activity, array $engagement, array $metadata): void
{
$associations = $this->getActivityAssociations($activity);
$associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);
if ($activity->isTypeConference()) {
try {
$hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);
$activityId = $hsEngagement->data->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
} else {
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
$activityId = $hsEngagement->data->engagement->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
}
}
private function convertActivityMeetingAssociationsV4(Activity $activity): array
{
$associations = [];
$contactIds = $this->getParticipantsIds($activity);
foreach ($contactIds as $contactId) {
$associations[] = $this->buildAssociation(
(int) $contactId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]
);
}
if ($activity->hasAccount()) {
$crmProviderId = $activity->getAccount()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]
);
}
}
if ($activity->hasOpportunity()) {
$crmProviderId = $activity->getOpportunity()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]
);
}
}
return $associations;
}
private function buildAssociation(int $id, array $categoryToTypeIds): array
{
$types = [];
foreach ($categoryToTypeIds as $category => $typeId) {
$types[] = $this->buildAssociationType($category, $typeId);
}
return [
'to' => ['id' => (string) $id],
'types' => $types,
];
}
private function buildAssociationType(string $category, int $typeId): array
{
return [
'associationCategory' => $category,
'associationTypeId' => $typeId,
];
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createMeetingV3(array $engagement, array $associations, array $metadata): Response
{
$payload = $this->payloadBuilder->getV3MeetingPayload(
engagement: $engagement,
metadata: $metadata,
associations: $associations
);
return $this->getClient()->createMeeting($payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
private function buildSoftphonePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'CALL',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'toNumber' => $decorator->formatCallToParticipant($activity),
'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),
'status' => $decorator->convertCallStatus($activity),
'durationMilliseconds' => (int) $activity->getDuration() * 1000,
'body' => $decorator->generateDescription($activity),
];
// For now, this is just a "disposition" field to store Call Outcome.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildTextMessagePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'NOTE',
];
$metadata = [
'body' => $decorator->generateDescription($activity),
];
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function getActivityAssociations(Activity $activity): array
{
return [
'contactIds' => $this->getParticipantsIds($activity),
'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],
'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],
'ownerIds' => [],
];
}
/**
* Get DecorateActivity instance. Can be overridden in tests for mocking.
*/
protected function getDecorateActivity(): DecorateActivity
{
return new DecorateActivity();
}
public function saveFollowupActivity(Activity $activity, array $fields): ?string
{
if (empty($fields['subject'])) {
return null;
}
$associations = $this->getActivityAssociations($activity);
if (empty($fields['timestamp']) === false) {
$formatTimestamp = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['timestamp']);
$timestamp = $formatTimestamp->getTimestamp();
} else {
$timestamp = strtotime('+1 day');
}
$reminder = null;
if (empty($fields['reminder']) === false) {
$formatReminder = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['reminder']);
$reminder = $formatReminder->getTimestamp() * 1000;
}
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $timestamp * 1000,
'type' => 'TASK',
];
$metadata = [
'subject' => $fields['subject'],
'body' => '',
'status' => 'NOT_STARTED',
'taskType' => 'CALL',
'reminders' => $reminder ? [$reminder] : [],
];
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
} catch (BadRequest $e) {
$this->logger->info('[HubSpot] Failed to create followup task', [
'activity' => $activity->getUuid(),
'engagement' => $engagement,
'metadata' => $metadata,
'error' => $e->getMessage(),
]);
return null;
}
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
// We don't actually create a corresponding activity object on our side yet.
return (string) $hsEngagement->data->engagement->id;
}
private function logCrmEngagementManipulation(
string $action,
array $engagementData,
array $data = [],
array $associations = [],
): void {
$payload = ['team' => $this->team->getUuid()];
if ($action === self::ACTION_CREATE) {
$payload['engagementData'] = $engagementData;
$payload['data'] = $data;
$payload['associations'] = $associations;
}
if ($action === self::ACTION_UPDATE) {
$payload['engagementId'] = $engagementData['crmId'];
$payload['data'] = $data;
}
if ($action === self::ACTION_DELETE) {
$payload['engagementId'] = $engagementData['crmId'];
}
$this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45184
|
1621
|
28
|
2026-05-14T14:24:05.366794+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768645366_m1.jpg...
|
PhpStorm
|
faVsco.js – WriteCrmTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
8
6
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldDataRepository;
use Jiminny\Services\Crm\Hubspot\DecorateActivity;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationCategoryV4;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationTypeIdV4;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Http\Response;
use SevenShores\Hubspot\Exceptions\HubspotException;
use Sentry;
trait WriteCrmTrait
{
private const string ACTION_CREATE = 'create';
private const string ACTION_UPDATE = 'update';
private const string ACTION_DELETE = 'delete';
/**
* @inheritdoc
*/
public function saveActivity(Activity $activity): Activity
{
switch ($activity->getCrmType()) {
case Activity::TYPE_CONFERENCE:
$activity = $this->buildConferencePayload($activity);
break;
case Activity::TYPE_SOFTPHONE:
case Activity::TYPE_SOFTPHONE_INBOUND:
$activity = $this->buildSoftphonePayload($activity);
break;
case Activity::TYPE_SMS_INBOUND:
case Activity::TYPE_SMS_OUTBOUND:
$activity = $this->buildTextMessagePayload($activity);
break;
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildConferencePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatStartTime($activity),
'type' => 'MEETING',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'meetingOutcome' => $decorator->convertMeetingOutcome($activity),
'internalMeetingNotes' => $decorator->generateDescription($activity),
];
// Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.
if (! $activity->hasCrmProviderId()) {
if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {
$metadata['startTime'] = $decorator->formatStartTime($activity);
$metadata['endTime'] = $decorator->formatEndTime($activity);
}
$metadata['title'] = $decorator->generateTitle($activity);
}
// For now, this is just a "meetingOutcome" field for some customers.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function fetchCustomFieldData(Activity $activity, string $objectType): array
{
$payload = [];
$fieldDataRepository = app(FieldDataRepository::class);
$fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);
foreach ($fieldData as $data) {
// Add the field and value to the payload.
$payload += [
$data->getField()->getCrmProviderId() => $data->getValue(),
];
}
return $payload;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity
{
if (! $activity->hasCrmProviderId()) {
$this->createActivity($activity, $engagement, $metadata);
return $activity;
}
try {
$oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());
} catch (HubspotException $e) {
$this->logger->warning('[HubSpot] Engagement not found', [
'activity_id' => $activity->getUuid(),
'crm_provider_id' => $activity->getCrmProviderId(),
'exception' => $e->getMessage(),
]);
// potentially here we can log new activity if it is not found
return $activity;
}
$type = $oldEngagement['engagement']['type'] ?? null;
$bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';
$decorator = $this->getDecorateActivity();
if ($type !== 'TASK') {
$oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldDescription
);
$oldAssociations = $oldEngagement['associations'] ?? [];
$this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);
} else {
$oldTaskBody = $oldEngagement['metadata']['body'] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldTaskBody
);
$this->updateTask($activity, $engagement, $metadata);
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateNonTaskActivity(
Activity $activity,
array $engagement,
array $metadata,
array $oldAssociations
): void {
if ($activity->isTypeConference()) {
$this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);
} else {
$this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);
}
$this->updateActivityAssociations($activity, $oldAssociations);
$this->logCrmEngagementManipulation(
self::ACTION_UPDATE,
['crmId' => $activity->getCrmProviderId()],
$metadata,
);
}
private function updateActivityAssociations(Activity $activity, array $oldAssociations): void
{
$currentAssociations = $this->getActivityAssociations($activity);
$associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);
// No changes - exit
if (empty($associationChanges)) {
return;
}
$this->logger->info('[HubSpot] Updating associations', [
'team' => $this->team->getUuid(),
'activity' => $activity->getUuid(),
'associationChanges' => $associationChanges,
]);
$this->updateAssociations($activity, $associationChanges);
}
private function getAssociationDifferences(
Activity $activity,
array $oldAssociations,
array $currentAssociations
): array {
$objects = ['contactIds', 'companyIds', 'dealIds'];
$changes = [];
foreach ($objects as $object) {
$oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);
$currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);
$added = array_diff($currentIds, $oldIds);
$removed = array_diff($oldIds, $currentIds);
$type = match ($object) {
'contactIds' => 'contact',
'companyIds' => 'company',
'dealIds' => 'deal',
};
$shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);
if (! empty($removed) && ! $shouldIncludeRemoved) {
$this->logger->info('[HubSpot] Skipping association removal', [
'team' => $this->team->getUuid(),
'type' => $type,
'oldIds' => $oldIds,
'currentIds' => $currentIds,
'skippedRemovals' => array_values($removed),
]);
}
if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {
$changes[$type] = [
'added' => array_values($added),
'removed' => array_values($removed),
];
}
}
return $changes;
}
/**
* Determines whether removed associations should be synced to HubSpot.
*
* For contacts and companies, we always sync removals (original behavior).
* For deals, we only sync removals when:
* - There are new deals being added (replacement scenario), OR
* - The activity has an account associated (indicating intentional association management)
*
* This prevents accidentally removing deal associations that exist in HubSpot
* but not in Jiminny's database (e.g., deals manually added by users in HubSpot).
*/
private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool
{
// Contacts and companies: always sync removals
if ($type !== 'deal') {
return true;
}
// Deals: sync removals if we're adding new deals (replacement)
if (! empty($added)) {
return true;
}
// Deals: sync removals only if activity has an account (intentional management)
return $activity->hasAccount();
}
private function normalizeIds(array $ids): array
{
$stringIds = array_map('strval', $ids);
$uniqueIds = array_unique($stringIds);
sort($uniqueIds);
return $uniqueIds;
}
private function updateAssociations(Activity $activity, array $associationChanges): void
{
$objectType = $activity->isTypeConference() ? 'meeting' : 'call';
foreach ($associationChanges as $type => $changes) {
if (! empty($changes['added'])) {
$associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);
$payload = $this->payloadBuilder->buildAddAssociationPayload(
$activity->getCrmProviderId(),
$changes['added'],
$associationType
);
$this->getClient()->addAssociations($objectType, $type, $payload);
}
if (! empty($changes['removed'])) {
$payload = $this->payloadBuilder->buildRemoveAssociationPayload(
$activity->getCrmProviderId(),
$changes['removed']
);
$this->getClient()->removeAssociations($objectType, $type, $payload);
}
}
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response
{
// If already set by us/client, do not override it
if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {
$this->logger->info('[HubSpot] Meeting activity type already set', [
'meetingId' => $meetingId,
'activityType' => $engagement['activityType'],
]);
unset($engagement['activityType']);
}
$payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);
return $this->getClient()->updateMeeting($meetingId, $payload);
}
private function hasEngagementActivityType(string $engagementId): bool
{
try {
$engagementData = $this->getClient()->getEngagementData($engagementId);
if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {
return true;
}
return false;
} catch (\Exception $exception) {
Sentry::captureException($exception);
$this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [
'engagementId' => $engagementId,
'exception' => $exception->getMessage(),
]);
}
return true;
}
private function updateTask(Activity $activity, array $engagement, array $metadata): void
{
$this->getClient()->deleteEngagement($activity->getCrmProviderId());
$this->logCrmEngagementManipulation(
self::ACTION_DELETE,
['crmId' => $activity->getCrmProviderId()],
);
$this->createActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createActivity(Activity $activity, array $engagement, array $metadata): void
{
$associations = $this->getActivityAssociations($activity);
$associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);
if ($activity->isTypeConference()) {
try {
$hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);
$activityId = $hsEngagement->data->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
} else {
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
$activityId = $hsEngagement->data->engagement->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
}
}
private function convertActivityMeetingAssociationsV4(Activity $activity): array
{
$associations = [];
$contactIds = $this->getParticipantsIds($activity);
foreach ($contactIds as $contactId) {
$associations[] = $this->buildAssociation(
(int) $contactId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]
);
}
if ($activity->hasAccount()) {
$crmProviderId = $activity->getAccount()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]
);
}
}
if ($activity->hasOpportunity()) {
$crmProviderId = $activity->getOpportunity()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]
);
}
}
return $associations;
}
private function buildAssociation(int $id, array $categoryToTypeIds): array
{
$types = [];
foreach ($categoryToTypeIds as $category => $typeId) {
$types[] = $this->buildAssociationType($category, $typeId);
}
return [
'to' => ['id' => (string) $id],
'types' => $types,
];
}
private function buildAssociationType(string $category, int $typeId): array
{
return [
'associationCategory' => $category,
'associationTypeId' => $typeId,
];
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createMeetingV3(array $engagement, array $associations, array $metadata): Response
{
$payload = $this->payloadBuilder->getV3MeetingPayload(
engagement: $engagement,
metadata: $metadata,
associations: $associations
);
return $this->getClient()->createMeeting($payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
private function buildSoftphonePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'CALL',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'toNumber' => $decorator->formatCallToParticipant($activity),
'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),
'status' => $decorator->convertCallStatus($activity),
'durationMilliseconds' => (int) $activity->getDuration() * 1000,
'body' => $decorator->generateDescription($activity),
];
// For now, this is just a "disposition" field to store Call Outcome.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildTextMessagePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'NOTE',
];
$metadata = [
'body' => $decorator->generateDescription($activity),
];
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function getActivityAssociations(Activity $activity): array
{
return [
'contactIds' => $this->getParticipantsIds($activity),
'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],
'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],
'ownerIds' => [],
];
}
/**
* Get DecorateActivity instance. Can be overridden in tests for mocking.
*/
protected function getDecorateActivity(): DecorateActivity
{
return new DecorateActivity();
}
public function saveFollowupActivity(Activity $activity, array $fields): ?string
{
if (empty($fields['subject'])) {
return null;
}
$associations = $this->getActivityAssociations($activity);
if (empty($fields['timestamp']) === false) {
$formatTimestamp = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['timestamp']);
$timestamp = $formatTimestamp->getTimestamp();
} else {
$timestamp = strtotime('+1 day');
}
$reminder = null;
if (empty($fields['reminder']) === false) {
$formatReminder = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['reminder']);
$reminder = $formatReminder->getTimestamp() * 1000;
}
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $timestamp * 1000,
'type' => 'TASK',
];
$metadata = [
'subject' => $fields['subject'],
'body' => '',
'status' => 'NOT_STARTED',
'taskType' => 'CALL',
'reminders' => $reminder ? [$reminder] : [],
];
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
} catch (BadRequest $e) {
$this->logger->info('[HubSpot] Failed to create followup task', [
'activity' => $activity->getUuid(),
'engagement' => $engagement,
'metadata' => $metadata,
'error' => $e->getMessage(),
]);
return null;
}
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
// We don't actually create a corresponding activity object on our side yet.
return (string) $hsEngagement->data->engagement->id;
}
private function logCrmEngagementManipulation(
string $action,
array $engagementData,
array $data = [],
array $associations = [],
): void {
$payload = ['team' => $this->team->getUuid()];
if ($action === self::ACTION_CREATE) {
$payload['engagementData'] = $engagementData;
$payload['data'] = $data;
$payload['associations'] = $associations;
}
if ($action === self::ACTION_UPDATE) {
$payload['engagementId'] = $engagementData['crmId'];
$payload['data'] = $data;
}
if ($action === self::ACTION_DELETE) {
$payload['engagementId'] = $engagementData['crmId'];
}
$this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);
}
}...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.054166667,"top":0.027777778,"width":0.08055556,"height":0.035555556},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#12066 on JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.13472222,"top":0.027777778,"width":0.25555557,"height":0.035555556},"on_screen":true,"help_text":"Pull request #12066 exists for current branch JY-20725-handle-HS-search-rate-limit, 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.6326389,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HandleHubspotRateLimitTest","depth":6,"bounds":{"left":0.6645833,"top":0.027777778,"width":0.15902779,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.82361114,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.8472222,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.87083334,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.9291667,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9527778,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9763889,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"8","depth":4,"bounds":{"left":0.78055555,"top":0.2777778,"width":0.016666668,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.80138886,"top":0.2777778,"width":0.016666668,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.8215278,"top":0.27555555,"width":0.015277778,"height":0.025555555},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.8368056,"top":0.27555555,"width":0.014583333,"height":0.025555555},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldDataRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DecorateActivity;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationCategoryV4;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationTypeIdV4;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse Sentry;\n\ntrait WriteCrmTrait\n{\n private const string ACTION_CREATE = 'create';\n private const string ACTION_UPDATE = 'update';\n private const string ACTION_DELETE = 'delete';\n\n /**\n * @inheritdoc\n */\n public function saveActivity(Activity $activity): Activity\n {\n switch ($activity->getCrmType()) {\n case Activity::TYPE_CONFERENCE:\n $activity = $this->buildConferencePayload($activity);\n\n break;\n case Activity::TYPE_SOFTPHONE:\n case Activity::TYPE_SOFTPHONE_INBOUND:\n $activity = $this->buildSoftphonePayload($activity);\n\n break;\n\n case Activity::TYPE_SMS_INBOUND:\n case Activity::TYPE_SMS_OUTBOUND:\n $activity = $this->buildTextMessagePayload($activity);\n\n break;\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildConferencePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatStartTime($activity),\n 'type' => 'MEETING',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'meetingOutcome' => $decorator->convertMeetingOutcome($activity),\n 'internalMeetingNotes' => $decorator->generateDescription($activity),\n ];\n\n // Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.\n if (! $activity->hasCrmProviderId()) {\n if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {\n $metadata['startTime'] = $decorator->formatStartTime($activity);\n $metadata['endTime'] = $decorator->formatEndTime($activity);\n }\n $metadata['title'] = $decorator->generateTitle($activity);\n }\n\n // For now, this is just a \"meetingOutcome\" field for some customers.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function fetchCustomFieldData(Activity $activity, string $objectType): array\n {\n $payload = [];\n\n $fieldDataRepository = app(FieldDataRepository::class);\n $fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);\n\n foreach ($fieldData as $data) {\n // Add the field and value to the payload.\n $payload += [\n $data->getField()->getCrmProviderId() => $data->getValue(),\n ];\n }\n\n return $payload;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity\n {\n if (! $activity->hasCrmProviderId()) {\n $this->createActivity($activity, $engagement, $metadata);\n\n return $activity;\n }\n\n try {\n $oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());\n } catch (HubspotException $e) {\n $this->logger->warning('[HubSpot] Engagement not found', [\n 'activity_id' => $activity->getUuid(),\n 'crm_provider_id' => $activity->getCrmProviderId(),\n 'exception' => $e->getMessage(),\n ]);\n\n // potentially here we can log new activity if it is not found\n\n return $activity;\n }\n\n $type = $oldEngagement['engagement']['type'] ?? null;\n $bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';\n\n $decorator = $this->getDecorateActivity();\n\n if ($type !== 'TASK') {\n $oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldDescription\n );\n\n $oldAssociations = $oldEngagement['associations'] ?? [];\n $this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);\n } else {\n $oldTaskBody = $oldEngagement['metadata']['body'] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldTaskBody\n );\n $this->updateTask($activity, $engagement, $metadata);\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateNonTaskActivity(\n Activity $activity,\n array $engagement,\n array $metadata,\n array $oldAssociations\n ): void {\n if ($activity->isTypeConference()) {\n $this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);\n } else {\n $this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);\n }\n\n $this->updateActivityAssociations($activity, $oldAssociations);\n\n $this->logCrmEngagementManipulation(\n self::ACTION_UPDATE,\n ['crmId' => $activity->getCrmProviderId()],\n $metadata,\n );\n }\n\n private function updateActivityAssociations(Activity $activity, array $oldAssociations): void\n {\n $currentAssociations = $this->getActivityAssociations($activity);\n $associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);\n\n // No changes - exit\n if (empty($associationChanges)) {\n return;\n }\n\n $this->logger->info('[HubSpot] Updating associations', [\n 'team' => $this->team->getUuid(),\n 'activity' => $activity->getUuid(),\n 'associationChanges' => $associationChanges,\n ]);\n\n $this->updateAssociations($activity, $associationChanges);\n }\n\n private function getAssociationDifferences(\n Activity $activity,\n array $oldAssociations,\n array $currentAssociations\n ): array {\n $objects = ['contactIds', 'companyIds', 'dealIds'];\n $changes = [];\n\n foreach ($objects as $object) {\n $oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);\n $currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);\n\n $added = array_diff($currentIds, $oldIds);\n $removed = array_diff($oldIds, $currentIds);\n\n $type = match ($object) {\n 'contactIds' => 'contact',\n 'companyIds' => 'company',\n 'dealIds' => 'deal',\n };\n\n $shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);\n\n if (! empty($removed) && ! $shouldIncludeRemoved) {\n $this->logger->info('[HubSpot] Skipping association removal', [\n 'team' => $this->team->getUuid(),\n 'type' => $type,\n 'oldIds' => $oldIds,\n 'currentIds' => $currentIds,\n 'skippedRemovals' => array_values($removed),\n ]);\n }\n\n if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {\n $changes[$type] = [\n 'added' => array_values($added),\n 'removed' => array_values($removed),\n ];\n }\n }\n\n return $changes;\n }\n\n /**\n * Determines whether removed associations should be synced to HubSpot.\n *\n * For contacts and companies, we always sync removals (original behavior).\n * For deals, we only sync removals when:\n * - There are new deals being added (replacement scenario), OR\n * - The activity has an account associated (indicating intentional association management)\n *\n * This prevents accidentally removing deal associations that exist in HubSpot\n * but not in Jiminny's database (e.g., deals manually added by users in HubSpot).\n */\n private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool\n {\n // Contacts and companies: always sync removals\n if ($type !== 'deal') {\n return true;\n }\n\n // Deals: sync removals if we're adding new deals (replacement)\n if (! empty($added)) {\n return true;\n }\n\n // Deals: sync removals only if activity has an account (intentional management)\n return $activity->hasAccount();\n }\n\n private function normalizeIds(array $ids): array\n {\n $stringIds = array_map('strval', $ids);\n $uniqueIds = array_unique($stringIds);\n sort($uniqueIds);\n\n return $uniqueIds;\n }\n\n private function updateAssociations(Activity $activity, array $associationChanges): void\n {\n $objectType = $activity->isTypeConference() ? 'meeting' : 'call';\n\n foreach ($associationChanges as $type => $changes) {\n if (! empty($changes['added'])) {\n $associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);\n $payload = $this->payloadBuilder->buildAddAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['added'],\n $associationType\n );\n $this->getClient()->addAssociations($objectType, $type, $payload);\n }\n\n if (! empty($changes['removed'])) {\n $payload = $this->payloadBuilder->buildRemoveAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['removed']\n );\n $this->getClient()->removeAssociations($objectType, $type, $payload);\n }\n }\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response\n {\n // If already set by us/client, do not override it\n if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {\n $this->logger->info('[HubSpot] Meeting activity type already set', [\n 'meetingId' => $meetingId,\n 'activityType' => $engagement['activityType'],\n ]);\n\n unset($engagement['activityType']);\n }\n\n $payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);\n\n return $this->getClient()->updateMeeting($meetingId, $payload);\n }\n\n private function hasEngagementActivityType(string $engagementId): bool\n {\n try {\n $engagementData = $this->getClient()->getEngagementData($engagementId);\n if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {\n return true;\n }\n\n return false;\n } catch (\\Exception $exception) {\n Sentry::captureException($exception);\n\n $this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [\n 'engagementId' => $engagementId,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n return true;\n }\n\n private function updateTask(Activity $activity, array $engagement, array $metadata): void\n {\n $this->getClient()->deleteEngagement($activity->getCrmProviderId());\n\n $this->logCrmEngagementManipulation(\n self::ACTION_DELETE,\n ['crmId' => $activity->getCrmProviderId()],\n );\n\n $this->createActivity($activity, $engagement, $metadata);\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createActivity(Activity $activity, array $engagement, array $metadata): void\n {\n $associations = $this->getActivityAssociations($activity);\n $associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);\n\n if ($activity->isTypeConference()) {\n try {\n $hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);\n $activityId = $hsEngagement->data->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n } else {\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n $activityId = $hsEngagement->data->engagement->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n }\n }\n\n private function convertActivityMeetingAssociationsV4(Activity $activity): array\n {\n $associations = [];\n\n $contactIds = $this->getParticipantsIds($activity);\n\n foreach ($contactIds as $contactId) {\n $associations[] = $this->buildAssociation(\n (int) $contactId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]\n );\n }\n\n if ($activity->hasAccount()) {\n $crmProviderId = $activity->getAccount()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]\n );\n }\n }\n\n if ($activity->hasOpportunity()) {\n $crmProviderId = $activity->getOpportunity()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]\n );\n }\n }\n\n return $associations;\n }\n\n private function buildAssociation(int $id, array $categoryToTypeIds): array\n {\n $types = [];\n\n foreach ($categoryToTypeIds as $category => $typeId) {\n $types[] = $this->buildAssociationType($category, $typeId);\n }\n\n return [\n 'to' => ['id' => (string) $id],\n 'types' => $types,\n ];\n }\n\n private function buildAssociationType(string $category, int $typeId): array\n {\n return [\n 'associationCategory' => $category,\n 'associationTypeId' => $typeId,\n ];\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createMeetingV3(array $engagement, array $associations, array $metadata): Response\n {\n $payload = $this->payloadBuilder->getV3MeetingPayload(\n engagement: $engagement,\n metadata: $metadata,\n associations: $associations\n );\n\n return $this->getClient()->createMeeting($payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n private function buildSoftphonePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'CALL',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'toNumber' => $decorator->formatCallToParticipant($activity),\n 'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),\n 'status' => $decorator->convertCallStatus($activity),\n 'durationMilliseconds' => (int) $activity->getDuration() * 1000,\n 'body' => $decorator->generateDescription($activity),\n ];\n\n // For now, this is just a \"disposition\" field to store Call Outcome.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildTextMessagePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'NOTE',\n ];\n\n $metadata = [\n 'body' => $decorator->generateDescription($activity),\n ];\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function getActivityAssociations(Activity $activity): array\n {\n return [\n 'contactIds' => $this->getParticipantsIds($activity),\n 'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],\n 'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],\n 'ownerIds' => [],\n ];\n }\n\n /**\n * Get DecorateActivity instance. Can be overridden in tests for mocking.\n */\n protected function getDecorateActivity(): DecorateActivity\n {\n return new DecorateActivity();\n }\n\n public function saveFollowupActivity(Activity $activity, array $fields): ?string\n {\n if (empty($fields['subject'])) {\n return null;\n }\n\n $associations = $this->getActivityAssociations($activity);\n\n if (empty($fields['timestamp']) === false) {\n $formatTimestamp = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['timestamp']);\n $timestamp = $formatTimestamp->getTimestamp();\n } else {\n $timestamp = strtotime('+1 day');\n }\n\n $reminder = null;\n if (empty($fields['reminder']) === false) {\n $formatReminder = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['reminder']);\n $reminder = $formatReminder->getTimestamp() * 1000;\n }\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $timestamp * 1000,\n 'type' => 'TASK',\n ];\n\n $metadata = [\n 'subject' => $fields['subject'],\n 'body' => '',\n 'status' => 'NOT_STARTED',\n 'taskType' => 'CALL',\n 'reminders' => $reminder ? [$reminder] : [],\n ];\n\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n } catch (BadRequest $e) {\n $this->logger->info('[HubSpot] Failed to create followup task', [\n 'activity' => $activity->getUuid(),\n 'engagement' => $engagement,\n 'metadata' => $metadata,\n 'error' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n // We don't actually create a corresponding activity object on our side yet.\n return (string) $hsEngagement->data->engagement->id;\n }\n\n private function logCrmEngagementManipulation(\n string $action,\n array $engagementData,\n array $data = [],\n array $associations = [],\n ): void {\n $payload = ['team' => $this->team->getUuid()];\n\n if ($action === self::ACTION_CREATE) {\n $payload['engagementData'] = $engagementData;\n $payload['data'] = $data;\n $payload['associations'] = $associations;\n }\n\n if ($action === self::ACTION_UPDATE) {\n $payload['engagementId'] = $engagementData['crmId'];\n $payload['data'] = $data;\n }\n\n if ($action === self::ACTION_DELETE) {\n $payload['engagementId'] = $engagementData['crmId'];\n }\n\n $this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldDataRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DecorateActivity;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationCategoryV4;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationTypeIdV4;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse Sentry;\n\ntrait WriteCrmTrait\n{\n private const string ACTION_CREATE = 'create';\n private const string ACTION_UPDATE = 'update';\n private const string ACTION_DELETE = 'delete';\n\n /**\n * @inheritdoc\n */\n public function saveActivity(Activity $activity): Activity\n {\n switch ($activity->getCrmType()) {\n case Activity::TYPE_CONFERENCE:\n $activity = $this->buildConferencePayload($activity);\n\n break;\n case Activity::TYPE_SOFTPHONE:\n case Activity::TYPE_SOFTPHONE_INBOUND:\n $activity = $this->buildSoftphonePayload($activity);\n\n break;\n\n case Activity::TYPE_SMS_INBOUND:\n case Activity::TYPE_SMS_OUTBOUND:\n $activity = $this->buildTextMessagePayload($activity);\n\n break;\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildConferencePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatStartTime($activity),\n 'type' => 'MEETING',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'meetingOutcome' => $decorator->convertMeetingOutcome($activity),\n 'internalMeetingNotes' => $decorator->generateDescription($activity),\n ];\n\n // Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.\n if (! $activity->hasCrmProviderId()) {\n if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {\n $metadata['startTime'] = $decorator->formatStartTime($activity);\n $metadata['endTime'] = $decorator->formatEndTime($activity);\n }\n $metadata['title'] = $decorator->generateTitle($activity);\n }\n\n // For now, this is just a \"meetingOutcome\" field for some customers.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function fetchCustomFieldData(Activity $activity, string $objectType): array\n {\n $payload = [];\n\n $fieldDataRepository = app(FieldDataRepository::class);\n $fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);\n\n foreach ($fieldData as $data) {\n // Add the field and value to the payload.\n $payload += [\n $data->getField()->getCrmProviderId() => $data->getValue(),\n ];\n }\n\n return $payload;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity\n {\n if (! $activity->hasCrmProviderId()) {\n $this->createActivity($activity, $engagement, $metadata);\n\n return $activity;\n }\n\n try {\n $oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());\n } catch (HubspotException $e) {\n $this->logger->warning('[HubSpot] Engagement not found', [\n 'activity_id' => $activity->getUuid(),\n 'crm_provider_id' => $activity->getCrmProviderId(),\n 'exception' => $e->getMessage(),\n ]);\n\n // potentially here we can log new activity if it is not found\n\n return $activity;\n }\n\n $type = $oldEngagement['engagement']['type'] ?? null;\n $bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';\n\n $decorator = $this->getDecorateActivity();\n\n if ($type !== 'TASK') {\n $oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldDescription\n );\n\n $oldAssociations = $oldEngagement['associations'] ?? [];\n $this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);\n } else {\n $oldTaskBody = $oldEngagement['metadata']['body'] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldTaskBody\n );\n $this->updateTask($activity, $engagement, $metadata);\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateNonTaskActivity(\n Activity $activity,\n array $engagement,\n array $metadata,\n array $oldAssociations\n ): void {\n if ($activity->isTypeConference()) {\n $this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);\n } else {\n $this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);\n }\n\n $this->updateActivityAssociations($activity, $oldAssociations);\n\n $this->logCrmEngagementManipulation(\n self::ACTION_UPDATE,\n ['crmId' => $activity->getCrmProviderId()],\n $metadata,\n );\n }\n\n private function updateActivityAssociations(Activity $activity, array $oldAssociations): void\n {\n $currentAssociations = $this->getActivityAssociations($activity);\n $associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);\n\n // No changes - exit\n if (empty($associationChanges)) {\n return;\n }\n\n $this->logger->info('[HubSpot] Updating associations', [\n 'team' => $this->team->getUuid(),\n 'activity' => $activity->getUuid(),\n 'associationChanges' => $associationChanges,\n ]);\n\n $this->updateAssociations($activity, $associationChanges);\n }\n\n private function getAssociationDifferences(\n Activity $activity,\n array $oldAssociations,\n array $currentAssociations\n ): array {\n $objects = ['contactIds', 'companyIds', 'dealIds'];\n $changes = [];\n\n foreach ($objects as $object) {\n $oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);\n $currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);\n\n $added = array_diff($currentIds, $oldIds);\n $removed = array_diff($oldIds, $currentIds);\n\n $type = match ($object) {\n 'contactIds' => 'contact',\n 'companyIds' => 'company',\n 'dealIds' => 'deal',\n };\n\n $shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);\n\n if (! empty($removed) && ! $shouldIncludeRemoved) {\n $this->logger->info('[HubSpot] Skipping association removal', [\n 'team' => $this->team->getUuid(),\n 'type' => $type,\n 'oldIds' => $oldIds,\n 'currentIds' => $currentIds,\n 'skippedRemovals' => array_values($removed),\n ]);\n }\n\n if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {\n $changes[$type] = [\n 'added' => array_values($added),\n 'removed' => array_values($removed),\n ];\n }\n }\n\n return $changes;\n }\n\n /**\n * Determines whether removed associations should be synced to HubSpot.\n *\n * For contacts and companies, we always sync removals (original behavior).\n * For deals, we only sync removals when:\n * - There are new deals being added (replacement scenario), OR\n * - The activity has an account associated (indicating intentional association management)\n *\n * This prevents accidentally removing deal associations that exist in HubSpot\n * but not in Jiminny's database (e.g., deals manually added by users in HubSpot).\n */\n private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool\n {\n // Contacts and companies: always sync removals\n if ($type !== 'deal') {\n return true;\n }\n\n // Deals: sync removals if we're adding new deals (replacement)\n if (! empty($added)) {\n return true;\n }\n\n // Deals: sync removals only if activity has an account (intentional management)\n return $activity->hasAccount();\n }\n\n private function normalizeIds(array $ids): array\n {\n $stringIds = array_map('strval', $ids);\n $uniqueIds = array_unique($stringIds);\n sort($uniqueIds);\n\n return $uniqueIds;\n }\n\n private function updateAssociations(Activity $activity, array $associationChanges): void\n {\n $objectType = $activity->isTypeConference() ? 'meeting' : 'call';\n\n foreach ($associationChanges as $type => $changes) {\n if (! empty($changes['added'])) {\n $associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);\n $payload = $this->payloadBuilder->buildAddAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['added'],\n $associationType\n );\n $this->getClient()->addAssociations($objectType, $type, $payload);\n }\n\n if (! empty($changes['removed'])) {\n $payload = $this->payloadBuilder->buildRemoveAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['removed']\n );\n $this->getClient()->removeAssociations($objectType, $type, $payload);\n }\n }\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response\n {\n // If already set by us/client, do not override it\n if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {\n $this->logger->info('[HubSpot] Meeting activity type already set', [\n 'meetingId' => $meetingId,\n 'activityType' => $engagement['activityType'],\n ]);\n\n unset($engagement['activityType']);\n }\n\n $payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);\n\n return $this->getClient()->updateMeeting($meetingId, $payload);\n }\n\n private function hasEngagementActivityType(string $engagementId): bool\n {\n try {\n $engagementData = $this->getClient()->getEngagementData($engagementId);\n if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {\n return true;\n }\n\n return false;\n } catch (\\Exception $exception) {\n Sentry::captureException($exception);\n\n $this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [\n 'engagementId' => $engagementId,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n return true;\n }\n\n private function updateTask(Activity $activity, array $engagement, array $metadata): void\n {\n $this->getClient()->deleteEngagement($activity->getCrmProviderId());\n\n $this->logCrmEngagementManipulation(\n self::ACTION_DELETE,\n ['crmId' => $activity->getCrmProviderId()],\n );\n\n $this->createActivity($activity, $engagement, $metadata);\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createActivity(Activity $activity, array $engagement, array $metadata): void\n {\n $associations = $this->getActivityAssociations($activity);\n $associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);\n\n if ($activity->isTypeConference()) {\n try {\n $hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);\n $activityId = $hsEngagement->data->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n } else {\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n $activityId = $hsEngagement->data->engagement->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n }\n }\n\n private function convertActivityMeetingAssociationsV4(Activity $activity): array\n {\n $associations = [];\n\n $contactIds = $this->getParticipantsIds($activity);\n\n foreach ($contactIds as $contactId) {\n $associations[] = $this->buildAssociation(\n (int) $contactId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]\n );\n }\n\n if ($activity->hasAccount()) {\n $crmProviderId = $activity->getAccount()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]\n );\n }\n }\n\n if ($activity->hasOpportunity()) {\n $crmProviderId = $activity->getOpportunity()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]\n );\n }\n }\n\n return $associations;\n }\n\n private function buildAssociation(int $id, array $categoryToTypeIds): array\n {\n $types = [];\n\n foreach ($categoryToTypeIds as $category => $typeId) {\n $types[] = $this->buildAssociationType($category, $typeId);\n }\n\n return [\n 'to' => ['id' => (string) $id],\n 'types' => $types,\n ];\n }\n\n private function buildAssociationType(string $category, int $typeId): array\n {\n return [\n 'associationCategory' => $category,\n 'associationTypeId' => $typeId,\n ];\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createMeetingV3(array $engagement, array $associations, array $metadata): Response\n {\n $payload = $this->payloadBuilder->getV3MeetingPayload(\n engagement: $engagement,\n metadata: $metadata,\n associations: $associations\n );\n\n return $this->getClient()->createMeeting($payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n private function buildSoftphonePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'CALL',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'toNumber' => $decorator->formatCallToParticipant($activity),\n 'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),\n 'status' => $decorator->convertCallStatus($activity),\n 'durationMilliseconds' => (int) $activity->getDuration() * 1000,\n 'body' => $decorator->generateDescription($activity),\n ];\n\n // For now, this is just a \"disposition\" field to store Call Outcome.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildTextMessagePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'NOTE',\n ];\n\n $metadata = [\n 'body' => $decorator->generateDescription($activity),\n ];\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function getActivityAssociations(Activity $activity): array\n {\n return [\n 'contactIds' => $this->getParticipantsIds($activity),\n 'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],\n 'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],\n 'ownerIds' => [],\n ];\n }\n\n /**\n * Get DecorateActivity instance. Can be overridden in tests for mocking.\n */\n protected function getDecorateActivity(): DecorateActivity\n {\n return new DecorateActivity();\n }\n\n public function saveFollowupActivity(Activity $activity, array $fields): ?string\n {\n if (empty($fields['subject'])) {\n return null;\n }\n\n $associations = $this->getActivityAssociations($activity);\n\n if (empty($fields['timestamp']) === false) {\n $formatTimestamp = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['timestamp']);\n $timestamp = $formatTimestamp->getTimestamp();\n } else {\n $timestamp = strtotime('+1 day');\n }\n\n $reminder = null;\n if (empty($fields['reminder']) === false) {\n $formatReminder = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['reminder']);\n $reminder = $formatReminder->getTimestamp() * 1000;\n }\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $timestamp * 1000,\n 'type' => 'TASK',\n ];\n\n $metadata = [\n 'subject' => $fields['subject'],\n 'body' => '',\n 'status' => 'NOT_STARTED',\n 'taskType' => 'CALL',\n 'reminders' => $reminder ? [$reminder] : [],\n ];\n\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n } catch (BadRequest $e) {\n $this->logger->info('[HubSpot] Failed to create followup task', [\n 'activity' => $activity->getUuid(),\n 'engagement' => $engagement,\n 'metadata' => $metadata,\n 'error' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n // We don't actually create a corresponding activity object on our side yet.\n return (string) $hsEngagement->data->engagement->id;\n }\n\n private function logCrmEngagementManipulation(\n string $action,\n array $engagementData,\n array $data = [],\n array $associations = [],\n ): void {\n $payload = ['team' => $this->team->getUuid()];\n\n if ($action === self::ACTION_CREATE) {\n $payload['engagementData'] = $engagementData;\n $payload['data'] = $data;\n $payload['associations'] = $associations;\n }\n\n if ($action === self::ACTION_UPDATE) {\n $payload['engagementId'] = $engagementData['crmId'];\n $payload['data'] = $data;\n }\n\n if ($action === self::ACTION_DELETE) {\n $payload['engagementId'] = $engagementData['crmId'];\n }\n\n $this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false}]...
|
-8990710920805402580
|
-2691170164485591123
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
8
6
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldDataRepository;
use Jiminny\Services\Crm\Hubspot\DecorateActivity;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationCategoryV4;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationTypeIdV4;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Http\Response;
use SevenShores\Hubspot\Exceptions\HubspotException;
use Sentry;
trait WriteCrmTrait
{
private const string ACTION_CREATE = 'create';
private const string ACTION_UPDATE = 'update';
private const string ACTION_DELETE = 'delete';
/**
* @inheritdoc
*/
public function saveActivity(Activity $activity): Activity
{
switch ($activity->getCrmType()) {
case Activity::TYPE_CONFERENCE:
$activity = $this->buildConferencePayload($activity);
break;
case Activity::TYPE_SOFTPHONE:
case Activity::TYPE_SOFTPHONE_INBOUND:
$activity = $this->buildSoftphonePayload($activity);
break;
case Activity::TYPE_SMS_INBOUND:
case Activity::TYPE_SMS_OUTBOUND:
$activity = $this->buildTextMessagePayload($activity);
break;
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildConferencePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatStartTime($activity),
'type' => 'MEETING',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'meetingOutcome' => $decorator->convertMeetingOutcome($activity),
'internalMeetingNotes' => $decorator->generateDescription($activity),
];
// Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.
if (! $activity->hasCrmProviderId()) {
if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {
$metadata['startTime'] = $decorator->formatStartTime($activity);
$metadata['endTime'] = $decorator->formatEndTime($activity);
}
$metadata['title'] = $decorator->generateTitle($activity);
}
// For now, this is just a "meetingOutcome" field for some customers.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function fetchCustomFieldData(Activity $activity, string $objectType): array
{
$payload = [];
$fieldDataRepository = app(FieldDataRepository::class);
$fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);
foreach ($fieldData as $data) {
// Add the field and value to the payload.
$payload += [
$data->getField()->getCrmProviderId() => $data->getValue(),
];
}
return $payload;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity
{
if (! $activity->hasCrmProviderId()) {
$this->createActivity($activity, $engagement, $metadata);
return $activity;
}
try {
$oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());
} catch (HubspotException $e) {
$this->logger->warning('[HubSpot] Engagement not found', [
'activity_id' => $activity->getUuid(),
'crm_provider_id' => $activity->getCrmProviderId(),
'exception' => $e->getMessage(),
]);
// potentially here we can log new activity if it is not found
return $activity;
}
$type = $oldEngagement['engagement']['type'] ?? null;
$bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';
$decorator = $this->getDecorateActivity();
if ($type !== 'TASK') {
$oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldDescription
);
$oldAssociations = $oldEngagement['associations'] ?? [];
$this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);
} else {
$oldTaskBody = $oldEngagement['metadata']['body'] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldTaskBody
);
$this->updateTask($activity, $engagement, $metadata);
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateNonTaskActivity(
Activity $activity,
array $engagement,
array $metadata,
array $oldAssociations
): void {
if ($activity->isTypeConference()) {
$this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);
} else {
$this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);
}
$this->updateActivityAssociations($activity, $oldAssociations);
$this->logCrmEngagementManipulation(
self::ACTION_UPDATE,
['crmId' => $activity->getCrmProviderId()],
$metadata,
);
}
private function updateActivityAssociations(Activity $activity, array $oldAssociations): void
{
$currentAssociations = $this->getActivityAssociations($activity);
$associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);
// No changes - exit
if (empty($associationChanges)) {
return;
}
$this->logger->info('[HubSpot] Updating associations', [
'team' => $this->team->getUuid(),
'activity' => $activity->getUuid(),
'associationChanges' => $associationChanges,
]);
$this->updateAssociations($activity, $associationChanges);
}
private function getAssociationDifferences(
Activity $activity,
array $oldAssociations,
array $currentAssociations
): array {
$objects = ['contactIds', 'companyIds', 'dealIds'];
$changes = [];
foreach ($objects as $object) {
$oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);
$currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);
$added = array_diff($currentIds, $oldIds);
$removed = array_diff($oldIds, $currentIds);
$type = match ($object) {
'contactIds' => 'contact',
'companyIds' => 'company',
'dealIds' => 'deal',
};
$shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);
if (! empty($removed) && ! $shouldIncludeRemoved) {
$this->logger->info('[HubSpot] Skipping association removal', [
'team' => $this->team->getUuid(),
'type' => $type,
'oldIds' => $oldIds,
'currentIds' => $currentIds,
'skippedRemovals' => array_values($removed),
]);
}
if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {
$changes[$type] = [
'added' => array_values($added),
'removed' => array_values($removed),
];
}
}
return $changes;
}
/**
* Determines whether removed associations should be synced to HubSpot.
*
* For contacts and companies, we always sync removals (original behavior).
* For deals, we only sync removals when:
* - There are new deals being added (replacement scenario), OR
* - The activity has an account associated (indicating intentional association management)
*
* This prevents accidentally removing deal associations that exist in HubSpot
* but not in Jiminny's database (e.g., deals manually added by users in HubSpot).
*/
private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool
{
// Contacts and companies: always sync removals
if ($type !== 'deal') {
return true;
}
// Deals: sync removals if we're adding new deals (replacement)
if (! empty($added)) {
return true;
}
// Deals: sync removals only if activity has an account (intentional management)
return $activity->hasAccount();
}
private function normalizeIds(array $ids): array
{
$stringIds = array_map('strval', $ids);
$uniqueIds = array_unique($stringIds);
sort($uniqueIds);
return $uniqueIds;
}
private function updateAssociations(Activity $activity, array $associationChanges): void
{
$objectType = $activity->isTypeConference() ? 'meeting' : 'call';
foreach ($associationChanges as $type => $changes) {
if (! empty($changes['added'])) {
$associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);
$payload = $this->payloadBuilder->buildAddAssociationPayload(
$activity->getCrmProviderId(),
$changes['added'],
$associationType
);
$this->getClient()->addAssociations($objectType, $type, $payload);
}
if (! empty($changes['removed'])) {
$payload = $this->payloadBuilder->buildRemoveAssociationPayload(
$activity->getCrmProviderId(),
$changes['removed']
);
$this->getClient()->removeAssociations($objectType, $type, $payload);
}
}
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response
{
// If already set by us/client, do not override it
if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {
$this->logger->info('[HubSpot] Meeting activity type already set', [
'meetingId' => $meetingId,
'activityType' => $engagement['activityType'],
]);
unset($engagement['activityType']);
}
$payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);
return $this->getClient()->updateMeeting($meetingId, $payload);
}
private function hasEngagementActivityType(string $engagementId): bool
{
try {
$engagementData = $this->getClient()->getEngagementData($engagementId);
if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {
return true;
}
return false;
} catch (\Exception $exception) {
Sentry::captureException($exception);
$this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [
'engagementId' => $engagementId,
'exception' => $exception->getMessage(),
]);
}
return true;
}
private function updateTask(Activity $activity, array $engagement, array $metadata): void
{
$this->getClient()->deleteEngagement($activity->getCrmProviderId());
$this->logCrmEngagementManipulation(
self::ACTION_DELETE,
['crmId' => $activity->getCrmProviderId()],
);
$this->createActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createActivity(Activity $activity, array $engagement, array $metadata): void
{
$associations = $this->getActivityAssociations($activity);
$associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);
if ($activity->isTypeConference()) {
try {
$hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);
$activityId = $hsEngagement->data->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
} else {
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
$activityId = $hsEngagement->data->engagement->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
}
}
private function convertActivityMeetingAssociationsV4(Activity $activity): array
{
$associations = [];
$contactIds = $this->getParticipantsIds($activity);
foreach ($contactIds as $contactId) {
$associations[] = $this->buildAssociation(
(int) $contactId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]
);
}
if ($activity->hasAccount()) {
$crmProviderId = $activity->getAccount()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]
);
}
}
if ($activity->hasOpportunity()) {
$crmProviderId = $activity->getOpportunity()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]
);
}
}
return $associations;
}
private function buildAssociation(int $id, array $categoryToTypeIds): array
{
$types = [];
foreach ($categoryToTypeIds as $category => $typeId) {
$types[] = $this->buildAssociationType($category, $typeId);
}
return [
'to' => ['id' => (string) $id],
'types' => $types,
];
}
private function buildAssociationType(string $category, int $typeId): array
{
return [
'associationCategory' => $category,
'associationTypeId' => $typeId,
];
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createMeetingV3(array $engagement, array $associations, array $metadata): Response
{
$payload = $this->payloadBuilder->getV3MeetingPayload(
engagement: $engagement,
metadata: $metadata,
associations: $associations
);
return $this->getClient()->createMeeting($payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
private function buildSoftphonePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'CALL',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'toNumber' => $decorator->formatCallToParticipant($activity),
'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),
'status' => $decorator->convertCallStatus($activity),
'durationMilliseconds' => (int) $activity->getDuration() * 1000,
'body' => $decorator->generateDescription($activity),
];
// For now, this is just a "disposition" field to store Call Outcome.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildTextMessagePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'NOTE',
];
$metadata = [
'body' => $decorator->generateDescription($activity),
];
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function getActivityAssociations(Activity $activity): array
{
return [
'contactIds' => $this->getParticipantsIds($activity),
'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],
'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],
'ownerIds' => [],
];
}
/**
* Get DecorateActivity instance. Can be overridden in tests for mocking.
*/
protected function getDecorateActivity(): DecorateActivity
{
return new DecorateActivity();
}
public function saveFollowupActivity(Activity $activity, array $fields): ?string
{
if (empty($fields['subject'])) {
return null;
}
$associations = $this->getActivityAssociations($activity);
if (empty($fields['timestamp']) === false) {
$formatTimestamp = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['timestamp']);
$timestamp = $formatTimestamp->getTimestamp();
} else {
$timestamp = strtotime('+1 day');
}
$reminder = null;
if (empty($fields['reminder']) === false) {
$formatReminder = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['reminder']);
$reminder = $formatReminder->getTimestamp() * 1000;
}
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $timestamp * 1000,
'type' => 'TASK',
];
$metadata = [
'subject' => $fields['subject'],
'body' => '',
'status' => 'NOT_STARTED',
'taskType' => 'CALL',
'reminders' => $reminder ? [$reminder] : [],
];
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
} catch (BadRequest $e) {
$this->logger->info('[HubSpot] Failed to create followup task', [
'activity' => $activity->getUuid(),
'engagement' => $engagement,
'metadata' => $metadata,
'error' => $e->getMessage(),
]);
return null;
}
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
// We don't actually create a corresponding activity object on our side yet.
return (string) $hsEngagement->data->engagement->id;
}
private function logCrmEngagementManipulation(
string $action,
array $engagementData,
array $data = [],
array $associations = [],
): void {
$payload = ['team' => $this->team->getUuid()];
if ($action === self::ACTION_CREATE) {
$payload['engagementData'] = $engagementData;
$payload['data'] = $data;
$payload['associations'] = $associations;
}
if ($action === self::ACTION_UPDATE) {
$payload['engagementId'] = $engagementData['crmId'];
$payload['data'] = $data;
}
if ($action === self::ACTION_DELETE) {
$payload['engagementId'] = $engagementData['crmId'];
}
$this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);
}
}...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45186
|
1621
|
29
|
2026-05-14T14:24:07.503713+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768647503_m1.jpg...
|
PhpStorm
|
faVsco.js – WriteCrmTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
8
6
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldDataRepository;
use Jiminny\Services\Crm\Hubspot\DecorateActivity;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationCategoryV4;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationTypeIdV4;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Http\Response;
use SevenShores\Hubspot\Exceptions\HubspotException;
use Sentry;
trait WriteCrmTrait
{
private const string ACTION_CREATE = 'create';
private const string ACTION_UPDATE = 'update';
private const string ACTION_DELETE = 'delete';
/**
* @inheritdoc
*/
public function saveActivity(Activity $activity): Activity
{
switch ($activity->getCrmType()) {
case Activity::TYPE_CONFERENCE:
$activity = $this->buildConferencePayload($activity);
break;
case Activity::TYPE_SOFTPHONE:
case Activity::TYPE_SOFTPHONE_INBOUND:
$activity = $this->buildSoftphonePayload($activity);
break;
case Activity::TYPE_SMS_INBOUND:
case Activity::TYPE_SMS_OUTBOUND:
$activity = $this->buildTextMessagePayload($activity);
break;
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildConferencePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatStartTime($activity),
'type' => 'MEETING',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'meetingOutcome' => $decorator->convertMeetingOutcome($activity),
'internalMeetingNotes' => $decorator->generateDescription($activity),
];
// Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.
if (! $activity->hasCrmProviderId()) {
if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {
$metadata['startTime'] = $decorator->formatStartTime($activity);
$metadata['endTime'] = $decorator->formatEndTime($activity);
}
$metadata['title'] = $decorator->generateTitle($activity);
}
// For now, this is just a "meetingOutcome" field for some customers.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function fetchCustomFieldData(Activity $activity, string $objectType): array
{
$payload = [];
$fieldDataRepository = app(FieldDataRepository::class);
$fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);
foreach ($fieldData as $data) {
// Add the field and value to the payload.
$payload += [
$data->getField()->getCrmProviderId() => $data->getValue(),
];
}
return $payload;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity
{
if (! $activity->hasCrmProviderId()) {
$this->createActivity($activity, $engagement, $metadata);
return $activity;
}
try {
$oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());
} catch (HubspotException $e) {
$this->logger->warning('[HubSpot] Engagement not found', [
'activity_id' => $activity->getUuid(),
'crm_provider_id' => $activity->getCrmProviderId(),
'exception' => $e->getMessage(),
]);
// potentially here we can log new activity if it is not found
return $activity;
}
$type = $oldEngagement['engagement']['type'] ?? null;
$bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';
$decorator = $this->getDecorateActivity();
if ($type !== 'TASK') {
$oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldDescription
);
$oldAssociations = $oldEngagement['associations'] ?? [];
$this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);
} else {
$oldTaskBody = $oldEngagement['metadata']['body'] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldTaskBody
);
$this->updateTask($activity, $engagement, $metadata);
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateNonTaskActivity(
Activity $activity,
array $engagement,
array $metadata,
array $oldAssociations
): void {
if ($activity->isTypeConference()) {
$this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);
} else {
$this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);
}
$this->updateActivityAssociations($activity, $oldAssociations);
$this->logCrmEngagementManipulation(
self::ACTION_UPDATE,
['crmId' => $activity->getCrmProviderId()],
$metadata,
);
}
private function updateActivityAssociations(Activity $activity, array $oldAssociations): void
{
$currentAssociations = $this->getActivityAssociations($activity);
$associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);
// No changes - exit
if (empty($associationChanges)) {
return;
}
$this->logger->info('[HubSpot] Updating associations', [
'team' => $this->team->getUuid(),
'activity' => $activity->getUuid(),
'associationChanges' => $associationChanges,
]);
$this->updateAssociations($activity, $associationChanges);
}
private function getAssociationDifferences(
Activity $activity,
array $oldAssociations,
array $currentAssociations
): array {
$objects = ['contactIds', 'companyIds', 'dealIds'];
$changes = [];
foreach ($objects as $object) {
$oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);
$currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);
$added = array_diff($currentIds, $oldIds);
$removed = array_diff($oldIds, $currentIds);
$type = match ($object) {
'contactIds' => 'contact',
'companyIds' => 'company',
'dealIds' => 'deal',
};
$shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);
if (! empty($removed) && ! $shouldIncludeRemoved) {
$this->logger->info('[HubSpot] Skipping association removal', [
'team' => $this->team->getUuid(),
'type' => $type,
'oldIds' => $oldIds,
'currentIds' => $currentIds,
'skippedRemovals' => array_values($removed),
]);
}
if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {
$changes[$type] = [
'added' => array_values($added),
'removed' => array_values($removed),
];
}
}
return $changes;
}
/**
* Determines whether removed associations should be synced to HubSpot.
*
* For contacts and companies, we always sync removals (original behavior).
* For deals, we only sync removals when:
* - There are new deals being added (replacement scenario), OR
* - The activity has an account associated (indicating intentional association management)
*
* This prevents accidentally removing deal associations that exist in HubSpot
* but not in Jiminny's database (e.g., deals manually added by users in HubSpot).
*/
private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool
{
// Contacts and companies: always sync removals
if ($type !== 'deal') {
return true;
}
// Deals: sync removals if we're adding new deals (replacement)
if (! empty($added)) {
return true;
}
// Deals: sync removals only if activity has an account (intentional management)
return $activity->hasAccount();
}
private function normalizeIds(array $ids): array
{
$stringIds = array_map('strval', $ids);
$uniqueIds = array_unique($stringIds);
sort($uniqueIds);
return $uniqueIds;
}
private function updateAssociations(Activity $activity, array $associationChanges): void
{
$objectType = $activity->isTypeConference() ? 'meeting' : 'call';
foreach ($associationChanges as $type => $changes) {
if (! empty($changes['added'])) {
$associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);
$payload = $this->payloadBuilder->buildAddAssociationPayload(
$activity->getCrmProviderId(),
$changes['added'],
$associationType
);
$this->getClient()->addAssociations($objectType, $type, $payload);
}
if (! empty($changes['removed'])) {
$payload = $this->payloadBuilder->buildRemoveAssociationPayload(
$activity->getCrmProviderId(),
$changes['removed']
);
$this->getClient()->removeAssociations($objectType, $type, $payload);
}
}
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response
{
// If already set by us/client, do not override it
if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {
$this->logger->info('[HubSpot] Meeting activity type already set', [
'meetingId' => $meetingId,
'activityType' => $engagement['activityType'],
]);
unset($engagement['activityType']);
}
$payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);
return $this->getClient()->updateMeeting($meetingId, $payload);
}
private function hasEngagementActivityType(string $engagementId): bool
{
try {
$engagementData = $this->getClient()->getEngagementData($engagementId);
if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {
return true;
}
return false;
} catch (\Exception $exception) {
Sentry::captureException($exception);
$this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [
'engagementId' => $engagementId,
'exception' => $exception->getMessage(),
]);
}
return true;
}
private function updateTask(Activity $activity, array $engagement, array $metadata): void
{
$this->getClient()->deleteEngagement($activity->getCrmProviderId());
$this->logCrmEngagementManipulation(
self::ACTION_DELETE,
['crmId' => $activity->getCrmProviderId()],
);
$this->createActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createActivity(Activity $activity, array $engagement, array $metadata): void
{
$associations = $this->getActivityAssociations($activity);
$associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);
if ($activity->isTypeConference()) {
try {
$hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);
$activityId = $hsEngagement->data->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
} else {
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
$activityId = $hsEngagement->data->engagement->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
}
}
private function convertActivityMeetingAssociationsV4(Activity $activity): array
{
$associations = [];
$contactIds = $this->getParticipantsIds($activity);
foreach ($contactIds as $contactId) {
$associations[] = $this->buildAssociation(
(int) $contactId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]
);
}
if ($activity->hasAccount()) {
$crmProviderId = $activity->getAccount()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]
);
}
}
if ($activity->hasOpportunity()) {
$crmProviderId = $activity->getOpportunity()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]
);
}
}
return $associations;
}
private function buildAssociation(int $id, array $categoryToTypeIds): array
{
$types = [];
foreach ($categoryToTypeIds as $category => $typeId) {
$types[] = $this->buildAssociationType($category, $typeId);
}
return [
'to' => ['id' => (string) $id],
'types' => $types,
];
}
private function buildAssociationType(string $category, int $typeId): array
{
return [
'associationCategory' => $category,
'associationTypeId' => $typeId,
];
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createMeetingV3(array $engagement, array $associations, array $metadata): Response
{
$payload = $this->payloadBuilder->getV3MeetingPayload(
engagement: $engagement,
metadata: $metadata,
associations: $associations
);
return $this->getClient()->createMeeting($payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
private function buildSoftphonePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'CALL',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'toNumber' => $decorator->formatCallToParticipant($activity),
'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),
'status' => $decorator->convertCallStatus($activity),
'durationMilliseconds' => (int) $activity->getDuration() * 1000,
'body' => $decorator->generateDescription($activity),
];
// For now, this is just a "disposition" field to store Call Outcome.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildTextMessagePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'NOTE',
];
$metadata = [
'body' => $decorator->generateDescription($activity),
];
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function getActivityAssociations(Activity $activity): array
{
return [
'contactIds' => $this->getParticipantsIds($activity),
'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],
'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],
'ownerIds' => [],
];
}
/**
* Get DecorateActivity instance. Can be overridden in tests for mocking.
*/
protected function getDecorateActivity(): DecorateActivity
{
return new DecorateActivity();
}
public function saveFollowupActivity(Activity $activity, array $fields): ?string
{
if (empty($fields['subject'])) {
return null;
}
$associations = $this->getActivityAssociations($activity);
if (empty($fields['timestamp']) === false) {
$formatTimestamp = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['timestamp']);
$timestamp = $formatTimestamp->getTimestamp();
} else {
$timestamp = strtotime('+1 day');
}
$reminder = null;
if (empty($fields['reminder']) === false) {
$formatReminder = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['reminder']);
$reminder = $formatReminder->getTimestamp() * 1000;
}
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $timestamp * 1000,
'type' => 'TASK',
];
$metadata = [
'subject' => $fields['subject'],
'body' => '',
'status' => 'NOT_STARTED',
'taskType' => 'CALL',
'reminders' => $reminder ? [$reminder] : [],
];
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
} catch (BadRequest $e) {
$this->logger->info('[HubSpot] Failed to create followup task', [
'activity' => $activity->getUuid(),
'engagement' => $engagement,
'metadata' => $metadata,
'error' => $e->getMessage(),
]);
return null;
}
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
// We don't actually create a corresponding activity object on our side yet.
return (string) $hsEngagement->data->engagement->id;
}
private function logCrmEngagementManipulation(
string $action,
array $engagementData,
array $data = [],
array $associations = [],
): void {
$payload = ['team' => $this->team->getUuid()];
if ($action === self::ACTION_CREATE) {
$payload['engagementData'] = $engagementData;
$payload['data'] = $data;
$payload['associations'] = $associations;
}
if ($action === self::ACTION_UPDATE) {
$payload['engagementId'] = $engagementData['crmId'];
$payload['data'] = $data;
}
if ($action === self::ACTION_DELETE) {
$payload['engagementId'] = $engagementData['crmId'];
}
$this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
8
1
3
4
Previous Highlighted Error
Next Highlighted Error
SELECT
# DISTINCT
opp.id as opp_id, opp.uuid, opp.name,
# COUNT(dr.id) AS deal_risk_count,
# dr.id,
# cfd.value,
usr.name AS owner_name,
opp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,
usr.uuid as owner_uuid,
usr.photo_path as owner_photo,
usr.id AS owner_id,
jt.name as owner_job,
opp.stage_id, opp.stage_updated_at,
acc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,
rt.business_process_id AS pipeline_id
FROM opportunities opp
LEFT JOIN record_types rt ON opp.record_type_id = rt.id
LEFT JOIN users usr ON opp.user_id = usr.id
LEFT JOIN accounts acc ON opp.account_id = acc.id
LEFT JOIN job_titles jt ON usr.job_title_id = jt.id
# LEFT JOIN group_deal_risk_types gdrt ON gdrt.group_id = usr.group_id
# LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id AND dr.group_deal_risk_type_id = gdrt.id
LEFT JOIN crm_field_data cfd ON (cfd.object_id = opp.id and cfd.crm_field_id = 66814)
WHERE opp.user_id IS NOT NULL
AND opp.deleted_at IS NULL
AND opp.is_closed = 1
AND opp.is_won = 0
AND opp.close_date >= '2024-01-01 00:00:00'
AND opp.close_date <= '2024-12-31 23:59:59'
AND usr.team_id = 1
# and opp.id = 4823179
GROUP BY opp.id, cfd.value
ORDER BY
# cfd.value
CAST(cfd.value AS UNSIGNED)
# owner_name
# (SELECT COUNT(dr.id)
# FROM deal_risks dr
# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
# WHERE dr.opportunity_id = opp.id
# AND dr.is_active = 1
# AND gdrt.is_enabled = 1)
DESC
LIMIT 25
# OFFSET 0
;
select * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';
select * from crm_layout_entities where crm_layout_id = 1493;
SELECT * FROM opportunities WHERE id = 4584045;
SELECT * FROM crm_field_data WHERE object_id = 4584045;
SELECT * FROM opportunities where team_id = 1 and crm_provider_id = '0061K00000lfb9IQAQ'
SELECT
opp.id as opportunity_id,
opp.name,
COUNT(dr.id) as risk_count
FROM opportunities opp
LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id
WHERE opp.id IN (
6069574,8900938,9507638,9524799,9660490,9662824,9705675,9749758,4158460,4391812,6450439,4448422,5118945,5590675,4584045,7228149,9002408,9165534,9446720,9641778,9665149,9703344,9709280,5747948,4158491,6182565,6263970,5798120,5111315,4536978,6062352,6548383,6072095,6548225,4480986,5011422,6548381,8760540,8917554,9509986,9514392,9569009,9569011,9578490,9713604,5784397,7276612,7288405,8994421,9118219,9608148,9818911,4510535,6479693,5958049,6271674,6550448,4158331,4158483,6126571,6171615,6540943,4897466,5190896,5796182,5932762,8572433,8723698,8892697,9711001,9789264,4549188,6100831,6170064,6260799,6263653,6449936,6530871,6538978,7777651,8059269,8319918,8787049,8901150,9263153,9453207,9514738,9696700,9791062,5752018,6421452,7439134,7878923,9354763,9369285,9514396,9582506,9889949,9890216,4094311,4158495,4158496,6098128,5585661,3872564,6442149,5891604,6164746,6199593,6583474,6519684,9018490,9809006,4496897,5041324,5829430,6198319,6253504,6555763,7242914,7931055,8024125,8797814,8058559,8673347,8892695,8994420,9616219,9714970,9722004,9809439,9818918,6523177,8134147,9002915,9711422,9892713,9901719,9954210,9978435,5800810,6243518,6416114,6222251,6411974,6512456,5791953,6545606,9914780,5805540,6238986,6463838,6547680,9767049,9809437,9810885,9890855,6673493,9902036,4335521,6379871,6503799,6546077,8018765,9907556,9958433,9905855,9916179,9946741,9957877,5563344,6271838,6450815,7641128,7762567,7780592,8684810,8685786,8685787,8685788
)
GROUP BY opp.id;
SELECT COUNT(dr.id)
FROM deal_risks dr
JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
WHERE dr.opportunity_id = 5563344
# AND gdrt.group_id = usr.group_id
EXPLAIN SELECT
opp.id as opp_id, opp.uuid, opp.name,
cfd.value,
cfv.sequence,
cfv.value,
# MAX(cfd.value) AS max_cfd_value,
usr.name AS owner_name,
opp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,
usr.uuid as owner_uuid,
usr.photo_path as owner_photo,
usr.id AS owner_id,
jt.name as owner_job,
opp.stage_id, opp.stage_updated_at,
acc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,
rt.business_process_id AS pipeline_id
FROM opportunities opp
LEFT JOIN record_types rt ON opp.record_type_id = rt.id
LEFT JOIN users usr ON opp.user_id = usr.id
LEFT JOIN accounts acc ON opp.account_id = acc.id
LEFT JOIN job_titles jt ON usr.job_title_id = jt.id
LEFT JOIN crm_field_data cfd ON cfd.id = (SELECT sub_cfd.id
FROM crm_field_data sub_cfd
WHERE sub_cfd.object_id = opp.id
AND sub_cfd.crm_field_id = 66810
ORDER BY sub_cfd.updated_at DESC
LIMIT 1)
LEFT JOIN crm_field_values cfv ON cfv.crm_field_id = 66810 AND cfv.value = cfd.value
WHERE opp.user_id IS NOT NULL
AND opp.deleted_at IS NULL
AND opp.is_closed = 0
# AND opp.is_closed = 1
# AND opp.is_won = 0
AND opp.close_date >= '2024-01-01 00:00:00'
AND opp.close_date <= '2024-12-31 23:59:59'
AND usr.team_id = 1
# and opp.id = 4823179
# GROUP BY opp.id
ORDER BY
cfv.sequence DESC,
# opp.name
# owner_name
cfd.value
# CAST(cfd.value AS UNSIGNED)
# CAST(MAX(cfd.value) AS UNSIGNED)
# (SELECT COUNT(dr.id)
# FROM deal_risks dr
# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
# WHERE dr.opportunity_id = opp.id
# AND dr.is_active = 1 AND gdrt.is_enabled = 1)
DESC
# LIMIT 75, 25
;
SELECT * FROM crm_field_values WHERE crm_field_id = 66810;
SELECT f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value, fd.updated_at, fd.id, fd.created_at
FROM crm_fields f
INNER JOIN crm_field_data fd ON fd.crm_field_id = f.id
WHERE (f.crm_configuration_id = 1)
AND (f.object_type = 'opportunity')
# AND (fd.object_id IN (4158331,4158483,4158495,4158496,6069574,6263970,6171615,6540943,6545606,5829430,6198319,6263653,6548381,7242914,8797814,7228149,7439134,7878923,8134147,9002915,9118219,9165534,9354763,9369285,9446720))
AND (fd.object_id IN (4158460))
AND (f.crm_provider_id IN ('ForecastCategoryName'))
ORDER BY fd.object_id ASC, fd.updated_at DESC;
select * from crm_layouts where crm_configuration_id = 1;
select cf.* from crm_fields cf
join crm_layout_entities cle on cf.id = cle.crm_field_id
where crm_layout_id = 1493;
SELECT * FROM opportunities WHERE id = 4158460;
SELECT * FROM crm_field_data WHERE object_id = 4158460;
select * from users where team_id = 1;
SELECT * FROM users WHERE id = 7160;
select * from role_user where user_id = 3248;
select * from crm_field_values where crm_field_id = 66824;
SELECT * FROM users WHERE id = 23470;
select * from crm_configurations;
# [PASSWORD_DOTS]
select * from teams where id = 1038; # 23521, 966
select * from users where team_id = 1038;
select * from crm_configurations where id = 966;
SELECT * FROM activities WHERE uuid_to_bin('e8dc7c34-31f6-4430-bd81-2d9d8f00dd07') = uuid; # 54965762
SELECT * FROM activities WHERE uuid_to_bin('4b5727c0-69d1-428f-b8d9-b649055166e2') = uuid; # 54965764
SELECT * FROM participants WHERE activity_id = 54965764;
SELECT id, uuid, title, recording_state FROM activities WHERE uuid_to_bin('355f105d-5606-4379-b0c5-d91daf59f4ce') = uuid; # 54964075
SELECT * FROM participants WHERE activity_id = 54964075;
select f.id, slug, title, tf.team_id from team_features tf JOIN features f on tf.feature_id = f.id
where tf.team_id = 1038;
# [PASSWORD_DOTS] PD [PASSWORD_DOTS]
select * from teams where id = 1029;
SELECT * FROM crm_configurations WHERE id = 957;
SELECT * FROM activities WHERE uuid_to_bin('581a33cb-4343-44bd-ac0c-4a5cee71c5ec') = uuid; # 54963423
SELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433
SELECT * FROM activities WHERE uuid_to_bin('8b6bb2f7-e7a7-4aec-8806-fbfea7093ccd') = uuid; # 54963421
# [PASSWORD_DOTS] Close [PASSWORD_DOTS]
select * from teams where id = 1031;
SELECT * FROM crm_configurations WHERE id = 959;
SELECT * FROM activities WHERE uuid_to_bin('59c517e0-96c9-4dad-bfeb-b7bef72f8725') = uuid; # 54963517
SELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433
SELECT * FROM activities WHERE uuid_to_bin('d68311e3-ac34-49bd-bf04-88323a7e5352') = uuid; # 54966009
# [PASSWORD_DOTS] SF [PASSWORD_DOTS]
SELECT * FROM activities WHERE uuid_to_bin('2bd4cfa3-4eb5-4c77-84a7-9ed46a21c988') = uuid;
select * from roles;
select * from permissions;
select * from permission_role where permission_id = 136;
select * from migrations order by id desc;
select * from teams where id IN (1, 1037);
select * from crm_layouts where crm_configuration_id = 1;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 1493;
SELECT * FROM crm_fields WHERE id IN (1652,1661,66799,66814,66821,66836,66843,66846,66864,84752,182306,323580,380378);
select * from features;
select * from team_features where feature_id = 33;
select * from opportunities;
select * from teams;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1052 and sa.provider = 'hubspot';
SELECT * FROM accounts where id = 11512582;
select * from activities where account_id = 11512582;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.054166667,"top":0.027777778,"width":0.08055556,"height":0.035555556},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#12066 on JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.13472222,"top":0.027777778,"width":0.25555557,"height":0.035555556},"on_screen":true,"help_text":"Pull request #12066 exists for current branch JY-20725-handle-HS-search-rate-limit, 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.6326389,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HandleHubspotRateLimitTest","depth":6,"bounds":{"left":0.6645833,"top":0.027777778,"width":0.15902779,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.82361114,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.8472222,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.87083334,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.9291667,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9527778,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9763889,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"8","depth":4,"bounds":{"left":0.78055555,"top":0.2777778,"width":0.016666668,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.80138886,"top":0.2777778,"width":0.016666668,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.8215278,"top":0.27555555,"width":0.015277778,"height":0.025555555},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.8368056,"top":0.27555555,"width":0.014583333,"height":0.025555555},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldDataRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DecorateActivity;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationCategoryV4;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationTypeIdV4;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse Sentry;\n\ntrait WriteCrmTrait\n{\n private const string ACTION_CREATE = 'create';\n private const string ACTION_UPDATE = 'update';\n private const string ACTION_DELETE = 'delete';\n\n /**\n * @inheritdoc\n */\n public function saveActivity(Activity $activity): Activity\n {\n switch ($activity->getCrmType()) {\n case Activity::TYPE_CONFERENCE:\n $activity = $this->buildConferencePayload($activity);\n\n break;\n case Activity::TYPE_SOFTPHONE:\n case Activity::TYPE_SOFTPHONE_INBOUND:\n $activity = $this->buildSoftphonePayload($activity);\n\n break;\n\n case Activity::TYPE_SMS_INBOUND:\n case Activity::TYPE_SMS_OUTBOUND:\n $activity = $this->buildTextMessagePayload($activity);\n\n break;\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildConferencePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatStartTime($activity),\n 'type' => 'MEETING',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'meetingOutcome' => $decorator->convertMeetingOutcome($activity),\n 'internalMeetingNotes' => $decorator->generateDescription($activity),\n ];\n\n // Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.\n if (! $activity->hasCrmProviderId()) {\n if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {\n $metadata['startTime'] = $decorator->formatStartTime($activity);\n $metadata['endTime'] = $decorator->formatEndTime($activity);\n }\n $metadata['title'] = $decorator->generateTitle($activity);\n }\n\n // For now, this is just a \"meetingOutcome\" field for some customers.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function fetchCustomFieldData(Activity $activity, string $objectType): array\n {\n $payload = [];\n\n $fieldDataRepository = app(FieldDataRepository::class);\n $fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);\n\n foreach ($fieldData as $data) {\n // Add the field and value to the payload.\n $payload += [\n $data->getField()->getCrmProviderId() => $data->getValue(),\n ];\n }\n\n return $payload;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity\n {\n if (! $activity->hasCrmProviderId()) {\n $this->createActivity($activity, $engagement, $metadata);\n\n return $activity;\n }\n\n try {\n $oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());\n } catch (HubspotException $e) {\n $this->logger->warning('[HubSpot] Engagement not found', [\n 'activity_id' => $activity->getUuid(),\n 'crm_provider_id' => $activity->getCrmProviderId(),\n 'exception' => $e->getMessage(),\n ]);\n\n // potentially here we can log new activity if it is not found\n\n return $activity;\n }\n\n $type = $oldEngagement['engagement']['type'] ?? null;\n $bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';\n\n $decorator = $this->getDecorateActivity();\n\n if ($type !== 'TASK') {\n $oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldDescription\n );\n\n $oldAssociations = $oldEngagement['associations'] ?? [];\n $this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);\n } else {\n $oldTaskBody = $oldEngagement['metadata']['body'] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldTaskBody\n );\n $this->updateTask($activity, $engagement, $metadata);\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateNonTaskActivity(\n Activity $activity,\n array $engagement,\n array $metadata,\n array $oldAssociations\n ): void {\n if ($activity->isTypeConference()) {\n $this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);\n } else {\n $this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);\n }\n\n $this->updateActivityAssociations($activity, $oldAssociations);\n\n $this->logCrmEngagementManipulation(\n self::ACTION_UPDATE,\n ['crmId' => $activity->getCrmProviderId()],\n $metadata,\n );\n }\n\n private function updateActivityAssociations(Activity $activity, array $oldAssociations): void\n {\n $currentAssociations = $this->getActivityAssociations($activity);\n $associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);\n\n // No changes - exit\n if (empty($associationChanges)) {\n return;\n }\n\n $this->logger->info('[HubSpot] Updating associations', [\n 'team' => $this->team->getUuid(),\n 'activity' => $activity->getUuid(),\n 'associationChanges' => $associationChanges,\n ]);\n\n $this->updateAssociations($activity, $associationChanges);\n }\n\n private function getAssociationDifferences(\n Activity $activity,\n array $oldAssociations,\n array $currentAssociations\n ): array {\n $objects = ['contactIds', 'companyIds', 'dealIds'];\n $changes = [];\n\n foreach ($objects as $object) {\n $oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);\n $currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);\n\n $added = array_diff($currentIds, $oldIds);\n $removed = array_diff($oldIds, $currentIds);\n\n $type = match ($object) {\n 'contactIds' => 'contact',\n 'companyIds' => 'company',\n 'dealIds' => 'deal',\n };\n\n $shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);\n\n if (! empty($removed) && ! $shouldIncludeRemoved) {\n $this->logger->info('[HubSpot] Skipping association removal', [\n 'team' => $this->team->getUuid(),\n 'type' => $type,\n 'oldIds' => $oldIds,\n 'currentIds' => $currentIds,\n 'skippedRemovals' => array_values($removed),\n ]);\n }\n\n if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {\n $changes[$type] = [\n 'added' => array_values($added),\n 'removed' => array_values($removed),\n ];\n }\n }\n\n return $changes;\n }\n\n /**\n * Determines whether removed associations should be synced to HubSpot.\n *\n * For contacts and companies, we always sync removals (original behavior).\n * For deals, we only sync removals when:\n * - There are new deals being added (replacement scenario), OR\n * - The activity has an account associated (indicating intentional association management)\n *\n * This prevents accidentally removing deal associations that exist in HubSpot\n * but not in Jiminny's database (e.g., deals manually added by users in HubSpot).\n */\n private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool\n {\n // Contacts and companies: always sync removals\n if ($type !== 'deal') {\n return true;\n }\n\n // Deals: sync removals if we're adding new deals (replacement)\n if (! empty($added)) {\n return true;\n }\n\n // Deals: sync removals only if activity has an account (intentional management)\n return $activity->hasAccount();\n }\n\n private function normalizeIds(array $ids): array\n {\n $stringIds = array_map('strval', $ids);\n $uniqueIds = array_unique($stringIds);\n sort($uniqueIds);\n\n return $uniqueIds;\n }\n\n private function updateAssociations(Activity $activity, array $associationChanges): void\n {\n $objectType = $activity->isTypeConference() ? 'meeting' : 'call';\n\n foreach ($associationChanges as $type => $changes) {\n if (! empty($changes['added'])) {\n $associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);\n $payload = $this->payloadBuilder->buildAddAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['added'],\n $associationType\n );\n $this->getClient()->addAssociations($objectType, $type, $payload);\n }\n\n if (! empty($changes['removed'])) {\n $payload = $this->payloadBuilder->buildRemoveAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['removed']\n );\n $this->getClient()->removeAssociations($objectType, $type, $payload);\n }\n }\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response\n {\n // If already set by us/client, do not override it\n if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {\n $this->logger->info('[HubSpot] Meeting activity type already set', [\n 'meetingId' => $meetingId,\n 'activityType' => $engagement['activityType'],\n ]);\n\n unset($engagement['activityType']);\n }\n\n $payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);\n\n return $this->getClient()->updateMeeting($meetingId, $payload);\n }\n\n private function hasEngagementActivityType(string $engagementId): bool\n {\n try {\n $engagementData = $this->getClient()->getEngagementData($engagementId);\n if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {\n return true;\n }\n\n return false;\n } catch (\\Exception $exception) {\n Sentry::captureException($exception);\n\n $this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [\n 'engagementId' => $engagementId,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n return true;\n }\n\n private function updateTask(Activity $activity, array $engagement, array $metadata): void\n {\n $this->getClient()->deleteEngagement($activity->getCrmProviderId());\n\n $this->logCrmEngagementManipulation(\n self::ACTION_DELETE,\n ['crmId' => $activity->getCrmProviderId()],\n );\n\n $this->createActivity($activity, $engagement, $metadata);\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createActivity(Activity $activity, array $engagement, array $metadata): void\n {\n $associations = $this->getActivityAssociations($activity);\n $associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);\n\n if ($activity->isTypeConference()) {\n try {\n $hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);\n $activityId = $hsEngagement->data->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n } else {\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n $activityId = $hsEngagement->data->engagement->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n }\n }\n\n private function convertActivityMeetingAssociationsV4(Activity $activity): array\n {\n $associations = [];\n\n $contactIds = $this->getParticipantsIds($activity);\n\n foreach ($contactIds as $contactId) {\n $associations[] = $this->buildAssociation(\n (int) $contactId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]\n );\n }\n\n if ($activity->hasAccount()) {\n $crmProviderId = $activity->getAccount()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]\n );\n }\n }\n\n if ($activity->hasOpportunity()) {\n $crmProviderId = $activity->getOpportunity()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]\n );\n }\n }\n\n return $associations;\n }\n\n private function buildAssociation(int $id, array $categoryToTypeIds): array\n {\n $types = [];\n\n foreach ($categoryToTypeIds as $category => $typeId) {\n $types[] = $this->buildAssociationType($category, $typeId);\n }\n\n return [\n 'to' => ['id' => (string) $id],\n 'types' => $types,\n ];\n }\n\n private function buildAssociationType(string $category, int $typeId): array\n {\n return [\n 'associationCategory' => $category,\n 'associationTypeId' => $typeId,\n ];\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createMeetingV3(array $engagement, array $associations, array $metadata): Response\n {\n $payload = $this->payloadBuilder->getV3MeetingPayload(\n engagement: $engagement,\n metadata: $metadata,\n associations: $associations\n );\n\n return $this->getClient()->createMeeting($payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n private function buildSoftphonePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'CALL',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'toNumber' => $decorator->formatCallToParticipant($activity),\n 'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),\n 'status' => $decorator->convertCallStatus($activity),\n 'durationMilliseconds' => (int) $activity->getDuration() * 1000,\n 'body' => $decorator->generateDescription($activity),\n ];\n\n // For now, this is just a \"disposition\" field to store Call Outcome.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildTextMessagePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'NOTE',\n ];\n\n $metadata = [\n 'body' => $decorator->generateDescription($activity),\n ];\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function getActivityAssociations(Activity $activity): array\n {\n return [\n 'contactIds' => $this->getParticipantsIds($activity),\n 'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],\n 'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],\n 'ownerIds' => [],\n ];\n }\n\n /**\n * Get DecorateActivity instance. Can be overridden in tests for mocking.\n */\n protected function getDecorateActivity(): DecorateActivity\n {\n return new DecorateActivity();\n }\n\n public function saveFollowupActivity(Activity $activity, array $fields): ?string\n {\n if (empty($fields['subject'])) {\n return null;\n }\n\n $associations = $this->getActivityAssociations($activity);\n\n if (empty($fields['timestamp']) === false) {\n $formatTimestamp = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['timestamp']);\n $timestamp = $formatTimestamp->getTimestamp();\n } else {\n $timestamp = strtotime('+1 day');\n }\n\n $reminder = null;\n if (empty($fields['reminder']) === false) {\n $formatReminder = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['reminder']);\n $reminder = $formatReminder->getTimestamp() * 1000;\n }\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $timestamp * 1000,\n 'type' => 'TASK',\n ];\n\n $metadata = [\n 'subject' => $fields['subject'],\n 'body' => '',\n 'status' => 'NOT_STARTED',\n 'taskType' => 'CALL',\n 'reminders' => $reminder ? [$reminder] : [],\n ];\n\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n } catch (BadRequest $e) {\n $this->logger->info('[HubSpot] Failed to create followup task', [\n 'activity' => $activity->getUuid(),\n 'engagement' => $engagement,\n 'metadata' => $metadata,\n 'error' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n // We don't actually create a corresponding activity object on our side yet.\n return (string) $hsEngagement->data->engagement->id;\n }\n\n private function logCrmEngagementManipulation(\n string $action,\n array $engagementData,\n array $data = [],\n array $associations = [],\n ): void {\n $payload = ['team' => $this->team->getUuid()];\n\n if ($action === self::ACTION_CREATE) {\n $payload['engagementData'] = $engagementData;\n $payload['data'] = $data;\n $payload['associations'] = $associations;\n }\n\n if ($action === self::ACTION_UPDATE) {\n $payload['engagementId'] = $engagementData['crmId'];\n $payload['data'] = $data;\n }\n\n if ($action === self::ACTION_DELETE) {\n $payload['engagementId'] = $engagementData['crmId'];\n }\n\n $this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldDataRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DecorateActivity;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationCategoryV4;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationTypeIdV4;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse Sentry;\n\ntrait WriteCrmTrait\n{\n private const string ACTION_CREATE = 'create';\n private const string ACTION_UPDATE = 'update';\n private const string ACTION_DELETE = 'delete';\n\n /**\n * @inheritdoc\n */\n public function saveActivity(Activity $activity): Activity\n {\n switch ($activity->getCrmType()) {\n case Activity::TYPE_CONFERENCE:\n $activity = $this->buildConferencePayload($activity);\n\n break;\n case Activity::TYPE_SOFTPHONE:\n case Activity::TYPE_SOFTPHONE_INBOUND:\n $activity = $this->buildSoftphonePayload($activity);\n\n break;\n\n case Activity::TYPE_SMS_INBOUND:\n case Activity::TYPE_SMS_OUTBOUND:\n $activity = $this->buildTextMessagePayload($activity);\n\n break;\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildConferencePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatStartTime($activity),\n 'type' => 'MEETING',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'meetingOutcome' => $decorator->convertMeetingOutcome($activity),\n 'internalMeetingNotes' => $decorator->generateDescription($activity),\n ];\n\n // Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.\n if (! $activity->hasCrmProviderId()) {\n if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {\n $metadata['startTime'] = $decorator->formatStartTime($activity);\n $metadata['endTime'] = $decorator->formatEndTime($activity);\n }\n $metadata['title'] = $decorator->generateTitle($activity);\n }\n\n // For now, this is just a \"meetingOutcome\" field for some customers.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function fetchCustomFieldData(Activity $activity, string $objectType): array\n {\n $payload = [];\n\n $fieldDataRepository = app(FieldDataRepository::class);\n $fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);\n\n foreach ($fieldData as $data) {\n // Add the field and value to the payload.\n $payload += [\n $data->getField()->getCrmProviderId() => $data->getValue(),\n ];\n }\n\n return $payload;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity\n {\n if (! $activity->hasCrmProviderId()) {\n $this->createActivity($activity, $engagement, $metadata);\n\n return $activity;\n }\n\n try {\n $oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());\n } catch (HubspotException $e) {\n $this->logger->warning('[HubSpot] Engagement not found', [\n 'activity_id' => $activity->getUuid(),\n 'crm_provider_id' => $activity->getCrmProviderId(),\n 'exception' => $e->getMessage(),\n ]);\n\n // potentially here we can log new activity if it is not found\n\n return $activity;\n }\n\n $type = $oldEngagement['engagement']['type'] ?? null;\n $bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';\n\n $decorator = $this->getDecorateActivity();\n\n if ($type !== 'TASK') {\n $oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldDescription\n );\n\n $oldAssociations = $oldEngagement['associations'] ?? [];\n $this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);\n } else {\n $oldTaskBody = $oldEngagement['metadata']['body'] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldTaskBody\n );\n $this->updateTask($activity, $engagement, $metadata);\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateNonTaskActivity(\n Activity $activity,\n array $engagement,\n array $metadata,\n array $oldAssociations\n ): void {\n if ($activity->isTypeConference()) {\n $this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);\n } else {\n $this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);\n }\n\n $this->updateActivityAssociations($activity, $oldAssociations);\n\n $this->logCrmEngagementManipulation(\n self::ACTION_UPDATE,\n ['crmId' => $activity->getCrmProviderId()],\n $metadata,\n );\n }\n\n private function updateActivityAssociations(Activity $activity, array $oldAssociations): void\n {\n $currentAssociations = $this->getActivityAssociations($activity);\n $associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);\n\n // No changes - exit\n if (empty($associationChanges)) {\n return;\n }\n\n $this->logger->info('[HubSpot] Updating associations', [\n 'team' => $this->team->getUuid(),\n 'activity' => $activity->getUuid(),\n 'associationChanges' => $associationChanges,\n ]);\n\n $this->updateAssociations($activity, $associationChanges);\n }\n\n private function getAssociationDifferences(\n Activity $activity,\n array $oldAssociations,\n array $currentAssociations\n ): array {\n $objects = ['contactIds', 'companyIds', 'dealIds'];\n $changes = [];\n\n foreach ($objects as $object) {\n $oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);\n $currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);\n\n $added = array_diff($currentIds, $oldIds);\n $removed = array_diff($oldIds, $currentIds);\n\n $type = match ($object) {\n 'contactIds' => 'contact',\n 'companyIds' => 'company',\n 'dealIds' => 'deal',\n };\n\n $shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);\n\n if (! empty($removed) && ! $shouldIncludeRemoved) {\n $this->logger->info('[HubSpot] Skipping association removal', [\n 'team' => $this->team->getUuid(),\n 'type' => $type,\n 'oldIds' => $oldIds,\n 'currentIds' => $currentIds,\n 'skippedRemovals' => array_values($removed),\n ]);\n }\n\n if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {\n $changes[$type] = [\n 'added' => array_values($added),\n 'removed' => array_values($removed),\n ];\n }\n }\n\n return $changes;\n }\n\n /**\n * Determines whether removed associations should be synced to HubSpot.\n *\n * For contacts and companies, we always sync removals (original behavior).\n * For deals, we only sync removals when:\n * - There are new deals being added (replacement scenario), OR\n * - The activity has an account associated (indicating intentional association management)\n *\n * This prevents accidentally removing deal associations that exist in HubSpot\n * but not in Jiminny's database (e.g., deals manually added by users in HubSpot).\n */\n private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool\n {\n // Contacts and companies: always sync removals\n if ($type !== 'deal') {\n return true;\n }\n\n // Deals: sync removals if we're adding new deals (replacement)\n if (! empty($added)) {\n return true;\n }\n\n // Deals: sync removals only if activity has an account (intentional management)\n return $activity->hasAccount();\n }\n\n private function normalizeIds(array $ids): array\n {\n $stringIds = array_map('strval', $ids);\n $uniqueIds = array_unique($stringIds);\n sort($uniqueIds);\n\n return $uniqueIds;\n }\n\n private function updateAssociations(Activity $activity, array $associationChanges): void\n {\n $objectType = $activity->isTypeConference() ? 'meeting' : 'call';\n\n foreach ($associationChanges as $type => $changes) {\n if (! empty($changes['added'])) {\n $associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);\n $payload = $this->payloadBuilder->buildAddAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['added'],\n $associationType\n );\n $this->getClient()->addAssociations($objectType, $type, $payload);\n }\n\n if (! empty($changes['removed'])) {\n $payload = $this->payloadBuilder->buildRemoveAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['removed']\n );\n $this->getClient()->removeAssociations($objectType, $type, $payload);\n }\n }\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response\n {\n // If already set by us/client, do not override it\n if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {\n $this->logger->info('[HubSpot] Meeting activity type already set', [\n 'meetingId' => $meetingId,\n 'activityType' => $engagement['activityType'],\n ]);\n\n unset($engagement['activityType']);\n }\n\n $payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);\n\n return $this->getClient()->updateMeeting($meetingId, $payload);\n }\n\n private function hasEngagementActivityType(string $engagementId): bool\n {\n try {\n $engagementData = $this->getClient()->getEngagementData($engagementId);\n if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {\n return true;\n }\n\n return false;\n } catch (\\Exception $exception) {\n Sentry::captureException($exception);\n\n $this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [\n 'engagementId' => $engagementId,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n return true;\n }\n\n private function updateTask(Activity $activity, array $engagement, array $metadata): void\n {\n $this->getClient()->deleteEngagement($activity->getCrmProviderId());\n\n $this->logCrmEngagementManipulation(\n self::ACTION_DELETE,\n ['crmId' => $activity->getCrmProviderId()],\n );\n\n $this->createActivity($activity, $engagement, $metadata);\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createActivity(Activity $activity, array $engagement, array $metadata): void\n {\n $associations = $this->getActivityAssociations($activity);\n $associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);\n\n if ($activity->isTypeConference()) {\n try {\n $hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);\n $activityId = $hsEngagement->data->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n } else {\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n $activityId = $hsEngagement->data->engagement->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n }\n }\n\n private function convertActivityMeetingAssociationsV4(Activity $activity): array\n {\n $associations = [];\n\n $contactIds = $this->getParticipantsIds($activity);\n\n foreach ($contactIds as $contactId) {\n $associations[] = $this->buildAssociation(\n (int) $contactId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]\n );\n }\n\n if ($activity->hasAccount()) {\n $crmProviderId = $activity->getAccount()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]\n );\n }\n }\n\n if ($activity->hasOpportunity()) {\n $crmProviderId = $activity->getOpportunity()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]\n );\n }\n }\n\n return $associations;\n }\n\n private function buildAssociation(int $id, array $categoryToTypeIds): array\n {\n $types = [];\n\n foreach ($categoryToTypeIds as $category => $typeId) {\n $types[] = $this->buildAssociationType($category, $typeId);\n }\n\n return [\n 'to' => ['id' => (string) $id],\n 'types' => $types,\n ];\n }\n\n private function buildAssociationType(string $category, int $typeId): array\n {\n return [\n 'associationCategory' => $category,\n 'associationTypeId' => $typeId,\n ];\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createMeetingV3(array $engagement, array $associations, array $metadata): Response\n {\n $payload = $this->payloadBuilder->getV3MeetingPayload(\n engagement: $engagement,\n metadata: $metadata,\n associations: $associations\n );\n\n return $this->getClient()->createMeeting($payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n private function buildSoftphonePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'CALL',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'toNumber' => $decorator->formatCallToParticipant($activity),\n 'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),\n 'status' => $decorator->convertCallStatus($activity),\n 'durationMilliseconds' => (int) $activity->getDuration() * 1000,\n 'body' => $decorator->generateDescription($activity),\n ];\n\n // For now, this is just a \"disposition\" field to store Call Outcome.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildTextMessagePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'NOTE',\n ];\n\n $metadata = [\n 'body' => $decorator->generateDescription($activity),\n ];\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function getActivityAssociations(Activity $activity): array\n {\n return [\n 'contactIds' => $this->getParticipantsIds($activity),\n 'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],\n 'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],\n 'ownerIds' => [],\n ];\n }\n\n /**\n * Get DecorateActivity instance. Can be overridden in tests for mocking.\n */\n protected function getDecorateActivity(): DecorateActivity\n {\n return new DecorateActivity();\n }\n\n public function saveFollowupActivity(Activity $activity, array $fields): ?string\n {\n if (empty($fields['subject'])) {\n return null;\n }\n\n $associations = $this->getActivityAssociations($activity);\n\n if (empty($fields['timestamp']) === false) {\n $formatTimestamp = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['timestamp']);\n $timestamp = $formatTimestamp->getTimestamp();\n } else {\n $timestamp = strtotime('+1 day');\n }\n\n $reminder = null;\n if (empty($fields['reminder']) === false) {\n $formatReminder = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['reminder']);\n $reminder = $formatReminder->getTimestamp() * 1000;\n }\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $timestamp * 1000,\n 'type' => 'TASK',\n ];\n\n $metadata = [\n 'subject' => $fields['subject'],\n 'body' => '',\n 'status' => 'NOT_STARTED',\n 'taskType' => 'CALL',\n 'reminders' => $reminder ? [$reminder] : [],\n ];\n\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n } catch (BadRequest $e) {\n $this->logger->info('[HubSpot] Failed to create followup task', [\n 'activity' => $activity->getUuid(),\n 'engagement' => $engagement,\n 'metadata' => $metadata,\n 'error' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n // We don't actually create a corresponding activity object on our side yet.\n return (string) $hsEngagement->data->engagement->id;\n }\n\n private function logCrmEngagementManipulation(\n string $action,\n array $engagementData,\n array $data = [],\n array $associations = [],\n ): void {\n $payload = ['team' => $this->team->getUuid()];\n\n if ($action === self::ACTION_CREATE) {\n $payload['engagementData'] = $engagementData;\n $payload['data'] = $data;\n $payload['associations'] = $associations;\n }\n\n if ($action === self::ACTION_UPDATE) {\n $payload['engagementId'] = $engagementData['crmId'];\n $payload['data'] = $data;\n }\n\n if ($action === self::ACTION_DELETE) {\n $payload['engagementId'] = $engagementData['crmId'];\n }\n\n $this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.85486114,"top":0.31,"width":0.018055556,"height":0.026666667},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"bounds":{"left":0.87291664,"top":0.31,"width":0.018055556,"height":0.026666667},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"bounds":{"left":0.9138889,"top":0.31,"width":0.059027776,"height":0.026666667},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"8","depth":4,"bounds":{"left":0.8645833,"top":0.34444445,"width":0.016666668,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.8854167,"top":0.34444445,"width":0.015277778,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.9048611,"top":0.34444445,"width":0.016666668,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"4","depth":4,"bounds":{"left":0.92569447,"top":0.34444445,"width":0.016666668,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.9458333,"top":0.3422222,"width":0.015277778,"height":0.025555555},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.9611111,"top":0.3422222,"width":0.014583333,"height":0.025555555},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"SELECT\n# DISTINCT\nopp.id as opp_id, opp.uuid, opp.name,\n# COUNT(dr.id) AS deal_risk_count,\n\n# dr.id,\n# cfd.value,\nusr.name AS owner_name,\nopp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,\nusr.uuid as owner_uuid,\nusr.photo_path as owner_photo,\nusr.id AS owner_id,\njt.name as owner_job,\nopp.stage_id, opp.stage_updated_at,\nacc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,\nrt.business_process_id AS pipeline_id\nFROM opportunities opp\nLEFT JOIN record_types rt ON opp.record_type_id = rt.id\nLEFT JOIN users usr ON opp.user_id = usr.id\nLEFT JOIN accounts acc ON opp.account_id = acc.id\nLEFT JOIN job_titles jt ON usr.job_title_id = jt.id\n\n# LEFT JOIN group_deal_risk_types gdrt ON gdrt.group_id = usr.group_id\n# LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id AND dr.group_deal_risk_type_id = gdrt.id\n\nLEFT JOIN crm_field_data cfd ON (cfd.object_id = opp.id and cfd.crm_field_id = 66814)\n\nWHERE opp.user_id IS NOT NULL\nAND opp.deleted_at IS NULL\nAND opp.is_closed = 1\nAND opp.is_won = 0\nAND opp.close_date >= '2024-01-01 00:00:00'\nAND opp.close_date <= '2024-12-31 23:59:59'\nAND usr.team_id = 1\n\n# and opp.id = 4823179\n\nGROUP BY opp.id, cfd.value\nORDER BY\n# cfd.value\n CAST(cfd.value AS UNSIGNED)\n# owner_name\n# (SELECT COUNT(dr.id)\n# FROM deal_risks dr\n# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n# WHERE dr.opportunity_id = opp.id\n# AND dr.is_active = 1\n# AND gdrt.is_enabled = 1)\nDESC\nLIMIT 25\n# OFFSET 0\n;\n\nselect * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';\nselect * from crm_layout_entities where crm_layout_id = 1493;\n\nSELECT * FROM opportunities WHERE id = 4584045;\nSELECT * FROM crm_field_data WHERE object_id = 4584045;\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id = '0061K00000lfb9IQAQ'\n\nSELECT\n opp.id as opportunity_id,\n opp.name,\n COUNT(dr.id) as risk_count\nFROM opportunities opp\nLEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id\nWHERE opp.id IN (\n 6069574,8900938,9507638,9524799,9660490,9662824,9705675,9749758,4158460,4391812,6450439,4448422,5118945,5590675,4584045,7228149,9002408,9165534,9446720,9641778,9665149,9703344,9709280,5747948,4158491,6182565,6263970,5798120,5111315,4536978,6062352,6548383,6072095,6548225,4480986,5011422,6548381,8760540,8917554,9509986,9514392,9569009,9569011,9578490,9713604,5784397,7276612,7288405,8994421,9118219,9608148,9818911,4510535,6479693,5958049,6271674,6550448,4158331,4158483,6126571,6171615,6540943,4897466,5190896,5796182,5932762,8572433,8723698,8892697,9711001,9789264,4549188,6100831,6170064,6260799,6263653,6449936,6530871,6538978,7777651,8059269,8319918,8787049,8901150,9263153,9453207,9514738,9696700,9791062,5752018,6421452,7439134,7878923,9354763,9369285,9514396,9582506,9889949,9890216,4094311,4158495,4158496,6098128,5585661,3872564,6442149,5891604,6164746,6199593,6583474,6519684,9018490,9809006,4496897,5041324,5829430,6198319,6253504,6555763,7242914,7931055,8024125,8797814,8058559,8673347,8892695,8994420,9616219,9714970,9722004,9809439,9818918,6523177,8134147,9002915,9711422,9892713,9901719,9954210,9978435,5800810,6243518,6416114,6222251,6411974,6512456,5791953,6545606,9914780,5805540,6238986,6463838,6547680,9767049,9809437,9810885,9890855,6673493,9902036,4335521,6379871,6503799,6546077,8018765,9907556,9958433,9905855,9916179,9946741,9957877,5563344,6271838,6450815,7641128,7762567,7780592,8684810,8685786,8685787,8685788\n)\nGROUP BY opp.id;\n\nSELECT COUNT(dr.id)\n FROM deal_risks dr\n JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n WHERE dr.opportunity_id = 5563344\n# AND gdrt.group_id = usr.group_id\n\n\n\nEXPLAIN SELECT\n opp.id as opp_id, opp.uuid, opp.name,\n\ncfd.value,\ncfv.sequence,\ncfv.value,\n# MAX(cfd.value) AS max_cfd_value,\nusr.name AS owner_name,\nopp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,\nusr.uuid as owner_uuid,\nusr.photo_path as owner_photo,\nusr.id AS owner_id,\njt.name as owner_job,\nopp.stage_id, opp.stage_updated_at,\nacc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,\nrt.business_process_id AS pipeline_id\nFROM opportunities opp\n\nLEFT JOIN record_types rt ON opp.record_type_id = rt.id\nLEFT JOIN users usr ON opp.user_id = usr.id\nLEFT JOIN accounts acc ON opp.account_id = acc.id\nLEFT JOIN job_titles jt ON usr.job_title_id = jt.id\n\nLEFT JOIN crm_field_data cfd ON cfd.id = (SELECT sub_cfd.id\n FROM crm_field_data sub_cfd\n WHERE sub_cfd.object_id = opp.id\n AND sub_cfd.crm_field_id = 66810\n ORDER BY sub_cfd.updated_at DESC\n LIMIT 1)\n\nLEFT JOIN crm_field_values cfv ON cfv.crm_field_id = 66810 AND cfv.value = cfd.value\n\nWHERE opp.user_id IS NOT NULL\nAND opp.deleted_at IS NULL\nAND opp.is_closed = 0\n# AND opp.is_closed = 1\n# AND opp.is_won = 0\nAND opp.close_date >= '2024-01-01 00:00:00'\nAND opp.close_date <= '2024-12-31 23:59:59'\nAND usr.team_id = 1\n\n# and opp.id = 4823179\n\n# GROUP BY opp.id\nORDER BY\n cfv.sequence DESC,\n# opp.name\n# owner_name\n cfd.value\n# CAST(cfd.value AS UNSIGNED)\n# CAST(MAX(cfd.value) AS UNSIGNED)\n# (SELECT COUNT(dr.id)\n# FROM deal_risks dr\n# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n# WHERE dr.opportunity_id = opp.id\n# AND dr.is_active = 1 AND gdrt.is_enabled = 1)\nDESC\n# LIMIT 75, 25\n;\n\n\n\nSELECT * FROM crm_field_values WHERE crm_field_id = 66810;\n\nSELECT f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value, fd.updated_at, fd.id, fd.created_at\nFROM crm_fields f\nINNER JOIN crm_field_data fd ON fd.crm_field_id = f.id\n WHERE (f.crm_configuration_id = 1)\n AND (f.object_type = 'opportunity')\n# AND (fd.object_id IN (4158331,4158483,4158495,4158496,6069574,6263970,6171615,6540943,6545606,5829430,6198319,6263653,6548381,7242914,8797814,7228149,7439134,7878923,8134147,9002915,9118219,9165534,9354763,9369285,9446720))\n AND (fd.object_id IN (4158460))\n AND (f.crm_provider_id IN ('ForecastCategoryName'))\n ORDER BY fd.object_id ASC, fd.updated_at DESC;\n\nselect * from crm_layouts where crm_configuration_id = 1;\nselect cf.* from crm_fields cf\njoin crm_layout_entities cle on cf.id = cle.crm_field_id\nwhere crm_layout_id = 1493;\n\nSELECT * FROM opportunities WHERE id = 4158460;\nSELECT * FROM crm_field_data WHERE object_id = 4158460;\n\nselect * from users where team_id = 1;\nSELECT * FROM users WHERE id = 7160;\nselect * from role_user where user_id = 3248;\n\nselect * from crm_field_values where crm_field_id = 66824;\n\nSELECT * FROM users WHERE id = 23470;\nselect * from crm_configurations;\n\n# ******************************************\nselect * from teams where id = 1038; # 23521, 966\nselect * from users where team_id = 1038;\nselect * from crm_configurations where id = 966;\nSELECT * FROM activities WHERE uuid_to_bin('e8dc7c34-31f6-4430-bd81-2d9d8f00dd07') = uuid; # 54965762\nSELECT * FROM activities WHERE uuid_to_bin('4b5727c0-69d1-428f-b8d9-b649055166e2') = uuid; # 54965764\nSELECT * FROM participants WHERE activity_id = 54965764;\n\nSELECT id, uuid, title, recording_state FROM activities WHERE uuid_to_bin('355f105d-5606-4379-b0c5-d91daf59f4ce') = uuid; # 54964075\nSELECT * FROM participants WHERE activity_id = 54964075;\nselect f.id, slug, title, tf.team_id from team_features tf JOIN features f on tf.feature_id = f.id\n where tf.team_id = 1038;\n\n\n# ****************************************** PD *************************\nselect * from teams where id = 1029;\nSELECT * FROM crm_configurations WHERE id = 957;\nSELECT * FROM activities WHERE uuid_to_bin('581a33cb-4343-44bd-ac0c-4a5cee71c5ec') = uuid; # 54963423\nSELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433\n\nSELECT * FROM activities WHERE uuid_to_bin('8b6bb2f7-e7a7-4aec-8806-fbfea7093ccd') = uuid; # 54963421\n\n# ****************************************** Close *************************\nselect * from teams where id = 1031;\nSELECT * FROM crm_configurations WHERE id = 959;\nSELECT * FROM activities WHERE uuid_to_bin('59c517e0-96c9-4dad-bfeb-b7bef72f8725') = uuid; # 54963517\n\n\nSELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433\n\nSELECT * FROM activities WHERE uuid_to_bin('d68311e3-ac34-49bd-bf04-88323a7e5352') = uuid; # 54966009\n\n# ****************************************** SF *************************\nSELECT * FROM activities WHERE uuid_to_bin('2bd4cfa3-4eb5-4c77-84a7-9ed46a21c988') = uuid;\n\nselect * from roles;\nselect * from permissions;\nselect * from permission_role where permission_id = 136;\n\nselect * from migrations order by id desc;\n\nselect * from teams where id IN (1, 1037);\nselect * from crm_layouts where crm_configuration_id = 1;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1493;\nSELECT * FROM crm_fields WHERE id IN (1652,1661,66799,66814,66821,66836,66843,66846,66864,84752,182306,323580,380378);\n\nselect * from features;\nselect * from team_features where feature_id = 33;\nselect * from opportunities;\n\nselect * from teams;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1052 and sa.provider = 'hubspot';\n\nSELECT * FROM accounts where id = 11512582;\nselect * from activities where account_id = 11512582;","depth":4,"on_screen":true,"value":"SELECT\n# DISTINCT\nopp.id as opp_id, opp.uuid, opp.name,\n# COUNT(dr.id) AS deal_risk_count,\n\n# dr.id,\n# cfd.value,\nusr.name AS owner_name,\nopp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,\nusr.uuid as owner_uuid,\nusr.photo_path as owner_photo,\nusr.id AS owner_id,\njt.name as owner_job,\nopp.stage_id, opp.stage_updated_at,\nacc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,\nrt.business_process_id AS pipeline_id\nFROM opportunities opp\nLEFT JOIN record_types rt ON opp.record_type_id = rt.id\nLEFT JOIN users usr ON opp.user_id = usr.id\nLEFT JOIN accounts acc ON opp.account_id = acc.id\nLEFT JOIN job_titles jt ON usr.job_title_id = jt.id\n\n# LEFT JOIN group_deal_risk_types gdrt ON gdrt.group_id = usr.group_id\n# LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id AND dr.group_deal_risk_type_id = gdrt.id\n\nLEFT JOIN crm_field_data cfd ON (cfd.object_id = opp.id and cfd.crm_field_id = 66814)\n\nWHERE opp.user_id IS NOT NULL\nAND opp.deleted_at IS NULL\nAND opp.is_closed = 1\nAND opp.is_won = 0\nAND opp.close_date >= '2024-01-01 00:00:00'\nAND opp.close_date <= '2024-12-31 23:59:59'\nAND usr.team_id = 1\n\n# and opp.id = 4823179\n\nGROUP BY opp.id, cfd.value\nORDER BY\n# cfd.value\n CAST(cfd.value AS UNSIGNED)\n# owner_name\n# (SELECT COUNT(dr.id)\n# FROM deal_risks dr\n# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n# WHERE dr.opportunity_id = opp.id\n# AND dr.is_active = 1\n# AND gdrt.is_enabled = 1)\nDESC\nLIMIT 25\n# OFFSET 0\n;\n\nselect * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';\nselect * from crm_layout_entities where crm_layout_id = 1493;\n\nSELECT * FROM opportunities WHERE id = 4584045;\nSELECT * FROM crm_field_data WHERE object_id = 4584045;\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id = '0061K00000lfb9IQAQ'\n\nSELECT\n opp.id as opportunity_id,\n opp.name,\n COUNT(dr.id) as risk_count\nFROM opportunities opp\nLEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id\nWHERE opp.id IN (\n 6069574,8900938,9507638,9524799,9660490,9662824,9705675,9749758,4158460,4391812,6450439,4448422,5118945,5590675,4584045,7228149,9002408,9165534,9446720,9641778,9665149,9703344,9709280,5747948,4158491,6182565,6263970,5798120,5111315,4536978,6062352,6548383,6072095,6548225,4480986,5011422,6548381,8760540,8917554,9509986,9514392,9569009,9569011,9578490,9713604,5784397,7276612,7288405,8994421,9118219,9608148,9818911,4510535,6479693,5958049,6271674,6550448,4158331,4158483,6126571,6171615,6540943,4897466,5190896,5796182,5932762,8572433,8723698,8892697,9711001,9789264,4549188,6100831,6170064,6260799,6263653,6449936,6530871,6538978,7777651,8059269,8319918,8787049,8901150,9263153,9453207,9514738,9696700,9791062,5752018,6421452,7439134,7878923,9354763,9369285,9514396,9582506,9889949,9890216,4094311,4158495,4158496,6098128,5585661,3872564,6442149,5891604,6164746,6199593,6583474,6519684,9018490,9809006,4496897,5041324,5829430,6198319,6253504,6555763,7242914,7931055,8024125,8797814,8058559,8673347,8892695,8994420,9616219,9714970,9722004,9809439,9818918,6523177,8134147,9002915,9711422,9892713,9901719,9954210,9978435,5800810,6243518,6416114,6222251,6411974,6512456,5791953,6545606,9914780,5805540,6238986,6463838,6547680,9767049,9809437,9810885,9890855,6673493,9902036,4335521,6379871,6503799,6546077,8018765,9907556,9958433,9905855,9916179,9946741,9957877,5563344,6271838,6450815,7641128,7762567,7780592,8684810,8685786,8685787,8685788\n)\nGROUP BY opp.id;\n\nSELECT COUNT(dr.id)\n FROM deal_risks dr\n JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n WHERE dr.opportunity_id = 5563344\n# AND gdrt.group_id = usr.group_id\n\n\n\nEXPLAIN SELECT\n opp.id as opp_id, opp.uuid, opp.name,\n\ncfd.value,\ncfv.sequence,\ncfv.value,\n# MAX(cfd.value) AS max_cfd_value,\nusr.name AS owner_name,\nopp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,\nusr.uuid as owner_uuid,\nusr.photo_path as owner_photo,\nusr.id AS owner_id,\njt.name as owner_job,\nopp.stage_id, opp.stage_updated_at,\nacc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,\nrt.business_process_id AS pipeline_id\nFROM opportunities opp\n\nLEFT JOIN record_types rt ON opp.record_type_id = rt.id\nLEFT JOIN users usr ON opp.user_id = usr.id\nLEFT JOIN accounts acc ON opp.account_id = acc.id\nLEFT JOIN job_titles jt ON usr.job_title_id = jt.id\n\nLEFT JOIN crm_field_data cfd ON cfd.id = (SELECT sub_cfd.id\n FROM crm_field_data sub_cfd\n WHERE sub_cfd.object_id = opp.id\n AND sub_cfd.crm_field_id = 66810\n ORDER BY sub_cfd.updated_at DESC\n LIMIT 1)\n\nLEFT JOIN crm_field_values cfv ON cfv.crm_field_id = 66810 AND cfv.value = cfd.value\n\nWHERE opp.user_id IS NOT NULL\nAND opp.deleted_at IS NULL\nAND opp.is_closed = 0\n# AND opp.is_closed = 1\n# AND opp.is_won = 0\nAND opp.close_date >= '2024-01-01 00:00:00'\nAND opp.close_date <= '2024-12-31 23:59:59'\nAND usr.team_id = 1\n\n# and opp.id = 4823179\n\n# GROUP BY opp.id\nORDER BY\n cfv.sequence DESC,\n# opp.name\n# owner_name\n cfd.value\n# CAST(cfd.value AS UNSIGNED)\n# CAST(MAX(cfd.value) AS UNSIGNED)\n# (SELECT COUNT(dr.id)\n# FROM deal_risks dr\n# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n# WHERE dr.opportunity_id = opp.id\n# AND dr.is_active = 1 AND gdrt.is_enabled = 1)\nDESC\n# LIMIT 75, 25\n;\n\n\n\nSELECT * FROM crm_field_values WHERE crm_field_id = 66810;\n\nSELECT f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value, fd.updated_at, fd.id, fd.created_at\nFROM crm_fields f\nINNER JOIN crm_field_data fd ON fd.crm_field_id = f.id\n WHERE (f.crm_configuration_id = 1)\n AND (f.object_type = 'opportunity')\n# AND (fd.object_id IN (4158331,4158483,4158495,4158496,6069574,6263970,6171615,6540943,6545606,5829430,6198319,6263653,6548381,7242914,8797814,7228149,7439134,7878923,8134147,9002915,9118219,9165534,9354763,9369285,9446720))\n AND (fd.object_id IN (4158460))\n AND (f.crm_provider_id IN ('ForecastCategoryName'))\n ORDER BY fd.object_id ASC, fd.updated_at DESC;\n\nselect * from crm_layouts where crm_configuration_id = 1;\nselect cf.* from crm_fields cf\njoin crm_layout_entities cle on cf.id = cle.crm_field_id\nwhere crm_layout_id = 1493;\n\nSELECT * FROM opportunities WHERE id = 4158460;\nSELECT * FROM crm_field_data WHERE object_id = 4158460;\n\nselect * from users where team_id = 1;\nSELECT * FROM users WHERE id = 7160;\nselect * from role_user where user_id = 3248;\n\nselect * from crm_field_values where crm_field_id = 66824;\n\nSELECT * FROM users WHERE id = 23470;\nselect * from crm_configurations;\n\n# ******************************************\nselect * from teams where id = 1038; # 23521, 966\nselect * from users where team_id = 1038;\nselect * from crm_configurations where id = 966;\nSELECT * FROM activities WHERE uuid_to_bin('e8dc7c34-31f6-4430-bd81-2d9d8f00dd07') = uuid; # 54965762\nSELECT * FROM activities WHERE uuid_to_bin('4b5727c0-69d1-428f-b8d9-b649055166e2') = uuid; # 54965764\nSELECT * FROM participants WHERE activity_id = 54965764;\n\nSELECT id, uuid, title, recording_state FROM activities WHERE uuid_to_bin('355f105d-5606-4379-b0c5-d91daf59f4ce') = uuid; # 54964075\nSELECT * FROM participants WHERE activity_id = 54964075;\nselect f.id, slug, title, tf.team_id from team_features tf JOIN features f on tf.feature_id = f.id\n where tf.team_id = 1038;\n\n\n# ****************************************** PD *************************\nselect * from teams where id = 1029;\nSELECT * FROM crm_configurations WHERE id = 957;\nSELECT * FROM activities WHERE uuid_to_bin('581a33cb-4343-44bd-ac0c-4a5cee71c5ec') = uuid; # 54963423\nSELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433\n\nSELECT * FROM activities WHERE uuid_to_bin('8b6bb2f7-e7a7-4aec-8806-fbfea7093ccd') = uuid; # 54963421\n\n# ****************************************** Close *************************\nselect * from teams where id = 1031;\nSELECT * FROM crm_configurations WHERE id = 959;\nSELECT * FROM activities WHERE uuid_to_bin('59c517e0-96c9-4dad-bfeb-b7bef72f8725') = uuid; # 54963517\n\n\nSELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433\n\nSELECT * FROM activities WHERE uuid_to_bin('d68311e3-ac34-49bd-bf04-88323a7e5352') = uuid; # 54966009\n\n# ****************************************** SF *************************\nSELECT * FROM activities WHERE uuid_to_bin('2bd4cfa3-4eb5-4c77-84a7-9ed46a21c988') = uuid;\n\nselect * from roles;\nselect * from permissions;\nselect * from permission_role where permission_id = 136;\n\nselect * from migrations order by id desc;\n\nselect * from teams where id IN (1, 1037);\nselect * from crm_layouts where crm_configuration_id = 1;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1493;\nSELECT * FROM crm_fields WHERE id IN (1652,1661,66799,66814,66821,66836,66843,66846,66864,84752,182306,323580,380378);\n\nselect * from features;\nselect * from team_features where feature_id = 33;\nselect * from opportunities;\n\nselect * from teams;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1052 and sa.provider = 'hubspot';\n\nSELECT * FROM accounts where id = 11512582;\nselect * from activities where account_id = 11512582;","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.025,"top":0.06666667,"width":0.050694443,"height":0.034444444},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3382584868951134281
|
6551498163460912845
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
8
6
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldDataRepository;
use Jiminny\Services\Crm\Hubspot\DecorateActivity;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationCategoryV4;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationTypeIdV4;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Http\Response;
use SevenShores\Hubspot\Exceptions\HubspotException;
use Sentry;
trait WriteCrmTrait
{
private const string ACTION_CREATE = 'create';
private const string ACTION_UPDATE = 'update';
private const string ACTION_DELETE = 'delete';
/**
* @inheritdoc
*/
public function saveActivity(Activity $activity): Activity
{
switch ($activity->getCrmType()) {
case Activity::TYPE_CONFERENCE:
$activity = $this->buildConferencePayload($activity);
break;
case Activity::TYPE_SOFTPHONE:
case Activity::TYPE_SOFTPHONE_INBOUND:
$activity = $this->buildSoftphonePayload($activity);
break;
case Activity::TYPE_SMS_INBOUND:
case Activity::TYPE_SMS_OUTBOUND:
$activity = $this->buildTextMessagePayload($activity);
break;
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildConferencePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatStartTime($activity),
'type' => 'MEETING',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'meetingOutcome' => $decorator->convertMeetingOutcome($activity),
'internalMeetingNotes' => $decorator->generateDescription($activity),
];
// Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.
if (! $activity->hasCrmProviderId()) {
if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {
$metadata['startTime'] = $decorator->formatStartTime($activity);
$metadata['endTime'] = $decorator->formatEndTime($activity);
}
$metadata['title'] = $decorator->generateTitle($activity);
}
// For now, this is just a "meetingOutcome" field for some customers.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function fetchCustomFieldData(Activity $activity, string $objectType): array
{
$payload = [];
$fieldDataRepository = app(FieldDataRepository::class);
$fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);
foreach ($fieldData as $data) {
// Add the field and value to the payload.
$payload += [
$data->getField()->getCrmProviderId() => $data->getValue(),
];
}
return $payload;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity
{
if (! $activity->hasCrmProviderId()) {
$this->createActivity($activity, $engagement, $metadata);
return $activity;
}
try {
$oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());
} catch (HubspotException $e) {
$this->logger->warning('[HubSpot] Engagement not found', [
'activity_id' => $activity->getUuid(),
'crm_provider_id' => $activity->getCrmProviderId(),
'exception' => $e->getMessage(),
]);
// potentially here we can log new activity if it is not found
return $activity;
}
$type = $oldEngagement['engagement']['type'] ?? null;
$bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';
$decorator = $this->getDecorateActivity();
if ($type !== 'TASK') {
$oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldDescription
);
$oldAssociations = $oldEngagement['associations'] ?? [];
$this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);
} else {
$oldTaskBody = $oldEngagement['metadata']['body'] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldTaskBody
);
$this->updateTask($activity, $engagement, $metadata);
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateNonTaskActivity(
Activity $activity,
array $engagement,
array $metadata,
array $oldAssociations
): void {
if ($activity->isTypeConference()) {
$this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);
} else {
$this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);
}
$this->updateActivityAssociations($activity, $oldAssociations);
$this->logCrmEngagementManipulation(
self::ACTION_UPDATE,
['crmId' => $activity->getCrmProviderId()],
$metadata,
);
}
private function updateActivityAssociations(Activity $activity, array $oldAssociations): void
{
$currentAssociations = $this->getActivityAssociations($activity);
$associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);
// No changes - exit
if (empty($associationChanges)) {
return;
}
$this->logger->info('[HubSpot] Updating associations', [
'team' => $this->team->getUuid(),
'activity' => $activity->getUuid(),
'associationChanges' => $associationChanges,
]);
$this->updateAssociations($activity, $associationChanges);
}
private function getAssociationDifferences(
Activity $activity,
array $oldAssociations,
array $currentAssociations
): array {
$objects = ['contactIds', 'companyIds', 'dealIds'];
$changes = [];
foreach ($objects as $object) {
$oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);
$currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);
$added = array_diff($currentIds, $oldIds);
$removed = array_diff($oldIds, $currentIds);
$type = match ($object) {
'contactIds' => 'contact',
'companyIds' => 'company',
'dealIds' => 'deal',
};
$shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);
if (! empty($removed) && ! $shouldIncludeRemoved) {
$this->logger->info('[HubSpot] Skipping association removal', [
'team' => $this->team->getUuid(),
'type' => $type,
'oldIds' => $oldIds,
'currentIds' => $currentIds,
'skippedRemovals' => array_values($removed),
]);
}
if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {
$changes[$type] = [
'added' => array_values($added),
'removed' => array_values($removed),
];
}
}
return $changes;
}
/**
* Determines whether removed associations should be synced to HubSpot.
*
* For contacts and companies, we always sync removals (original behavior).
* For deals, we only sync removals when:
* - There are new deals being added (replacement scenario), OR
* - The activity has an account associated (indicating intentional association management)
*
* This prevents accidentally removing deal associations that exist in HubSpot
* but not in Jiminny's database (e.g., deals manually added by users in HubSpot).
*/
private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool
{
// Contacts and companies: always sync removals
if ($type !== 'deal') {
return true;
}
// Deals: sync removals if we're adding new deals (replacement)
if (! empty($added)) {
return true;
}
// Deals: sync removals only if activity has an account (intentional management)
return $activity->hasAccount();
}
private function normalizeIds(array $ids): array
{
$stringIds = array_map('strval', $ids);
$uniqueIds = array_unique($stringIds);
sort($uniqueIds);
return $uniqueIds;
}
private function updateAssociations(Activity $activity, array $associationChanges): void
{
$objectType = $activity->isTypeConference() ? 'meeting' : 'call';
foreach ($associationChanges as $type => $changes) {
if (! empty($changes['added'])) {
$associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);
$payload = $this->payloadBuilder->buildAddAssociationPayload(
$activity->getCrmProviderId(),
$changes['added'],
$associationType
);
$this->getClient()->addAssociations($objectType, $type, $payload);
}
if (! empty($changes['removed'])) {
$payload = $this->payloadBuilder->buildRemoveAssociationPayload(
$activity->getCrmProviderId(),
$changes['removed']
);
$this->getClient()->removeAssociations($objectType, $type, $payload);
}
}
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response
{
// If already set by us/client, do not override it
if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {
$this->logger->info('[HubSpot] Meeting activity type already set', [
'meetingId' => $meetingId,
'activityType' => $engagement['activityType'],
]);
unset($engagement['activityType']);
}
$payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);
return $this->getClient()->updateMeeting($meetingId, $payload);
}
private function hasEngagementActivityType(string $engagementId): bool
{
try {
$engagementData = $this->getClient()->getEngagementData($engagementId);
if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {
return true;
}
return false;
} catch (\Exception $exception) {
Sentry::captureException($exception);
$this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [
'engagementId' => $engagementId,
'exception' => $exception->getMessage(),
]);
}
return true;
}
private function updateTask(Activity $activity, array $engagement, array $metadata): void
{
$this->getClient()->deleteEngagement($activity->getCrmProviderId());
$this->logCrmEngagementManipulation(
self::ACTION_DELETE,
['crmId' => $activity->getCrmProviderId()],
);
$this->createActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createActivity(Activity $activity, array $engagement, array $metadata): void
{
$associations = $this->getActivityAssociations($activity);
$associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);
if ($activity->isTypeConference()) {
try {
$hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);
$activityId = $hsEngagement->data->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
} else {
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
$activityId = $hsEngagement->data->engagement->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
}
}
private function convertActivityMeetingAssociationsV4(Activity $activity): array
{
$associations = [];
$contactIds = $this->getParticipantsIds($activity);
foreach ($contactIds as $contactId) {
$associations[] = $this->buildAssociation(
(int) $contactId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]
);
}
if ($activity->hasAccount()) {
$crmProviderId = $activity->getAccount()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]
);
}
}
if ($activity->hasOpportunity()) {
$crmProviderId = $activity->getOpportunity()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]
);
}
}
return $associations;
}
private function buildAssociation(int $id, array $categoryToTypeIds): array
{
$types = [];
foreach ($categoryToTypeIds as $category => $typeId) {
$types[] = $this->buildAssociationType($category, $typeId);
}
return [
'to' => ['id' => (string) $id],
'types' => $types,
];
}
private function buildAssociationType(string $category, int $typeId): array
{
return [
'associationCategory' => $category,
'associationTypeId' => $typeId,
];
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createMeetingV3(array $engagement, array $associations, array $metadata): Response
{
$payload = $this->payloadBuilder->getV3MeetingPayload(
engagement: $engagement,
metadata: $metadata,
associations: $associations
);
return $this->getClient()->createMeeting($payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
private function buildSoftphonePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'CALL',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'toNumber' => $decorator->formatCallToParticipant($activity),
'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),
'status' => $decorator->convertCallStatus($activity),
'durationMilliseconds' => (int) $activity->getDuration() * 1000,
'body' => $decorator->generateDescription($activity),
];
// For now, this is just a "disposition" field to store Call Outcome.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildTextMessagePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'NOTE',
];
$metadata = [
'body' => $decorator->generateDescription($activity),
];
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function getActivityAssociations(Activity $activity): array
{
return [
'contactIds' => $this->getParticipantsIds($activity),
'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],
'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],
'ownerIds' => [],
];
}
/**
* Get DecorateActivity instance. Can be overridden in tests for mocking.
*/
protected function getDecorateActivity(): DecorateActivity
{
return new DecorateActivity();
}
public function saveFollowupActivity(Activity $activity, array $fields): ?string
{
if (empty($fields['subject'])) {
return null;
}
$associations = $this->getActivityAssociations($activity);
if (empty($fields['timestamp']) === false) {
$formatTimestamp = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['timestamp']);
$timestamp = $formatTimestamp->getTimestamp();
} else {
$timestamp = strtotime('+1 day');
}
$reminder = null;
if (empty($fields['reminder']) === false) {
$formatReminder = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['reminder']);
$reminder = $formatReminder->getTimestamp() * 1000;
}
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $timestamp * 1000,
'type' => 'TASK',
];
$metadata = [
'subject' => $fields['subject'],
'body' => '',
'status' => 'NOT_STARTED',
'taskType' => 'CALL',
'reminders' => $reminder ? [$reminder] : [],
];
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
} catch (BadRequest $e) {
$this->logger->info('[HubSpot] Failed to create followup task', [
'activity' => $activity->getUuid(),
'engagement' => $engagement,
'metadata' => $metadata,
'error' => $e->getMessage(),
]);
return null;
}
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
// We don't actually create a corresponding activity object on our side yet.
return (string) $hsEngagement->data->engagement->id;
}
private function logCrmEngagementManipulation(
string $action,
array $engagementData,
array $data = [],
array $associations = [],
): void {
$payload = ['team' => $this->team->getUuid()];
if ($action === self::ACTION_CREATE) {
$payload['engagementData'] = $engagementData;
$payload['data'] = $data;
$payload['associations'] = $associations;
}
if ($action === self::ACTION_UPDATE) {
$payload['engagementId'] = $engagementData['crmId'];
$payload['data'] = $data;
}
if ($action === self::ACTION_DELETE) {
$payload['engagementId'] = $engagementData['crmId'];
}
$this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
8
1
3
4
Previous Highlighted Error
Next Highlighted Error
SELECT
# DISTINCT
opp.id as opp_id, opp.uuid, opp.name,
# COUNT(dr.id) AS deal_risk_count,
# dr.id,
# cfd.value,
usr.name AS owner_name,
opp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,
usr.uuid as owner_uuid,
usr.photo_path as owner_photo,
usr.id AS owner_id,
jt.name as owner_job,
opp.stage_id, opp.stage_updated_at,
acc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,
rt.business_process_id AS pipeline_id
FROM opportunities opp
LEFT JOIN record_types rt ON opp.record_type_id = rt.id
LEFT JOIN users usr ON opp.user_id = usr.id
LEFT JOIN accounts acc ON opp.account_id = acc.id
LEFT JOIN job_titles jt ON usr.job_title_id = jt.id
# LEFT JOIN group_deal_risk_types gdrt ON gdrt.group_id = usr.group_id
# LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id AND dr.group_deal_risk_type_id = gdrt.id
LEFT JOIN crm_field_data cfd ON (cfd.object_id = opp.id and cfd.crm_field_id = 66814)
WHERE opp.user_id IS NOT NULL
AND opp.deleted_at IS NULL
AND opp.is_closed = 1
AND opp.is_won = 0
AND opp.close_date >= '2024-01-01 00:00:00'
AND opp.close_date <= '2024-12-31 23:59:59'
AND usr.team_id = 1
# and opp.id = 4823179
GROUP BY opp.id, cfd.value
ORDER BY
# cfd.value
CAST(cfd.value AS UNSIGNED)
# owner_name
# (SELECT COUNT(dr.id)
# FROM deal_risks dr
# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
# WHERE dr.opportunity_id = opp.id
# AND dr.is_active = 1
# AND gdrt.is_enabled = 1)
DESC
LIMIT 25
# OFFSET 0
;
select * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';
select * from crm_layout_entities where crm_layout_id = 1493;
SELECT * FROM opportunities WHERE id = 4584045;
SELECT * FROM crm_field_data WHERE object_id = 4584045;
SELECT * FROM opportunities where team_id = 1 and crm_provider_id = '0061K00000lfb9IQAQ'
SELECT
opp.id as opportunity_id,
opp.name,
COUNT(dr.id) as risk_count
FROM opportunities opp
LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id
WHERE opp.id IN (
6069574,8900938,9507638,9524799,9660490,9662824,9705675,9749758,4158460,4391812,6450439,4448422,5118945,5590675,4584045,7228149,9002408,9165534,9446720,9641778,9665149,9703344,9709280,5747948,4158491,6182565,6263970,5798120,5111315,4536978,6062352,6548383,6072095,6548225,4480986,5011422,6548381,8760540,8917554,9509986,9514392,9569009,9569011,9578490,9713604,5784397,7276612,7288405,8994421,9118219,9608148,9818911,4510535,6479693,5958049,6271674,6550448,4158331,4158483,6126571,6171615,6540943,4897466,5190896,5796182,5932762,8572433,8723698,8892697,9711001,9789264,4549188,6100831,6170064,6260799,6263653,6449936,6530871,6538978,7777651,8059269,8319918,8787049,8901150,9263153,9453207,9514738,9696700,9791062,5752018,6421452,7439134,7878923,9354763,9369285,9514396,9582506,9889949,9890216,4094311,4158495,4158496,6098128,5585661,3872564,6442149,5891604,6164746,6199593,6583474,6519684,9018490,9809006,4496897,5041324,5829430,6198319,6253504,6555763,7242914,7931055,8024125,8797814,8058559,8673347,8892695,8994420,9616219,9714970,9722004,9809439,9818918,6523177,8134147,9002915,9711422,9892713,9901719,9954210,9978435,5800810,6243518,6416114,6222251,6411974,6512456,5791953,6545606,9914780,5805540,6238986,6463838,6547680,9767049,9809437,9810885,9890855,6673493,9902036,4335521,6379871,6503799,6546077,8018765,9907556,9958433,9905855,9916179,9946741,9957877,5563344,6271838,6450815,7641128,7762567,7780592,8684810,8685786,8685787,8685788
)
GROUP BY opp.id;
SELECT COUNT(dr.id)
FROM deal_risks dr
JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
WHERE dr.opportunity_id = 5563344
# AND gdrt.group_id = usr.group_id
EXPLAIN SELECT
opp.id as opp_id, opp.uuid, opp.name,
cfd.value,
cfv.sequence,
cfv.value,
# MAX(cfd.value) AS max_cfd_value,
usr.name AS owner_name,
opp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,
usr.uuid as owner_uuid,
usr.photo_path as owner_photo,
usr.id AS owner_id,
jt.name as owner_job,
opp.stage_id, opp.stage_updated_at,
acc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,
rt.business_process_id AS pipeline_id
FROM opportunities opp
LEFT JOIN record_types rt ON opp.record_type_id = rt.id
LEFT JOIN users usr ON opp.user_id = usr.id
LEFT JOIN accounts acc ON opp.account_id = acc.id
LEFT JOIN job_titles jt ON usr.job_title_id = jt.id
LEFT JOIN crm_field_data cfd ON cfd.id = (SELECT sub_cfd.id
FROM crm_field_data sub_cfd
WHERE sub_cfd.object_id = opp.id
AND sub_cfd.crm_field_id = 66810
ORDER BY sub_cfd.updated_at DESC
LIMIT 1)
LEFT JOIN crm_field_values cfv ON cfv.crm_field_id = 66810 AND cfv.value = cfd.value
WHERE opp.user_id IS NOT NULL
AND opp.deleted_at IS NULL
AND opp.is_closed = 0
# AND opp.is_closed = 1
# AND opp.is_won = 0
AND opp.close_date >= '2024-01-01 00:00:00'
AND opp.close_date <= '2024-12-31 23:59:59'
AND usr.team_id = 1
# and opp.id = 4823179
# GROUP BY opp.id
ORDER BY
cfv.sequence DESC,
# opp.name
# owner_name
cfd.value
# CAST(cfd.value AS UNSIGNED)
# CAST(MAX(cfd.value) AS UNSIGNED)
# (SELECT COUNT(dr.id)
# FROM deal_risks dr
# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
# WHERE dr.opportunity_id = opp.id
# AND dr.is_active = 1 AND gdrt.is_enabled = 1)
DESC
# LIMIT 75, 25
;
SELECT * FROM crm_field_values WHERE crm_field_id = 66810;
SELECT f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value, fd.updated_at, fd.id, fd.created_at
FROM crm_fields f
INNER JOIN crm_field_data fd ON fd.crm_field_id = f.id
WHERE (f.crm_configuration_id = 1)
AND (f.object_type = 'opportunity')
# AND (fd.object_id IN (4158331,4158483,4158495,4158496,6069574,6263970,6171615,6540943,6545606,5829430,6198319,6263653,6548381,7242914,8797814,7228149,7439134,7878923,8134147,9002915,9118219,9165534,9354763,9369285,9446720))
AND (fd.object_id IN (4158460))
AND (f.crm_provider_id IN ('ForecastCategoryName'))
ORDER BY fd.object_id ASC, fd.updated_at DESC;
select * from crm_layouts where crm_configuration_id = 1;
select cf.* from crm_fields cf
join crm_layout_entities cle on cf.id = cle.crm_field_id
where crm_layout_id = 1493;
SELECT * FROM opportunities WHERE id = 4158460;
SELECT * FROM crm_field_data WHERE object_id = 4158460;
select * from users where team_id = 1;
SELECT * FROM users WHERE id = 7160;
select * from role_user where user_id = 3248;
select * from crm_field_values where crm_field_id = 66824;
SELECT * FROM users WHERE id = 23470;
select * from crm_configurations;
# [PASSWORD_DOTS]
select * from teams where id = 1038; # 23521, 966
select * from users where team_id = 1038;
select * from crm_configurations where id = 966;
SELECT * FROM activities WHERE uuid_to_bin('e8dc7c34-31f6-4430-bd81-2d9d8f00dd07') = uuid; # 54965762
SELECT * FROM activities WHERE uuid_to_bin('4b5727c0-69d1-428f-b8d9-b649055166e2') = uuid; # 54965764
SELECT * FROM participants WHERE activity_id = 54965764;
SELECT id, uuid, title, recording_state FROM activities WHERE uuid_to_bin('355f105d-5606-4379-b0c5-d91daf59f4ce') = uuid; # 54964075
SELECT * FROM participants WHERE activity_id = 54964075;
select f.id, slug, title, tf.team_id from team_features tf JOIN features f on tf.feature_id = f.id
where tf.team_id = 1038;
# [PASSWORD_DOTS] PD [PASSWORD_DOTS]
select * from teams where id = 1029;
SELECT * FROM crm_configurations WHERE id = 957;
SELECT * FROM activities WHERE uuid_to_bin('581a33cb-4343-44bd-ac0c-4a5cee71c5ec') = uuid; # 54963423
SELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433
SELECT * FROM activities WHERE uuid_to_bin('8b6bb2f7-e7a7-4aec-8806-fbfea7093ccd') = uuid; # 54963421
# [PASSWORD_DOTS] Close [PASSWORD_DOTS]
select * from teams where id = 1031;
SELECT * FROM crm_configurations WHERE id = 959;
SELECT * FROM activities WHERE uuid_to_bin('59c517e0-96c9-4dad-bfeb-b7bef72f8725') = uuid; # 54963517
SELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433
SELECT * FROM activities WHERE uuid_to_bin('d68311e3-ac34-49bd-bf04-88323a7e5352') = uuid; # 54966009
# [PASSWORD_DOTS] SF [PASSWORD_DOTS]
SELECT * FROM activities WHERE uuid_to_bin('2bd4cfa3-4eb5-4c77-84a7-9ed46a21c988') = uuid;
select * from roles;
select * from permissions;
select * from permission_role where permission_id = 136;
select * from migrations order by id desc;
select * from teams where id IN (1, 1037);
select * from crm_layouts where crm_configuration_id = 1;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 1493;
SELECT * FROM crm_fields WHERE id IN (1652,1661,66799,66814,66821,66836,66843,66846,66864,84752,182306,323580,380378);
select * from features;
select * from team_features where feature_id = 33;
select * from opportunities;
select * from teams;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1052 and sa.provider = 'hubspot';
SELECT * FROM accounts where id = 11512582;
select * from activities where account_id = 11512582;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
45184
|
NULL
|
NULL
|
NULL
|
|
45187
|
1621
|
30
|
2026-05-14T14:24:10.514575+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768650514_m1.jpg...
|
PhpStorm
|
faVsco.js – WriteCrmTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactor Ru PhpStormFileEditViewNavigateCodeLaravelRefactor RunToolsGitWindowHelplhl••FV faVsco.jsv#12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTestProject v100% (C478• Thu 14 May 17:24:10Q© ReportController.php© AutomatedReportGenerated.phpTrackAutomatedReportGeneratedEvent.phpPlaybackController.php:=custom.log€ ConnectionStateTraUserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.phpSendReportJob.php© CrmHelperReposito© FilterJoinedParticip© DeleteCrmEntityTrait.php© DeleteAccountJob.php© ImportActivityTypes.phpT WriteCrmTrait.php x© ActivityPlaybookTrait.phpT OpportunitySyncab© CrmHelperRepository.php© AccountController.phpT IntegrationAppTrait.php.env.staging.envv HubspotDetachActivityObject.phpC RematchActivityOnCrmObjectDetach.phpMatchActivityCrmData.phpClient.php18D AccountSyncStrate› DJ Actions© HandleHubspotRateLimit.phpD ContactSyncStrate!› O DTO› O Fields› D JournalMetadatav OpportunitySyncSti© HubspotPaginationService.phptrait WriteCrmTraitprivate function buildConferencePayload(Activity $activity): Activity'active'= true,'ownerid' => Sthis-›profile-›getCrmProviderId(),'timestamp' => $decorator->formatStartTime(Sactivity),'type' => 'MEETING',> [ Concerns© HubspotLastMor© HubspotLastMor© HubspotLastMor© HubspotLastMor© HubspotLastMor© HubspotSingleS:© HubspotSyncStr© HubspotWebhocv D Pagination© HubspotPaginati© PaginationConfig© PaginationState.> D ProspectSearchStra> [ Redisv D ServiceTraits€ OpportunitySync© SyncCrmEntities+ SyncFieldsTrait./T WriteCrmTrait.plD UtilsD Webhook A8M6AY1;if ($activity->hasActivityType() && $this-›matchesCrmType($activity->getActivityType())) {$engagement['activityType'] = $activity->getActivityType()->getName();Smetadata = ['meetingOutcome' => Sdecorator->convertMeetingOutcome(Sactivity),'internalMeetingNotes' => $decorator->generateDescription(Sactivity),// Only if they are creating a fresh engagement should we update the times, otherwise it will causeaif (! Sactivity->hasCrmProviderIdO) ‹if (Sactivity->hasActualStartTime() && $activity->hasActualEndTime()) {Smetadata[ 'startTime'] = $decorator->formatStartTime(Sactivity);$metadata['endTime'] = $decorator->formatEndTime($activity):$metadata['title'] = $decorator->generateTitle($activity);}-// For now, this is just a "meetingOutcome"field for some customers.Smetadata = array_merge(Smetadata, $this->fetchCustomFieldData(Sactivity, Field::0BJECT_TASK));D— 211212-213214215216217218219-220221222223224225226=227-228229230231232— 233 V234@ RatchSvnrCnllertn'Workspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)W Windsurf Teams=laravel.logSF [jiminny@localhost]A HS_local [jiminny@localho:W4 console [QAI PROD] X4 console [PROD]A console (EU]Go jiminny0841A3 X4 A Vm migrations oim teams wherem crm_layouts !M crm_layout_eilIM crm_fields Wim features;m team_feature:m opportunitie:m teams;1.id, CASEWHENidFROMsocial,1 on u.id= sa.:1..n<->1: on t.L_id = 1052 andIMaccounts wheiactivities81:57UTF-8Co 4 spaces...
|
NULL
|
-4979536423535327115
|
NULL
|
visual_change
|
ocr
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactor Ru PhpStormFileEditViewNavigateCodeLaravelRefactor RunToolsGitWindowHelplhl••FV faVsco.jsv#12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTestProject v100% (C478• Thu 14 May 17:24:10Q© ReportController.php© AutomatedReportGenerated.phpTrackAutomatedReportGeneratedEvent.phpPlaybackController.php:=custom.log€ ConnectionStateTraUserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.phpSendReportJob.php© CrmHelperReposito© FilterJoinedParticip© DeleteCrmEntityTrait.php© DeleteAccountJob.php© ImportActivityTypes.phpT WriteCrmTrait.php x© ActivityPlaybookTrait.phpT OpportunitySyncab© CrmHelperRepository.php© AccountController.phpT IntegrationAppTrait.php.env.staging.envv HubspotDetachActivityObject.phpC RematchActivityOnCrmObjectDetach.phpMatchActivityCrmData.phpClient.php18D AccountSyncStrate› DJ Actions© HandleHubspotRateLimit.phpD ContactSyncStrate!› O DTO› O Fields› D JournalMetadatav OpportunitySyncSti© HubspotPaginationService.phptrait WriteCrmTraitprivate function buildConferencePayload(Activity $activity): Activity'active'= true,'ownerid' => Sthis-›profile-›getCrmProviderId(),'timestamp' => $decorator->formatStartTime(Sactivity),'type' => 'MEETING',> [ Concerns© HubspotLastMor© HubspotLastMor© HubspotLastMor© HubspotLastMor© HubspotLastMor© HubspotSingleS:© HubspotSyncStr© HubspotWebhocv D Pagination© HubspotPaginati© PaginationConfig© PaginationState.> D ProspectSearchStra> [ Redisv D ServiceTraits€ OpportunitySync© SyncCrmEntities+ SyncFieldsTrait./T WriteCrmTrait.plD UtilsD Webhook A8M6AY1;if ($activity->hasActivityType() && $this-›matchesCrmType($activity->getActivityType())) {$engagement['activityType'] = $activity->getActivityType()->getName();Smetadata = ['meetingOutcome' => Sdecorator->convertMeetingOutcome(Sactivity),'internalMeetingNotes' => $decorator->generateDescription(Sactivity),// Only if they are creating a fresh engagement should we update the times, otherwise it will causeaif (! Sactivity->hasCrmProviderIdO) ‹if (Sactivity->hasActualStartTime() && $activity->hasActualEndTime()) {Smetadata[ 'startTime'] = $decorator->formatStartTime(Sactivity);$metadata['endTime'] = $decorator->formatEndTime($activity):$metadata['title'] = $decorator->generateTitle($activity);}-// For now, this is just a "meetingOutcome"field for some customers.Smetadata = array_merge(Smetadata, $this->fetchCustomFieldData(Sactivity, Field::0BJECT_TASK));D— 211212-213214215216217218219-220221222223224225226=227-228229230231232— 233 V234@ RatchSvnrCnllertn'Workspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)W Windsurf Teams=laravel.logSF [jiminny@localhost]A HS_local [jiminny@localho:W4 console [QAI PROD] X4 console [PROD]A console (EU]Go jiminny0841A3 X4 A Vm migrations oim teams wherem crm_layouts !M crm_layout_eilIM crm_fields Wim features;m team_feature:m opportunitie:m teams;1.id, CASEWHENidFROMsocial,1 on u.id= sa.:1..n<->1: on t.L_id = 1052 andIMaccounts wheiactivities81:57UTF-8Co 4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
45188
|
1621
|
31
|
2026-05-14T14:24:14.065542+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768654065_m1.jpg...
|
PhpStorm
|
faVsco.js – WriteCrmTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
8
6
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldDataRepository;
use Jiminny\Services\Crm\Hubspot\DecorateActivity;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationCategoryV4;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationTypeIdV4;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Http\Response;
use SevenShores\Hubspot\Exceptions\HubspotException;
use Sentry;
trait WriteCrmTrait
{
private const string ACTION_CREATE = 'create';
private const string ACTION_UPDATE = 'update';
private const string ACTION_DELETE = 'delete';
/**
* @inheritdoc
*/
public function saveActivity(Activity $activity): Activity
{
switch ($activity->getCrmType()) {
case Activity::TYPE_CONFERENCE:
$activity = $this->buildConferencePayload($activity);
break;
case Activity::TYPE_SOFTPHONE:
case Activity::TYPE_SOFTPHONE_INBOUND:
$activity = $this->buildSoftphonePayload($activity);
break;
case Activity::TYPE_SMS_INBOUND:
case Activity::TYPE_SMS_OUTBOUND:
$activity = $this->buildTextMessagePayload($activity);
break;
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildConferencePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatStartTime($activity),
'type' => 'MEETING',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'meetingOutcome' => $decorator->convertMeetingOutcome($activity),
'internalMeetingNotes' => $decorator->generateDescription($activity),
];
// Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.
if (! $activity->hasCrmProviderId()) {
if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {
$metadata['startTime'] = $decorator->formatStartTime($activity);
$metadata['endTime'] = $decorator->formatEndTime($activity);
}
$metadata['title'] = $decorator->generateTitle($activity);
}
// For now, this is just a "meetingOutcome" field for some customers.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function fetchCustomFieldData(Activity $activity, string $objectType): array
{
$payload = [];
$fieldDataRepository = app(FieldDataRepository::class);
$fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);
foreach ($fieldData as $data) {
// Add the field and value to the payload.
$payload += [
$data->getField()->getCrmProviderId() => $data->getValue(),
];
}
return $payload;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity
{
if (! $activity->hasCrmProviderId()) {
$this->createActivity($activity, $engagement, $metadata);
return $activity;
}
try {
$oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());
} catch (HubspotException $e) {
$this->logger->warning('[HubSpot] Engagement not found', [
'activity_id' => $activity->getUuid(),
'crm_provider_id' => $activity->getCrmProviderId(),
'exception' => $e->getMessage(),
]);
// potentially here we can log new activity if it is not found
return $activity;
}
$type = $oldEngagement['engagement']['type'] ?? null;
$bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';
$decorator = $this->getDecorateActivity();
if ($type !== 'TASK') {
$oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldDescription
);
$oldAssociations = $oldEngagement['associations'] ?? [];
$this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);
} else {
$oldTaskBody = $oldEngagement['metadata']['body'] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldTaskBody
);
$this->updateTask($activity, $engagement, $metadata);
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateNonTaskActivity(
Activity $activity,
array $engagement,
array $metadata,
array $oldAssociations
): void {
if ($activity->isTypeConference()) {
$this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);
} else {
$this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);
}
$this->updateActivityAssociations($activity, $oldAssociations);
$this->logCrmEngagementManipulation(
self::ACTION_UPDATE,
['crmId' => $activity->getCrmProviderId()],
$metadata,
);
}
private function updateActivityAssociations(Activity $activity, array $oldAssociations): void
{
$currentAssociations = $this->getActivityAssociations($activity);
$associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);
// No changes - exit
if (empty($associationChanges)) {
return;
}
$this->logger->info('[HubSpot] Updating associations', [
'team' => $this->team->getUuid(),
'activity' => $activity->getUuid(),
'associationChanges' => $associationChanges,
]);
$this->updateAssociations($activity, $associationChanges);
}
private function getAssociationDifferences(
Activity $activity,
array $oldAssociations,
array $currentAssociations
): array {
$objects = ['contactIds', 'companyIds', 'dealIds'];
$changes = [];
foreach ($objects as $object) {
$oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);
$currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);
$added = array_diff($currentIds, $oldIds);
$removed = array_diff($oldIds, $currentIds);
$type = match ($object) {
'contactIds' => 'contact',
'companyIds' => 'company',
'dealIds' => 'deal',
};
$shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);
if (! empty($removed) && ! $shouldIncludeRemoved) {
$this->logger->info('[HubSpot] Skipping association removal', [
'team' => $this->team->getUuid(),
'type' => $type,
'oldIds' => $oldIds,
'currentIds' => $currentIds,
'skippedRemovals' => array_values($removed),
]);
}
if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {
$changes[$type] = [
'added' => array_values($added),
'removed' => array_values($removed),
];
}
}
return $changes;
}
/**
* Determines whether removed associations should be synced to HubSpot.
*
* For contacts and companies, we always sync removals (original behavior).
* For deals, we only sync removals when:
* - There are new deals being added (replacement scenario), OR
* - The activity has an account associated (indicating intentional association management)
*
* This prevents accidentally removing deal associations that exist in HubSpot
* but not in Jiminny's database (e.g., deals manually added by users in HubSpot).
*/
private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool
{
// Contacts and companies: always sync removals
if ($type !== 'deal') {
return true;
}
// Deals: sync removals if we're adding new deals (replacement)
if (! empty($added)) {
return true;
}
// Deals: sync removals only if activity has an account (intentional management)
return $activity->hasAccount();
}
private function normalizeIds(array $ids): array
{
$stringIds = array_map('strval', $ids);
$uniqueIds = array_unique($stringIds);
sort($uniqueIds);
return $uniqueIds;
}
private function updateAssociations(Activity $activity, array $associationChanges): void
{
$objectType = $activity->isTypeConference() ? 'meeting' : 'call';
foreach ($associationChanges as $type => $changes) {
if (! empty($changes['added'])) {
$associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);
$payload = $this->payloadBuilder->buildAddAssociationPayload(
$activity->getCrmProviderId(),
$changes['added'],
$associationType
);
$this->getClient()->addAssociations($objectType, $type, $payload);
}
if (! empty($changes['removed'])) {
$payload = $this->payloadBuilder->buildRemoveAssociationPayload(
$activity->getCrmProviderId(),
$changes['removed']
);
$this->getClient()->removeAssociations($objectType, $type, $payload);
}
}
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response
{
// If already set by us/client, do not override it
if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {
$this->logger->info('[HubSpot] Meeting activity type already set', [
'meetingId' => $meetingId,
'activityType' => $engagement['activityType'],
]);
unset($engagement['activityType']);
}
$payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);
return $this->getClient()->updateMeeting($meetingId, $payload);
}
private function hasEngagementActivityType(string $engagementId): bool
{
try {
$engagementData = $this->getClient()->getEngagementData($engagementId);
if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {
return true;
}
return false;
} catch (\Exception $exception) {
Sentry::captureException($exception);
$this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [
'engagementId' => $engagementId,
'exception' => $exception->getMessage(),
]);
}
return true;
}
private function updateTask(Activity $activity, array $engagement, array $metadata): void
{
$this->getClient()->deleteEngagement($activity->getCrmProviderId());
$this->logCrmEngagementManipulation(
self::ACTION_DELETE,
['crmId' => $activity->getCrmProviderId()],
);
$this->createActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createActivity(Activity $activity, array $engagement, array $metadata): void
{
$associations = $this->getActivityAssociations($activity);
$associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);
if ($activity->isTypeConference()) {
try {
$hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);
$activityId = $hsEngagement->data->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
} else {
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
$activityId = $hsEngagement->data->engagement->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
}
}
private function convertActivityMeetingAssociationsV4(Activity $activity): array
{
$associations = [];
$contactIds = $this->getParticipantsIds($activity);
foreach ($contactIds as $contactId) {
$associations[] = $this->buildAssociation(
(int) $contactId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]
);
}
if ($activity->hasAccount()) {
$crmProviderId = $activity->getAccount()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]
);
}
}
if ($activity->hasOpportunity()) {
$crmProviderId = $activity->getOpportunity()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]
);
}
}
return $associations;
}
private function buildAssociation(int $id, array $categoryToTypeIds): array
{
$types = [];
foreach ($categoryToTypeIds as $category => $typeId) {
$types[] = $this->buildAssociationType($category, $typeId);
}
return [
'to' => ['id' => (string) $id],
'types' => $types,
];
}
private function buildAssociationType(string $category, int $typeId): array
{
return [
'associationCategory' => $category,
'associationTypeId' => $typeId,
];
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createMeetingV3(array $engagement, array $associations, array $metadata): Response
{
$payload = $this->payloadBuilder->getV3MeetingPayload(
engagement: $engagement,
metadata: $metadata,
associations: $associations
);
return $this->getClient()->createMeeting($payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
private function buildSoftphonePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'CALL',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'toNumber' => $decorator->formatCallToParticipant($activity),
'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),
'status' => $decorator->convertCallStatus($activity),
'durationMilliseconds' => (int) $activity->getDuration() * 1000,
'body' => $decorator->generateDescription($activity),
];
// For now, this is just a "disposition" field to store Call Outcome.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildTextMessagePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'NOTE',
];
$metadata = [
'body' => $decorator->generateDescription($activity),
];
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function getActivityAssociations(Activity $activity): array
{
return [
'contactIds' => $this->getParticipantsIds($activity),
'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],
'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],
'ownerIds' => [],
];
}
/**
* Get DecorateActivity instance. Can be overridden in tests for mocking.
*/
protected function getDecorateActivity(): DecorateActivity
{
return new DecorateActivity();
}
public function saveFollowupActivity(Activity $activity, array $fields): ?string
{
if (empty($fields['subject'])) {
return null;
}
$associations = $this->getActivityAssociations($activity);
if (empty($fields['timestamp']) === false) {
$formatTimestamp = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['timestamp']);
$timestamp = $formatTimestamp->getTimestamp();
} else {
$timestamp = strtotime('+1 day');
}
$reminder = null;
if (empty($fields['reminder']) === false) {
$formatReminder = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['reminder']);
$reminder = $formatReminder->getTimestamp() * 1000;
}
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $timestamp * 1000,
'type' => 'TASK',
];
$metadata = [
'subject' => $fields['subject'],
'body' => '',
'status' => 'NOT_STARTED',
'taskType' => 'CALL',
'reminders' => $reminder ? [$reminder] : [],
];
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
} catch (BadRequest $e) {
$this->logger->info('[HubSpot] Failed to create followup task', [
'activity' => $activity->getUuid(),
'engagement' => $engagement,
'metadata' => $metadata,
'error' => $e->getMessage(),
]);
return null;
}
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
// We don't actually create a corresponding activity object on our side yet.
return (string) $hsEngagement->data->engagement->id;
}
private function logCrmEngagementManipulation(
string $action,
array $engagementData,
array $data = [],
array $associations = [],
): void {
$payload = ['team' => $this->team->getUuid()];
if ($action === self::ACTION_CREATE) {
$payload['engagementData'] = $engagementData;
$payload['data'] = $data;
$payload['associations'] = $associations;
}
if ($action === self::ACTION_UPDATE) {
$payload['engagementId'] = $engagementData['crmId'];
$payload['data'] = $data;
}
if ($action === self::ACTION_DELETE) {
$payload['engagementId'] = $engagementData['crmId'];
}
$this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.054166667,"top":0.027777778,"width":0.08055556,"height":0.035555556},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#12066 on JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.13472222,"top":0.027777778,"width":0.25555557,"height":0.035555556},"on_screen":true,"help_text":"Pull request #12066 exists for current branch JY-20725-handle-HS-search-rate-limit, 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.6326389,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HandleHubspotRateLimitTest","depth":6,"bounds":{"left":0.6645833,"top":0.027777778,"width":0.15902779,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.82361114,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.8472222,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.87083334,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.9291667,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9527778,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9763889,"top":0.027777778,"width":0.023611112,"height":0.035555556},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"8","depth":4,"bounds":{"left":0.78055555,"top":0.2777778,"width":0.016666668,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.80138886,"top":0.2777778,"width":0.016666668,"height":0.02111111},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.8215278,"top":0.27555555,"width":0.015277778,"height":0.025555555},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.8368056,"top":0.27555555,"width":0.014583333,"height":0.025555555},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldDataRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DecorateActivity;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationCategoryV4;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationTypeIdV4;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse Sentry;\n\ntrait WriteCrmTrait\n{\n private const string ACTION_CREATE = 'create';\n private const string ACTION_UPDATE = 'update';\n private const string ACTION_DELETE = 'delete';\n\n /**\n * @inheritdoc\n */\n public function saveActivity(Activity $activity): Activity\n {\n switch ($activity->getCrmType()) {\n case Activity::TYPE_CONFERENCE:\n $activity = $this->buildConferencePayload($activity);\n\n break;\n case Activity::TYPE_SOFTPHONE:\n case Activity::TYPE_SOFTPHONE_INBOUND:\n $activity = $this->buildSoftphonePayload($activity);\n\n break;\n\n case Activity::TYPE_SMS_INBOUND:\n case Activity::TYPE_SMS_OUTBOUND:\n $activity = $this->buildTextMessagePayload($activity);\n\n break;\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildConferencePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatStartTime($activity),\n 'type' => 'MEETING',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'meetingOutcome' => $decorator->convertMeetingOutcome($activity),\n 'internalMeetingNotes' => $decorator->generateDescription($activity),\n ];\n\n // Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.\n if (! $activity->hasCrmProviderId()) {\n if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {\n $metadata['startTime'] = $decorator->formatStartTime($activity);\n $metadata['endTime'] = $decorator->formatEndTime($activity);\n }\n $metadata['title'] = $decorator->generateTitle($activity);\n }\n\n // For now, this is just a \"meetingOutcome\" field for some customers.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function fetchCustomFieldData(Activity $activity, string $objectType): array\n {\n $payload = [];\n\n $fieldDataRepository = app(FieldDataRepository::class);\n $fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);\n\n foreach ($fieldData as $data) {\n // Add the field and value to the payload.\n $payload += [\n $data->getField()->getCrmProviderId() => $data->getValue(),\n ];\n }\n\n return $payload;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity\n {\n if (! $activity->hasCrmProviderId()) {\n $this->createActivity($activity, $engagement, $metadata);\n\n return $activity;\n }\n\n try {\n $oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());\n } catch (HubspotException $e) {\n $this->logger->warning('[HubSpot] Engagement not found', [\n 'activity_id' => $activity->getUuid(),\n 'crm_provider_id' => $activity->getCrmProviderId(),\n 'exception' => $e->getMessage(),\n ]);\n\n // potentially here we can log new activity if it is not found\n\n return $activity;\n }\n\n $type = $oldEngagement['engagement']['type'] ?? null;\n $bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';\n\n $decorator = $this->getDecorateActivity();\n\n if ($type !== 'TASK') {\n $oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldDescription\n );\n\n $oldAssociations = $oldEngagement['associations'] ?? [];\n $this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);\n } else {\n $oldTaskBody = $oldEngagement['metadata']['body'] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldTaskBody\n );\n $this->updateTask($activity, $engagement, $metadata);\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateNonTaskActivity(\n Activity $activity,\n array $engagement,\n array $metadata,\n array $oldAssociations\n ): void {\n if ($activity->isTypeConference()) {\n $this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);\n } else {\n $this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);\n }\n\n $this->updateActivityAssociations($activity, $oldAssociations);\n\n $this->logCrmEngagementManipulation(\n self::ACTION_UPDATE,\n ['crmId' => $activity->getCrmProviderId()],\n $metadata,\n );\n }\n\n private function updateActivityAssociations(Activity $activity, array $oldAssociations): void\n {\n $currentAssociations = $this->getActivityAssociations($activity);\n $associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);\n\n // No changes - exit\n if (empty($associationChanges)) {\n return;\n }\n\n $this->logger->info('[HubSpot] Updating associations', [\n 'team' => $this->team->getUuid(),\n 'activity' => $activity->getUuid(),\n 'associationChanges' => $associationChanges,\n ]);\n\n $this->updateAssociations($activity, $associationChanges);\n }\n\n private function getAssociationDifferences(\n Activity $activity,\n array $oldAssociations,\n array $currentAssociations\n ): array {\n $objects = ['contactIds', 'companyIds', 'dealIds'];\n $changes = [];\n\n foreach ($objects as $object) {\n $oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);\n $currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);\n\n $added = array_diff($currentIds, $oldIds);\n $removed = array_diff($oldIds, $currentIds);\n\n $type = match ($object) {\n 'contactIds' => 'contact',\n 'companyIds' => 'company',\n 'dealIds' => 'deal',\n };\n\n $shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);\n\n if (! empty($removed) && ! $shouldIncludeRemoved) {\n $this->logger->info('[HubSpot] Skipping association removal', [\n 'team' => $this->team->getUuid(),\n 'type' => $type,\n 'oldIds' => $oldIds,\n 'currentIds' => $currentIds,\n 'skippedRemovals' => array_values($removed),\n ]);\n }\n\n if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {\n $changes[$type] = [\n 'added' => array_values($added),\n 'removed' => array_values($removed),\n ];\n }\n }\n\n return $changes;\n }\n\n /**\n * Determines whether removed associations should be synced to HubSpot.\n *\n * For contacts and companies, we always sync removals (original behavior).\n * For deals, we only sync removals when:\n * - There are new deals being added (replacement scenario), OR\n * - The activity has an account associated (indicating intentional association management)\n *\n * This prevents accidentally removing deal associations that exist in HubSpot\n * but not in Jiminny's database (e.g., deals manually added by users in HubSpot).\n */\n private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool\n {\n // Contacts and companies: always sync removals\n if ($type !== 'deal') {\n return true;\n }\n\n // Deals: sync removals if we're adding new deals (replacement)\n if (! empty($added)) {\n return true;\n }\n\n // Deals: sync removals only if activity has an account (intentional management)\n return $activity->hasAccount();\n }\n\n private function normalizeIds(array $ids): array\n {\n $stringIds = array_map('strval', $ids);\n $uniqueIds = array_unique($stringIds);\n sort($uniqueIds);\n\n return $uniqueIds;\n }\n\n private function updateAssociations(Activity $activity, array $associationChanges): void\n {\n $objectType = $activity->isTypeConference() ? 'meeting' : 'call';\n\n foreach ($associationChanges as $type => $changes) {\n if (! empty($changes['added'])) {\n $associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);\n $payload = $this->payloadBuilder->buildAddAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['added'],\n $associationType\n );\n $this->getClient()->addAssociations($objectType, $type, $payload);\n }\n\n if (! empty($changes['removed'])) {\n $payload = $this->payloadBuilder->buildRemoveAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['removed']\n );\n $this->getClient()->removeAssociations($objectType, $type, $payload);\n }\n }\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response\n {\n // If already set by us/client, do not override it\n if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {\n $this->logger->info('[HubSpot] Meeting activity type already set', [\n 'meetingId' => $meetingId,\n 'activityType' => $engagement['activityType'],\n ]);\n\n unset($engagement['activityType']);\n }\n\n $payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);\n\n return $this->getClient()->updateMeeting($meetingId, $payload);\n }\n\n private function hasEngagementActivityType(string $engagementId): bool\n {\n try {\n $engagementData = $this->getClient()->getEngagementData($engagementId);\n if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {\n return true;\n }\n\n return false;\n } catch (\\Exception $exception) {\n Sentry::captureException($exception);\n\n $this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [\n 'engagementId' => $engagementId,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n return true;\n }\n\n private function updateTask(Activity $activity, array $engagement, array $metadata): void\n {\n $this->getClient()->deleteEngagement($activity->getCrmProviderId());\n\n $this->logCrmEngagementManipulation(\n self::ACTION_DELETE,\n ['crmId' => $activity->getCrmProviderId()],\n );\n\n $this->createActivity($activity, $engagement, $metadata);\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createActivity(Activity $activity, array $engagement, array $metadata): void\n {\n $associations = $this->getActivityAssociations($activity);\n $associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);\n\n if ($activity->isTypeConference()) {\n try {\n $hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);\n $activityId = $hsEngagement->data->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n } else {\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n $activityId = $hsEngagement->data->engagement->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n }\n }\n\n private function convertActivityMeetingAssociationsV4(Activity $activity): array\n {\n $associations = [];\n\n $contactIds = $this->getParticipantsIds($activity);\n\n foreach ($contactIds as $contactId) {\n $associations[] = $this->buildAssociation(\n (int) $contactId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]\n );\n }\n\n if ($activity->hasAccount()) {\n $crmProviderId = $activity->getAccount()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]\n );\n }\n }\n\n if ($activity->hasOpportunity()) {\n $crmProviderId = $activity->getOpportunity()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]\n );\n }\n }\n\n return $associations;\n }\n\n private function buildAssociation(int $id, array $categoryToTypeIds): array\n {\n $types = [];\n\n foreach ($categoryToTypeIds as $category => $typeId) {\n $types[] = $this->buildAssociationType($category, $typeId);\n }\n\n return [\n 'to' => ['id' => (string) $id],\n 'types' => $types,\n ];\n }\n\n private function buildAssociationType(string $category, int $typeId): array\n {\n return [\n 'associationCategory' => $category,\n 'associationTypeId' => $typeId,\n ];\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createMeetingV3(array $engagement, array $associations, array $metadata): Response\n {\n $payload = $this->payloadBuilder->getV3MeetingPayload(\n engagement: $engagement,\n metadata: $metadata,\n associations: $associations\n );\n\n return $this->getClient()->createMeeting($payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n private function buildSoftphonePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'CALL',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'toNumber' => $decorator->formatCallToParticipant($activity),\n 'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),\n 'status' => $decorator->convertCallStatus($activity),\n 'durationMilliseconds' => (int) $activity->getDuration() * 1000,\n 'body' => $decorator->generateDescription($activity),\n ];\n\n // For now, this is just a \"disposition\" field to store Call Outcome.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildTextMessagePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'NOTE',\n ];\n\n $metadata = [\n 'body' => $decorator->generateDescription($activity),\n ];\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function getActivityAssociations(Activity $activity): array\n {\n return [\n 'contactIds' => $this->getParticipantsIds($activity),\n 'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],\n 'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],\n 'ownerIds' => [],\n ];\n }\n\n /**\n * Get DecorateActivity instance. Can be overridden in tests for mocking.\n */\n protected function getDecorateActivity(): DecorateActivity\n {\n return new DecorateActivity();\n }\n\n public function saveFollowupActivity(Activity $activity, array $fields): ?string\n {\n if (empty($fields['subject'])) {\n return null;\n }\n\n $associations = $this->getActivityAssociations($activity);\n\n if (empty($fields['timestamp']) === false) {\n $formatTimestamp = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['timestamp']);\n $timestamp = $formatTimestamp->getTimestamp();\n } else {\n $timestamp = strtotime('+1 day');\n }\n\n $reminder = null;\n if (empty($fields['reminder']) === false) {\n $formatReminder = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['reminder']);\n $reminder = $formatReminder->getTimestamp() * 1000;\n }\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $timestamp * 1000,\n 'type' => 'TASK',\n ];\n\n $metadata = [\n 'subject' => $fields['subject'],\n 'body' => '',\n 'status' => 'NOT_STARTED',\n 'taskType' => 'CALL',\n 'reminders' => $reminder ? [$reminder] : [],\n ];\n\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n } catch (BadRequest $e) {\n $this->logger->info('[HubSpot] Failed to create followup task', [\n 'activity' => $activity->getUuid(),\n 'engagement' => $engagement,\n 'metadata' => $metadata,\n 'error' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n // We don't actually create a corresponding activity object on our side yet.\n return (string) $hsEngagement->data->engagement->id;\n }\n\n private function logCrmEngagementManipulation(\n string $action,\n array $engagementData,\n array $data = [],\n array $associations = [],\n ): void {\n $payload = ['team' => $this->team->getUuid()];\n\n if ($action === self::ACTION_CREATE) {\n $payload['engagementData'] = $engagementData;\n $payload['data'] = $data;\n $payload['associations'] = $associations;\n }\n\n if ($action === self::ACTION_UPDATE) {\n $payload['engagementId'] = $engagementData['crmId'];\n $payload['data'] = $data;\n }\n\n if ($action === self::ACTION_DELETE) {\n $payload['engagementId'] = $engagementData['crmId'];\n }\n\n $this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldDataRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DecorateActivity;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationCategoryV4;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationTypeIdV4;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse Sentry;\n\ntrait WriteCrmTrait\n{\n private const string ACTION_CREATE = 'create';\n private const string ACTION_UPDATE = 'update';\n private const string ACTION_DELETE = 'delete';\n\n /**\n * @inheritdoc\n */\n public function saveActivity(Activity $activity): Activity\n {\n switch ($activity->getCrmType()) {\n case Activity::TYPE_CONFERENCE:\n $activity = $this->buildConferencePayload($activity);\n\n break;\n case Activity::TYPE_SOFTPHONE:\n case Activity::TYPE_SOFTPHONE_INBOUND:\n $activity = $this->buildSoftphonePayload($activity);\n\n break;\n\n case Activity::TYPE_SMS_INBOUND:\n case Activity::TYPE_SMS_OUTBOUND:\n $activity = $this->buildTextMessagePayload($activity);\n\n break;\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildConferencePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatStartTime($activity),\n 'type' => 'MEETING',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'meetingOutcome' => $decorator->convertMeetingOutcome($activity),\n 'internalMeetingNotes' => $decorator->generateDescription($activity),\n ];\n\n // Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.\n if (! $activity->hasCrmProviderId()) {\n if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {\n $metadata['startTime'] = $decorator->formatStartTime($activity);\n $metadata['endTime'] = $decorator->formatEndTime($activity);\n }\n $metadata['title'] = $decorator->generateTitle($activity);\n }\n\n // For now, this is just a \"meetingOutcome\" field for some customers.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function fetchCustomFieldData(Activity $activity, string $objectType): array\n {\n $payload = [];\n\n $fieldDataRepository = app(FieldDataRepository::class);\n $fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);\n\n foreach ($fieldData as $data) {\n // Add the field and value to the payload.\n $payload += [\n $data->getField()->getCrmProviderId() => $data->getValue(),\n ];\n }\n\n return $payload;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity\n {\n if (! $activity->hasCrmProviderId()) {\n $this->createActivity($activity, $engagement, $metadata);\n\n return $activity;\n }\n\n try {\n $oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());\n } catch (HubspotException $e) {\n $this->logger->warning('[HubSpot] Engagement not found', [\n 'activity_id' => $activity->getUuid(),\n 'crm_provider_id' => $activity->getCrmProviderId(),\n 'exception' => $e->getMessage(),\n ]);\n\n // potentially here we can log new activity if it is not found\n\n return $activity;\n }\n\n $type = $oldEngagement['engagement']['type'] ?? null;\n $bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';\n\n $decorator = $this->getDecorateActivity();\n\n if ($type !== 'TASK') {\n $oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldDescription\n );\n\n $oldAssociations = $oldEngagement['associations'] ?? [];\n $this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);\n } else {\n $oldTaskBody = $oldEngagement['metadata']['body'] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldTaskBody\n );\n $this->updateTask($activity, $engagement, $metadata);\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateNonTaskActivity(\n Activity $activity,\n array $engagement,\n array $metadata,\n array $oldAssociations\n ): void {\n if ($activity->isTypeConference()) {\n $this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);\n } else {\n $this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);\n }\n\n $this->updateActivityAssociations($activity, $oldAssociations);\n\n $this->logCrmEngagementManipulation(\n self::ACTION_UPDATE,\n ['crmId' => $activity->getCrmProviderId()],\n $metadata,\n );\n }\n\n private function updateActivityAssociations(Activity $activity, array $oldAssociations): void\n {\n $currentAssociations = $this->getActivityAssociations($activity);\n $associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);\n\n // No changes - exit\n if (empty($associationChanges)) {\n return;\n }\n\n $this->logger->info('[HubSpot] Updating associations', [\n 'team' => $this->team->getUuid(),\n 'activity' => $activity->getUuid(),\n 'associationChanges' => $associationChanges,\n ]);\n\n $this->updateAssociations($activity, $associationChanges);\n }\n\n private function getAssociationDifferences(\n Activity $activity,\n array $oldAssociations,\n array $currentAssociations\n ): array {\n $objects = ['contactIds', 'companyIds', 'dealIds'];\n $changes = [];\n\n foreach ($objects as $object) {\n $oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);\n $currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);\n\n $added = array_diff($currentIds, $oldIds);\n $removed = array_diff($oldIds, $currentIds);\n\n $type = match ($object) {\n 'contactIds' => 'contact',\n 'companyIds' => 'company',\n 'dealIds' => 'deal',\n };\n\n $shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);\n\n if (! empty($removed) && ! $shouldIncludeRemoved) {\n $this->logger->info('[HubSpot] Skipping association removal', [\n 'team' => $this->team->getUuid(),\n 'type' => $type,\n 'oldIds' => $oldIds,\n 'currentIds' => $currentIds,\n 'skippedRemovals' => array_values($removed),\n ]);\n }\n\n if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {\n $changes[$type] = [\n 'added' => array_values($added),\n 'removed' => array_values($removed),\n ];\n }\n }\n\n return $changes;\n }\n\n /**\n * Determines whether removed associations should be synced to HubSpot.\n *\n * For contacts and companies, we always sync removals (original behavior).\n * For deals, we only sync removals when:\n * - There are new deals being added (replacement scenario), OR\n * - The activity has an account associated (indicating intentional association management)\n *\n * This prevents accidentally removing deal associations that exist in HubSpot\n * but not in Jiminny's database (e.g., deals manually added by users in HubSpot).\n */\n private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool\n {\n // Contacts and companies: always sync removals\n if ($type !== 'deal') {\n return true;\n }\n\n // Deals: sync removals if we're adding new deals (replacement)\n if (! empty($added)) {\n return true;\n }\n\n // Deals: sync removals only if activity has an account (intentional management)\n return $activity->hasAccount();\n }\n\n private function normalizeIds(array $ids): array\n {\n $stringIds = array_map('strval', $ids);\n $uniqueIds = array_unique($stringIds);\n sort($uniqueIds);\n\n return $uniqueIds;\n }\n\n private function updateAssociations(Activity $activity, array $associationChanges): void\n {\n $objectType = $activity->isTypeConference() ? 'meeting' : 'call';\n\n foreach ($associationChanges as $type => $changes) {\n if (! empty($changes['added'])) {\n $associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);\n $payload = $this->payloadBuilder->buildAddAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['added'],\n $associationType\n );\n $this->getClient()->addAssociations($objectType, $type, $payload);\n }\n\n if (! empty($changes['removed'])) {\n $payload = $this->payloadBuilder->buildRemoveAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['removed']\n );\n $this->getClient()->removeAssociations($objectType, $type, $payload);\n }\n }\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response\n {\n // If already set by us/client, do not override it\n if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {\n $this->logger->info('[HubSpot] Meeting activity type already set', [\n 'meetingId' => $meetingId,\n 'activityType' => $engagement['activityType'],\n ]);\n\n unset($engagement['activityType']);\n }\n\n $payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);\n\n return $this->getClient()->updateMeeting($meetingId, $payload);\n }\n\n private function hasEngagementActivityType(string $engagementId): bool\n {\n try {\n $engagementData = $this->getClient()->getEngagementData($engagementId);\n if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {\n return true;\n }\n\n return false;\n } catch (\\Exception $exception) {\n Sentry::captureException($exception);\n\n $this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [\n 'engagementId' => $engagementId,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n return true;\n }\n\n private function updateTask(Activity $activity, array $engagement, array $metadata): void\n {\n $this->getClient()->deleteEngagement($activity->getCrmProviderId());\n\n $this->logCrmEngagementManipulation(\n self::ACTION_DELETE,\n ['crmId' => $activity->getCrmProviderId()],\n );\n\n $this->createActivity($activity, $engagement, $metadata);\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createActivity(Activity $activity, array $engagement, array $metadata): void\n {\n $associations = $this->getActivityAssociations($activity);\n $associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);\n\n if ($activity->isTypeConference()) {\n try {\n $hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);\n $activityId = $hsEngagement->data->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n } else {\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n $activityId = $hsEngagement->data->engagement->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n }\n }\n\n private function convertActivityMeetingAssociationsV4(Activity $activity): array\n {\n $associations = [];\n\n $contactIds = $this->getParticipantsIds($activity);\n\n foreach ($contactIds as $contactId) {\n $associations[] = $this->buildAssociation(\n (int) $contactId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]\n );\n }\n\n if ($activity->hasAccount()) {\n $crmProviderId = $activity->getAccount()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]\n );\n }\n }\n\n if ($activity->hasOpportunity()) {\n $crmProviderId = $activity->getOpportunity()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]\n );\n }\n }\n\n return $associations;\n }\n\n private function buildAssociation(int $id, array $categoryToTypeIds): array\n {\n $types = [];\n\n foreach ($categoryToTypeIds as $category => $typeId) {\n $types[] = $this->buildAssociationType($category, $typeId);\n }\n\n return [\n 'to' => ['id' => (string) $id],\n 'types' => $types,\n ];\n }\n\n private function buildAssociationType(string $category, int $typeId): array\n {\n return [\n 'associationCategory' => $category,\n 'associationTypeId' => $typeId,\n ];\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createMeetingV3(array $engagement, array $associations, array $metadata): Response\n {\n $payload = $this->payloadBuilder->getV3MeetingPayload(\n engagement: $engagement,\n metadata: $metadata,\n associations: $associations\n );\n\n return $this->getClient()->createMeeting($payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n private function buildSoftphonePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'CALL',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'toNumber' => $decorator->formatCallToParticipant($activity),\n 'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),\n 'status' => $decorator->convertCallStatus($activity),\n 'durationMilliseconds' => (int) $activity->getDuration() * 1000,\n 'body' => $decorator->generateDescription($activity),\n ];\n\n // For now, this is just a \"disposition\" field to store Call Outcome.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildTextMessagePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'NOTE',\n ];\n\n $metadata = [\n 'body' => $decorator->generateDescription($activity),\n ];\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function getActivityAssociations(Activity $activity): array\n {\n return [\n 'contactIds' => $this->getParticipantsIds($activity),\n 'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],\n 'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],\n 'ownerIds' => [],\n ];\n }\n\n /**\n * Get DecorateActivity instance. Can be overridden in tests for mocking.\n */\n protected function getDecorateActivity(): DecorateActivity\n {\n return new DecorateActivity();\n }\n\n public function saveFollowupActivity(Activity $activity, array $fields): ?string\n {\n if (empty($fields['subject'])) {\n return null;\n }\n\n $associations = $this->getActivityAssociations($activity);\n\n if (empty($fields['timestamp']) === false) {\n $formatTimestamp = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['timestamp']);\n $timestamp = $formatTimestamp->getTimestamp();\n } else {\n $timestamp = strtotime('+1 day');\n }\n\n $reminder = null;\n if (empty($fields['reminder']) === false) {\n $formatReminder = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['reminder']);\n $reminder = $formatReminder->getTimestamp() * 1000;\n }\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $timestamp * 1000,\n 'type' => 'TASK',\n ];\n\n $metadata = [\n 'subject' => $fields['subject'],\n 'body' => '',\n 'status' => 'NOT_STARTED',\n 'taskType' => 'CALL',\n 'reminders' => $reminder ? [$reminder] : [],\n ];\n\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n } catch (BadRequest $e) {\n $this->logger->info('[HubSpot] Failed to create followup task', [\n 'activity' => $activity->getUuid(),\n 'engagement' => $engagement,\n 'metadata' => $metadata,\n 'error' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n // We don't actually create a corresponding activity object on our side yet.\n return (string) $hsEngagement->data->engagement->id;\n }\n\n private function logCrmEngagementManipulation(\n string $action,\n array $engagementData,\n array $data = [],\n array $associations = [],\n ): void {\n $payload = ['team' => $this->team->getUuid()];\n\n if ($action === self::ACTION_CREATE) {\n $payload['engagementData'] = $engagementData;\n $payload['data'] = $data;\n $payload['associations'] = $associations;\n }\n\n if ($action === self::ACTION_UPDATE) {\n $payload['engagementId'] = $engagementData['crmId'];\n $payload['data'] = $data;\n }\n\n if ($action === self::ACTION_DELETE) {\n $payload['engagementId'] = $engagementData['crmId'];\n }\n\n $this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.85486114,"top":0.31,"width":0.018055556,"height":0.026666667},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"bounds":{"left":0.87291664,"top":0.31,"width":0.018055556,"height":0.026666667},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
2768697343976106221
|
-2691170164485591123
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
8
6
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldDataRepository;
use Jiminny\Services\Crm\Hubspot\DecorateActivity;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationCategoryV4;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationTypeIdV4;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Http\Response;
use SevenShores\Hubspot\Exceptions\HubspotException;
use Sentry;
trait WriteCrmTrait
{
private const string ACTION_CREATE = 'create';
private const string ACTION_UPDATE = 'update';
private const string ACTION_DELETE = 'delete';
/**
* @inheritdoc
*/
public function saveActivity(Activity $activity): Activity
{
switch ($activity->getCrmType()) {
case Activity::TYPE_CONFERENCE:
$activity = $this->buildConferencePayload($activity);
break;
case Activity::TYPE_SOFTPHONE:
case Activity::TYPE_SOFTPHONE_INBOUND:
$activity = $this->buildSoftphonePayload($activity);
break;
case Activity::TYPE_SMS_INBOUND:
case Activity::TYPE_SMS_OUTBOUND:
$activity = $this->buildTextMessagePayload($activity);
break;
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildConferencePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatStartTime($activity),
'type' => 'MEETING',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'meetingOutcome' => $decorator->convertMeetingOutcome($activity),
'internalMeetingNotes' => $decorator->generateDescription($activity),
];
// Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.
if (! $activity->hasCrmProviderId()) {
if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {
$metadata['startTime'] = $decorator->formatStartTime($activity);
$metadata['endTime'] = $decorator->formatEndTime($activity);
}
$metadata['title'] = $decorator->generateTitle($activity);
}
// For now, this is just a "meetingOutcome" field for some customers.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function fetchCustomFieldData(Activity $activity, string $objectType): array
{
$payload = [];
$fieldDataRepository = app(FieldDataRepository::class);
$fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);
foreach ($fieldData as $data) {
// Add the field and value to the payload.
$payload += [
$data->getField()->getCrmProviderId() => $data->getValue(),
];
}
return $payload;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity
{
if (! $activity->hasCrmProviderId()) {
$this->createActivity($activity, $engagement, $metadata);
return $activity;
}
try {
$oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());
} catch (HubspotException $e) {
$this->logger->warning('[HubSpot] Engagement not found', [
'activity_id' => $activity->getUuid(),
'crm_provider_id' => $activity->getCrmProviderId(),
'exception' => $e->getMessage(),
]);
// potentially here we can log new activity if it is not found
return $activity;
}
$type = $oldEngagement['engagement']['type'] ?? null;
$bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';
$decorator = $this->getDecorateActivity();
if ($type !== 'TASK') {
$oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldDescription
);
$oldAssociations = $oldEngagement['associations'] ?? [];
$this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);
} else {
$oldTaskBody = $oldEngagement['metadata']['body'] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldTaskBody
);
$this->updateTask($activity, $engagement, $metadata);
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateNonTaskActivity(
Activity $activity,
array $engagement,
array $metadata,
array $oldAssociations
): void {
if ($activity->isTypeConference()) {
$this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);
} else {
$this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);
}
$this->updateActivityAssociations($activity, $oldAssociations);
$this->logCrmEngagementManipulation(
self::ACTION_UPDATE,
['crmId' => $activity->getCrmProviderId()],
$metadata,
);
}
private function updateActivityAssociations(Activity $activity, array $oldAssociations): void
{
$currentAssociations = $this->getActivityAssociations($activity);
$associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);
// No changes - exit
if (empty($associationChanges)) {
return;
}
$this->logger->info('[HubSpot] Updating associations', [
'team' => $this->team->getUuid(),
'activity' => $activity->getUuid(),
'associationChanges' => $associationChanges,
]);
$this->updateAssociations($activity, $associationChanges);
}
private function getAssociationDifferences(
Activity $activity,
array $oldAssociations,
array $currentAssociations
): array {
$objects = ['contactIds', 'companyIds', 'dealIds'];
$changes = [];
foreach ($objects as $object) {
$oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);
$currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);
$added = array_diff($currentIds, $oldIds);
$removed = array_diff($oldIds, $currentIds);
$type = match ($object) {
'contactIds' => 'contact',
'companyIds' => 'company',
'dealIds' => 'deal',
};
$shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);
if (! empty($removed) && ! $shouldIncludeRemoved) {
$this->logger->info('[HubSpot] Skipping association removal', [
'team' => $this->team->getUuid(),
'type' => $type,
'oldIds' => $oldIds,
'currentIds' => $currentIds,
'skippedRemovals' => array_values($removed),
]);
}
if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {
$changes[$type] = [
'added' => array_values($added),
'removed' => array_values($removed),
];
}
}
return $changes;
}
/**
* Determines whether removed associations should be synced to HubSpot.
*
* For contacts and companies, we always sync removals (original behavior).
* For deals, we only sync removals when:
* - There are new deals being added (replacement scenario), OR
* - The activity has an account associated (indicating intentional association management)
*
* This prevents accidentally removing deal associations that exist in HubSpot
* but not in Jiminny's database (e.g., deals manually added by users in HubSpot).
*/
private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool
{
// Contacts and companies: always sync removals
if ($type !== 'deal') {
return true;
}
// Deals: sync removals if we're adding new deals (replacement)
if (! empty($added)) {
return true;
}
// Deals: sync removals only if activity has an account (intentional management)
return $activity->hasAccount();
}
private function normalizeIds(array $ids): array
{
$stringIds = array_map('strval', $ids);
$uniqueIds = array_unique($stringIds);
sort($uniqueIds);
return $uniqueIds;
}
private function updateAssociations(Activity $activity, array $associationChanges): void
{
$objectType = $activity->isTypeConference() ? 'meeting' : 'call';
foreach ($associationChanges as $type => $changes) {
if (! empty($changes['added'])) {
$associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);
$payload = $this->payloadBuilder->buildAddAssociationPayload(
$activity->getCrmProviderId(),
$changes['added'],
$associationType
);
$this->getClient()->addAssociations($objectType, $type, $payload);
}
if (! empty($changes['removed'])) {
$payload = $this->payloadBuilder->buildRemoveAssociationPayload(
$activity->getCrmProviderId(),
$changes['removed']
);
$this->getClient()->removeAssociations($objectType, $type, $payload);
}
}
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response
{
// If already set by us/client, do not override it
if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {
$this->logger->info('[HubSpot] Meeting activity type already set', [
'meetingId' => $meetingId,
'activityType' => $engagement['activityType'],
]);
unset($engagement['activityType']);
}
$payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);
return $this->getClient()->updateMeeting($meetingId, $payload);
}
private function hasEngagementActivityType(string $engagementId): bool
{
try {
$engagementData = $this->getClient()->getEngagementData($engagementId);
if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {
return true;
}
return false;
} catch (\Exception $exception) {
Sentry::captureException($exception);
$this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [
'engagementId' => $engagementId,
'exception' => $exception->getMessage(),
]);
}
return true;
}
private function updateTask(Activity $activity, array $engagement, array $metadata): void
{
$this->getClient()->deleteEngagement($activity->getCrmProviderId());
$this->logCrmEngagementManipulation(
self::ACTION_DELETE,
['crmId' => $activity->getCrmProviderId()],
);
$this->createActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createActivity(Activity $activity, array $engagement, array $metadata): void
{
$associations = $this->getActivityAssociations($activity);
$associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);
if ($activity->isTypeConference()) {
try {
$hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);
$activityId = $hsEngagement->data->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
} else {
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
$activityId = $hsEngagement->data->engagement->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
}
}
private function convertActivityMeetingAssociationsV4(Activity $activity): array
{
$associations = [];
$contactIds = $this->getParticipantsIds($activity);
foreach ($contactIds as $contactId) {
$associations[] = $this->buildAssociation(
(int) $contactId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]
);
}
if ($activity->hasAccount()) {
$crmProviderId = $activity->getAccount()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]
);
}
}
if ($activity->hasOpportunity()) {
$crmProviderId = $activity->getOpportunity()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]
);
}
}
return $associations;
}
private function buildAssociation(int $id, array $categoryToTypeIds): array
{
$types = [];
foreach ($categoryToTypeIds as $category => $typeId) {
$types[] = $this->buildAssociationType($category, $typeId);
}
return [
'to' => ['id' => (string) $id],
'types' => $types,
];
}
private function buildAssociationType(string $category, int $typeId): array
{
return [
'associationCategory' => $category,
'associationTypeId' => $typeId,
];
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createMeetingV3(array $engagement, array $associations, array $metadata): Response
{
$payload = $this->payloadBuilder->getV3MeetingPayload(
engagement: $engagement,
metadata: $metadata,
associations: $associations
);
return $this->getClient()->createMeeting($payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
private function buildSoftphonePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'CALL',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'toNumber' => $decorator->formatCallToParticipant($activity),
'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),
'status' => $decorator->convertCallStatus($activity),
'durationMilliseconds' => (int) $activity->getDuration() * 1000,
'body' => $decorator->generateDescription($activity),
];
// For now, this is just a "disposition" field to store Call Outcome.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildTextMessagePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'NOTE',
];
$metadata = [
'body' => $decorator->generateDescription($activity),
];
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function getActivityAssociations(Activity $activity): array
{
return [
'contactIds' => $this->getParticipantsIds($activity),
'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],
'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],
'ownerIds' => [],
];
}
/**
* Get DecorateActivity instance. Can be overridden in tests for mocking.
*/
protected function getDecorateActivity(): DecorateActivity
{
return new DecorateActivity();
}
public function saveFollowupActivity(Activity $activity, array $fields): ?string
{
if (empty($fields['subject'])) {
return null;
}
$associations = $this->getActivityAssociations($activity);
if (empty($fields['timestamp']) === false) {
$formatTimestamp = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['timestamp']);
$timestamp = $formatTimestamp->getTimestamp();
} else {
$timestamp = strtotime('+1 day');
}
$reminder = null;
if (empty($fields['reminder']) === false) {
$formatReminder = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['reminder']);
$reminder = $formatReminder->getTimestamp() * 1000;
}
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $timestamp * 1000,
'type' => 'TASK',
];
$metadata = [
'subject' => $fields['subject'],
'body' => '',
'status' => 'NOT_STARTED',
'taskType' => 'CALL',
'reminders' => $reminder ? [$reminder] : [],
];
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
} catch (BadRequest $e) {
$this->logger->info('[HubSpot] Failed to create followup task', [
'activity' => $activity->getUuid(),
'engagement' => $engagement,
'metadata' => $metadata,
'error' => $e->getMessage(),
]);
return null;
}
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
// We don't actually create a corresponding activity object on our side yet.
return (string) $hsEngagement->data->engagement->id;
}
private function logCrmEngagementManipulation(
string $action,
array $engagementData,
array $data = [],
array $associations = [],
): void {
$payload = ['team' => $this->team->getUuid()];
if ($action === self::ACTION_CREATE) {
$payload['engagementData'] = $engagementData;
$payload['data'] = $data;
$payload['associations'] = $associations;
}
if ($action === self::ACTION_UPDATE) {
$payload['engagementId'] = $engagementData['crmId'];
$payload['data'] = $data;
}
if ($action === self::ACTION_DELETE) {
$payload['engagementId'] = $engagementData['crmId'];
}
$this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…...
|
45187
|
NULL
|
NULL
|
NULL
|
|
45189
|
1622
|
17
|
2026-05-14T14:24:14.066994+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768654066_m2.jpg...
|
PhpStorm
|
faVsco.js – WriteCrmTrait.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
8
6
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldDataRepository;
use Jiminny\Services\Crm\Hubspot\DecorateActivity;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationCategoryV4;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationTypeIdV4;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Http\Response;
use SevenShores\Hubspot\Exceptions\HubspotException;
use Sentry;
trait WriteCrmTrait
{
private const string ACTION_CREATE = 'create';
private const string ACTION_UPDATE = 'update';
private const string ACTION_DELETE = 'delete';
/**
* @inheritdoc
*/
public function saveActivity(Activity $activity): Activity
{
switch ($activity->getCrmType()) {
case Activity::TYPE_CONFERENCE:
$activity = $this->buildConferencePayload($activity);
break;
case Activity::TYPE_SOFTPHONE:
case Activity::TYPE_SOFTPHONE_INBOUND:
$activity = $this->buildSoftphonePayload($activity);
break;
case Activity::TYPE_SMS_INBOUND:
case Activity::TYPE_SMS_OUTBOUND:
$activity = $this->buildTextMessagePayload($activity);
break;
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildConferencePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatStartTime($activity),
'type' => 'MEETING',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'meetingOutcome' => $decorator->convertMeetingOutcome($activity),
'internalMeetingNotes' => $decorator->generateDescription($activity),
];
// Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.
if (! $activity->hasCrmProviderId()) {
if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {
$metadata['startTime'] = $decorator->formatStartTime($activity);
$metadata['endTime'] = $decorator->formatEndTime($activity);
}
$metadata['title'] = $decorator->generateTitle($activity);
}
// For now, this is just a "meetingOutcome" field for some customers.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function fetchCustomFieldData(Activity $activity, string $objectType): array
{
$payload = [];
$fieldDataRepository = app(FieldDataRepository::class);
$fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);
foreach ($fieldData as $data) {
// Add the field and value to the payload.
$payload += [
$data->getField()->getCrmProviderId() => $data->getValue(),
];
}
return $payload;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity
{
if (! $activity->hasCrmProviderId()) {
$this->createActivity($activity, $engagement, $metadata);
return $activity;
}
try {
$oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());
} catch (HubspotException $e) {
$this->logger->warning('[HubSpot] Engagement not found', [
'activity_id' => $activity->getUuid(),
'crm_provider_id' => $activity->getCrmProviderId(),
'exception' => $e->getMessage(),
]);
// potentially here we can log new activity if it is not found
return $activity;
}
$type = $oldEngagement['engagement']['type'] ?? null;
$bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';
$decorator = $this->getDecorateActivity();
if ($type !== 'TASK') {
$oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldDescription
);
$oldAssociations = $oldEngagement['associations'] ?? [];
$this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);
} else {
$oldTaskBody = $oldEngagement['metadata']['body'] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldTaskBody
);
$this->updateTask($activity, $engagement, $metadata);
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateNonTaskActivity(
Activity $activity,
array $engagement,
array $metadata,
array $oldAssociations
): void {
if ($activity->isTypeConference()) {
$this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);
} else {
$this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);
}
$this->updateActivityAssociations($activity, $oldAssociations);
$this->logCrmEngagementManipulation(
self::ACTION_UPDATE,
['crmId' => $activity->getCrmProviderId()],
$metadata,
);
}
private function updateActivityAssociations(Activity $activity, array $oldAssociations): void
{
$currentAssociations = $this->getActivityAssociations($activity);
$associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);
// No changes - exit
if (empty($associationChanges)) {
return;
}
$this->logger->info('[HubSpot] Updating associations', [
'team' => $this->team->getUuid(),
'activity' => $activity->getUuid(),
'associationChanges' => $associationChanges,
]);
$this->updateAssociations($activity, $associationChanges);
}
private function getAssociationDifferences(
Activity $activity,
array $oldAssociations,
array $currentAssociations
): array {
$objects = ['contactIds', 'companyIds', 'dealIds'];
$changes = [];
foreach ($objects as $object) {
$oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);
$currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);
$added = array_diff($currentIds, $oldIds);
$removed = array_diff($oldIds, $currentIds);
$type = match ($object) {
'contactIds' => 'contact',
'companyIds' => 'company',
'dealIds' => 'deal',
};
$shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);
if (! empty($removed) && ! $shouldIncludeRemoved) {
$this->logger->info('[HubSpot] Skipping association removal', [
'team' => $this->team->getUuid(),
'type' => $type,
'oldIds' => $oldIds,
'currentIds' => $currentIds,
'skippedRemovals' => array_values($removed),
]);
}
if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {
$changes[$type] = [
'added' => array_values($added),
'removed' => array_values($removed),
];
}
}
return $changes;
}
/**
* Determines whether removed associations should be synced to HubSpot.
*
* For contacts and companies, we always sync removals (original behavior).
* For deals, we only sync removals when:
* - There are new deals being added (replacement scenario), OR
* - The activity has an account associated (indicating intentional association management)
*
* This prevents accidentally removing deal associations that exist in HubSpot
* but not in Jiminny's database (e.g., deals manually added by users in HubSpot).
*/
private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool
{
// Contacts and companies: always sync removals
if ($type !== 'deal') {
return true;
}
// Deals: sync removals if we're adding new deals (replacement)
if (! empty($added)) {
return true;
}
// Deals: sync removals only if activity has an account (intentional management)
return $activity->hasAccount();
}
private function normalizeIds(array $ids): array
{
$stringIds = array_map('strval', $ids);
$uniqueIds = array_unique($stringIds);
sort($uniqueIds);
return $uniqueIds;
}
private function updateAssociations(Activity $activity, array $associationChanges): void
{
$objectType = $activity->isTypeConference() ? 'meeting' : 'call';
foreach ($associationChanges as $type => $changes) {
if (! empty($changes['added'])) {
$associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);
$payload = $this->payloadBuilder->buildAddAssociationPayload(
$activity->getCrmProviderId(),
$changes['added'],
$associationType
);
$this->getClient()->addAssociations($objectType, $type, $payload);
}
if (! empty($changes['removed'])) {
$payload = $this->payloadBuilder->buildRemoveAssociationPayload(
$activity->getCrmProviderId(),
$changes['removed']
);
$this->getClient()->removeAssociations($objectType, $type, $payload);
}
}
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response
{
// If already set by us/client, do not override it
if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {
$this->logger->info('[HubSpot] Meeting activity type already set', [
'meetingId' => $meetingId,
'activityType' => $engagement['activityType'],
]);
unset($engagement['activityType']);
}
$payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);
return $this->getClient()->updateMeeting($meetingId, $payload);
}
private function hasEngagementActivityType(string $engagementId): bool
{
try {
$engagementData = $this->getClient()->getEngagementData($engagementId);
if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {
return true;
}
return false;
} catch (\Exception $exception) {
Sentry::captureException($exception);
$this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [
'engagementId' => $engagementId,
'exception' => $exception->getMessage(),
]);
}
return true;
}
private function updateTask(Activity $activity, array $engagement, array $metadata): void
{
$this->getClient()->deleteEngagement($activity->getCrmProviderId());
$this->logCrmEngagementManipulation(
self::ACTION_DELETE,
['crmId' => $activity->getCrmProviderId()],
);
$this->createActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createActivity(Activity $activity, array $engagement, array $metadata): void
{
$associations = $this->getActivityAssociations($activity);
$associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);
if ($activity->isTypeConference()) {
try {
$hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);
$activityId = $hsEngagement->data->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
} else {
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
$activityId = $hsEngagement->data->engagement->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
}
}
private function convertActivityMeetingAssociationsV4(Activity $activity): array
{
$associations = [];
$contactIds = $this->getParticipantsIds($activity);
foreach ($contactIds as $contactId) {
$associations[] = $this->buildAssociation(
(int) $contactId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]
);
}
if ($activity->hasAccount()) {
$crmProviderId = $activity->getAccount()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]
);
}
}
if ($activity->hasOpportunity()) {
$crmProviderId = $activity->getOpportunity()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]
);
}
}
return $associations;
}
private function buildAssociation(int $id, array $categoryToTypeIds): array
{
$types = [];
foreach ($categoryToTypeIds as $category => $typeId) {
$types[] = $this->buildAssociationType($category, $typeId);
}
return [
'to' => ['id' => (string) $id],
'types' => $types,
];
}
private function buildAssociationType(string $category, int $typeId): array
{
return [
'associationCategory' => $category,
'associationTypeId' => $typeId,
];
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createMeetingV3(array $engagement, array $associations, array $metadata): Response
{
$payload = $this->payloadBuilder->getV3MeetingPayload(
engagement: $engagement,
metadata: $metadata,
associations: $associations
);
return $this->getClient()->createMeeting($payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
private function buildSoftphonePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'CALL',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'toNumber' => $decorator->formatCallToParticipant($activity),
'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),
'status' => $decorator->convertCallStatus($activity),
'durationMilliseconds' => (int) $activity->getDuration() * 1000,
'body' => $decorator->generateDescription($activity),
];
// For now, this is just a "disposition" field to store Call Outcome.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildTextMessagePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'NOTE',
];
$metadata = [
'body' => $decorator->generateDescription($activity),
];
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function getActivityAssociations(Activity $activity): array
{
return [
'contactIds' => $this->getParticipantsIds($activity),
'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],
'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],
'ownerIds' => [],
];
}
/**
* Get DecorateActivity instance. Can be overridden in tests for mocking.
*/
protected function getDecorateActivity(): DecorateActivity
{
return new DecorateActivity();
}
public function saveFollowupActivity(Activity $activity, array $fields): ?string
{
if (empty($fields['subject'])) {
return null;
}
$associations = $this->getActivityAssociations($activity);
if (empty($fields['timestamp']) === false) {
$formatTimestamp = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['timestamp']);
$timestamp = $formatTimestamp->getTimestamp();
} else {
$timestamp = strtotime('+1 day');
}
$reminder = null;
if (empty($fields['reminder']) === false) {
$formatReminder = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['reminder']);
$reminder = $formatReminder->getTimestamp() * 1000;
}
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $timestamp * 1000,
'type' => 'TASK',
];
$metadata = [
'subject' => $fields['subject'],
'body' => '',
'status' => 'NOT_STARTED',
'taskType' => 'CALL',
'reminders' => $reminder ? [$reminder] : [],
];
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
} catch (BadRequest $e) {
$this->logger->info('[HubSpot] Failed to create followup task', [
'activity' => $activity->getUuid(),
'engagement' => $engagement,
'metadata' => $metadata,
'error' => $e->getMessage(),
]);
return null;
}
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
// We don't actually create a corresponding activity object on our side yet.
return (string) $hsEngagement->data->engagement->id;
}
private function logCrmEngagementManipulation(
string $action,
array $engagementData,
array $data = [],
array $associations = [],
): void {
$payload = ['team' => $this->team->getUuid()];
if ($action === self::ACTION_CREATE) {
$payload['engagementData'] = $engagementData;
$payload['data'] = $data;
$payload['associations'] = $associations;
}
if ($action === self::ACTION_UPDATE) {
$payload['engagementId'] = $engagementData['crmId'];
$payload['data'] = $data;
}
if ($action === self::ACTION_DELETE) {
$payload['engagementId'] = $engagementData['crmId'];
}
$this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
8
1
3
4
Previous Highlighted Error
Next Highlighted Error
SELECT
# DISTINCT
opp.id as opp_id, opp.uuid, opp.name,
# COUNT(dr.id) AS deal_risk_count,
# dr.id,
# cfd.value,
usr.name AS owner_name,
opp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,
usr.uuid as owner_uuid,
usr.photo_path as owner_photo,
usr.id AS owner_id,
jt.name as owner_job,
opp.stage_id, opp.stage_updated_at,
acc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,
rt.business_process_id AS pipeline_id
FROM opportunities opp
LEFT JOIN record_types rt ON opp.record_type_id = rt.id
LEFT JOIN users usr ON opp.user_id = usr.id
LEFT JOIN accounts acc ON opp.account_id = acc.id
LEFT JOIN job_titles jt ON usr.job_title_id = jt.id
# LEFT JOIN group_deal_risk_types gdrt ON gdrt.group_id = usr.group_id
# LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id AND dr.group_deal_risk_type_id = gdrt.id
LEFT JOIN crm_field_data cfd ON (cfd.object_id = opp.id and cfd.crm_field_id = 66814)
WHERE opp.user_id IS NOT NULL
AND opp.deleted_at IS NULL
AND opp.is_closed = 1
AND opp.is_won = 0
AND opp.close_date >= '2024-01-01 00:00:00'
AND opp.close_date <= '2024-12-31 23:59:59'
AND usr.team_id = 1
# and opp.id = 4823179
GROUP BY opp.id, cfd.value
ORDER BY
# cfd.value
CAST(cfd.value AS UNSIGNED)
# owner_name
# (SELECT COUNT(dr.id)
# FROM deal_risks dr
# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
# WHERE dr.opportunity_id = opp.id
# AND dr.is_active = 1
# AND gdrt.is_enabled = 1)
DESC
LIMIT 25
# OFFSET 0
;
select * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';
select * from crm_layout_entities where crm_layout_id = 1493;
SELECT * FROM opportunities WHERE id = 4584045;
SELECT * FROM crm_field_data WHERE object_id = 4584045;
SELECT * FROM opportunities where team_id = 1 and crm_provider_id = '0061K00000lfb9IQAQ'
SELECT
opp.id as opportunity_id,
opp.name,
COUNT(dr.id) as risk_count
FROM opportunities opp
LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id
WHERE opp.id IN (
6069574,8900938,9507638,9524799,9660490,9662824,9705675,9749758,4158460,4391812,6450439,4448422,5118945,5590675,4584045,7228149,9002408,9165534,9446720,9641778,9665149,9703344,9709280,5747948,4158491,6182565,6263970,5798120,5111315,4536978,6062352,6548383,6072095,6548225,4480986,5011422,6548381,8760540,8917554,9509986,9514392,9569009,9569011,9578490,9713604,5784397,7276612,7288405,8994421,9118219,9608148,9818911,4510535,6479693,5958049,6271674,6550448,4158331,4158483,6126571,6171615,6540943,4897466,5190896,5796182,5932762,8572433,8723698,8892697,9711001,9789264,4549188,6100831,6170064,6260799,6263653,6449936,6530871,6538978,7777651,8059269,8319918,8787049,8901150,9263153,9453207,9514738,9696700,9791062,5752018,6421452,7439134,7878923,9354763,9369285,9514396,9582506,9889949,9890216,4094311,4158495,4158496,6098128,5585661,3872564,6442149,5891604,6164746,6199593,6583474,6519684,9018490,9809006,4496897,5041324,5829430,6198319,6253504,6555763,7242914,7931055,8024125,8797814,8058559,8673347,8892695,8994420,9616219,9714970,9722004,9809439,9818918,6523177,8134147,9002915,9711422,9892713,9901719,9954210,9978435,5800810,6243518,6416114,6222251,6411974,6512456,5791953,6545606,9914780,5805540,6238986,6463838,6547680,9767049,9809437,9810885,9890855,6673493,9902036,4335521,6379871,6503799,6546077,8018765,9907556,9958433,9905855,9916179,9946741,9957877,5563344,6271838,6450815,7641128,7762567,7780592,8684810,8685786,8685787,8685788
)
GROUP BY opp.id;
SELECT COUNT(dr.id)
FROM deal_risks dr
JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
WHERE dr.opportunity_id = 5563344
# AND gdrt.group_id = usr.group_id
EXPLAIN SELECT
opp.id as opp_id, opp.uuid, opp.name,
cfd.value,
cfv.sequence,
cfv.value,
# MAX(cfd.value) AS max_cfd_value,
usr.name AS owner_name,
opp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,
usr.uuid as owner_uuid,
usr.photo_path as owner_photo,
usr.id AS owner_id,
jt.name as owner_job,
opp.stage_id, opp.stage_updated_at,
acc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,
rt.business_process_id AS pipeline_id
FROM opportunities opp
LEFT JOIN record_types rt ON opp.record_type_id = rt.id
LEFT JOIN users usr ON opp.user_id = usr.id
LEFT JOIN accounts acc ON opp.account_id = acc.id
LEFT JOIN job_titles jt ON usr.job_title_id = jt.id
LEFT JOIN crm_field_data cfd ON cfd.id = (SELECT sub_cfd.id
FROM crm_field_data sub_cfd
WHERE sub_cfd.object_id = opp.id
AND sub_cfd.crm_field_id = 66810
ORDER BY sub_cfd.updated_at DESC
LIMIT 1)
LEFT JOIN crm_field_values cfv ON cfv.crm_field_id = 66810 AND cfv.value = cfd.value
WHERE opp.user_id IS NOT NULL
AND opp.deleted_at IS NULL
AND opp.is_closed = 0
# AND opp.is_closed = 1
# AND opp.is_won = 0
AND opp.close_date >= '2024-01-01 00:00:00'
AND opp.close_date <= '2024-12-31 23:59:59'
AND usr.team_id = 1
# and opp.id = 4823179
# GROUP BY opp.id
ORDER BY
cfv.sequence DESC,
# opp.name
# owner_name
cfd.value
# CAST(cfd.value AS UNSIGNED)
# CAST(MAX(cfd.value) AS UNSIGNED)
# (SELECT COUNT(dr.id)
# FROM deal_risks dr
# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
# WHERE dr.opportunity_id = opp.id
# AND dr.is_active = 1 AND gdrt.is_enabled = 1)
DESC
# LIMIT 75, 25
;
SELECT * FROM crm_field_values WHERE crm_field_id = 66810;
SELECT f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value, fd.updated_at, fd.id, fd.created_at
FROM crm_fields f
INNER JOIN crm_field_data fd ON fd.crm_field_id = f.id
WHERE (f.crm_configuration_id = 1)
AND (f.object_type = 'opportunity')
# AND (fd.object_id IN (4158331,4158483,4158495,4158496,6069574,6263970,6171615,6540943,6545606,5829430,6198319,6263653,6548381,7242914,8797814,7228149,7439134,7878923,8134147,9002915,9118219,9165534,9354763,9369285,9446720))
AND (fd.object_id IN (4158460))
AND (f.crm_provider_id IN ('ForecastCategoryName'))
ORDER BY fd.object_id ASC, fd.updated_at DESC;
select * from crm_layouts where crm_configuration_id = 1;
select cf.* from crm_fields cf
join crm_layout_entities cle on cf.id = cle.crm_field_id
where crm_layout_id = 1493;
SELECT * FROM opportunities WHERE id = 4158460;
SELECT * FROM crm_field_data WHERE object_id = 4158460;
select * from users where team_id = 1;
SELECT * FROM users WHERE id = 7160;
select * from role_user where user_id = 3248;
select * from crm_field_values where crm_field_id = 66824;
SELECT * FROM users WHERE id = 23470;
select * from crm_configurations;
# [PASSWORD_DOTS]
select * from teams where id = 1038; # 23521, 966
select * from users where team_id = 1038;
select * from crm_configurations where id = 966;
SELECT * FROM activities WHERE uuid_to_bin('e8dc7c34-31f6-4430-bd81-2d9d8f00dd07') = uuid; # 54965762
SELECT * FROM activities WHERE uuid_to_bin('4b5727c0-69d1-428f-b8d9-b649055166e2') = uuid; # 54965764
SELECT * FROM participants WHERE activity_id = 54965764;
SELECT id, uuid, title, recording_state FROM activities WHERE uuid_to_bin('355f105d-5606-4379-b0c5-d91daf59f4ce') = uuid; # 54964075
SELECT * FROM participants WHERE activity_id = 54964075;
select f.id, slug, title, tf.team_id from team_features tf JOIN features f on tf.feature_id = f.id
where tf.team_id = 1038;
# [PASSWORD_DOTS] PD [PASSWORD_DOTS]
select * from teams where id = 1029;
SELECT * FROM crm_configurations WHERE id = 957;
SELECT * FROM activities WHERE uuid_to_bin('581a33cb-4343-44bd-ac0c-4a5cee71c5ec') = uuid; # 54963423
SELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433
SELECT * FROM activities WHERE uuid_to_bin('8b6bb2f7-e7a7-4aec-8806-fbfea7093ccd') = uuid; # 54963421
# [PASSWORD_DOTS] Close [PASSWORD_DOTS]
select * from teams where id = 1031;
SELECT * FROM crm_configurations WHERE id = 959;
SELECT * FROM activities WHERE uuid_to_bin('59c517e0-96c9-4dad-bfeb-b7bef72f8725') = uuid; # 54963517
SELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433
SELECT * FROM activities WHERE uuid_to_bin('d68311e3-ac34-49bd-bf04-88323a7e5352') = uuid; # 54966009
# [PASSWORD_DOTS] SF [PASSWORD_DOTS]
SELECT * FROM activities WHERE uuid_to_bin('2bd4cfa3-4eb5-4c77-84a7-9ed46a21c988') = uuid;
select * from roles;
select * from permissions;
select * from permission_role where permission_id = 136;
select * from migrations order by id desc;
select * from teams where id IN (1, 1037);
select * from crm_layouts where crm_configuration_id = 1;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 1493;
SELECT * FROM crm_fields WHERE id IN (1652,1661,66799,66814,66821,66836,66843,66846,66864,84752,182306,323580,380378);
select * from features;
select * from team_features where feature_id = 33;
select * from opportunities;
select * from teams;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1052 and sa.provider = 'hubspot';
SELECT * FROM accounts where id = 11512582;
select * from activities where account_id = 11512582;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.2962101,"top":1.0,"width":0.03856383,"height":-0.019952059},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#12066 on JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.33477393,"top":1.0,"width":0.122340426,"height":-0.019952059},"on_screen":true,"help_text":"Pull request #12066 exists for current branch JY-20725-handle-HS-search-rate-limit, 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.5731383,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HandleHubspotRateLimitTest","depth":6,"bounds":{"left":0.5884308,"top":1.0,"width":0.076130316,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.66456115,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.67586434,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.6871675,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.7150931,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.72639626,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.73769945,"top":1.0,"width":0.011303191,"height":-0.019952059},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"8","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldDataRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DecorateActivity;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationCategoryV4;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationTypeIdV4;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse Sentry;\n\ntrait WriteCrmTrait\n{\n private const string ACTION_CREATE = 'create';\n private const string ACTION_UPDATE = 'update';\n private const string ACTION_DELETE = 'delete';\n\n /**\n * @inheritdoc\n */\n public function saveActivity(Activity $activity): Activity\n {\n switch ($activity->getCrmType()) {\n case Activity::TYPE_CONFERENCE:\n $activity = $this->buildConferencePayload($activity);\n\n break;\n case Activity::TYPE_SOFTPHONE:\n case Activity::TYPE_SOFTPHONE_INBOUND:\n $activity = $this->buildSoftphonePayload($activity);\n\n break;\n\n case Activity::TYPE_SMS_INBOUND:\n case Activity::TYPE_SMS_OUTBOUND:\n $activity = $this->buildTextMessagePayload($activity);\n\n break;\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildConferencePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatStartTime($activity),\n 'type' => 'MEETING',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'meetingOutcome' => $decorator->convertMeetingOutcome($activity),\n 'internalMeetingNotes' => $decorator->generateDescription($activity),\n ];\n\n // Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.\n if (! $activity->hasCrmProviderId()) {\n if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {\n $metadata['startTime'] = $decorator->formatStartTime($activity);\n $metadata['endTime'] = $decorator->formatEndTime($activity);\n }\n $metadata['title'] = $decorator->generateTitle($activity);\n }\n\n // For now, this is just a \"meetingOutcome\" field for some customers.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function fetchCustomFieldData(Activity $activity, string $objectType): array\n {\n $payload = [];\n\n $fieldDataRepository = app(FieldDataRepository::class);\n $fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);\n\n foreach ($fieldData as $data) {\n // Add the field and value to the payload.\n $payload += [\n $data->getField()->getCrmProviderId() => $data->getValue(),\n ];\n }\n\n return $payload;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity\n {\n if (! $activity->hasCrmProviderId()) {\n $this->createActivity($activity, $engagement, $metadata);\n\n return $activity;\n }\n\n try {\n $oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());\n } catch (HubspotException $e) {\n $this->logger->warning('[HubSpot] Engagement not found', [\n 'activity_id' => $activity->getUuid(),\n 'crm_provider_id' => $activity->getCrmProviderId(),\n 'exception' => $e->getMessage(),\n ]);\n\n // potentially here we can log new activity if it is not found\n\n return $activity;\n }\n\n $type = $oldEngagement['engagement']['type'] ?? null;\n $bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';\n\n $decorator = $this->getDecorateActivity();\n\n if ($type !== 'TASK') {\n $oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldDescription\n );\n\n $oldAssociations = $oldEngagement['associations'] ?? [];\n $this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);\n } else {\n $oldTaskBody = $oldEngagement['metadata']['body'] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldTaskBody\n );\n $this->updateTask($activity, $engagement, $metadata);\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateNonTaskActivity(\n Activity $activity,\n array $engagement,\n array $metadata,\n array $oldAssociations\n ): void {\n if ($activity->isTypeConference()) {\n $this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);\n } else {\n $this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);\n }\n\n $this->updateActivityAssociations($activity, $oldAssociations);\n\n $this->logCrmEngagementManipulation(\n self::ACTION_UPDATE,\n ['crmId' => $activity->getCrmProviderId()],\n $metadata,\n );\n }\n\n private function updateActivityAssociations(Activity $activity, array $oldAssociations): void\n {\n $currentAssociations = $this->getActivityAssociations($activity);\n $associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);\n\n // No changes - exit\n if (empty($associationChanges)) {\n return;\n }\n\n $this->logger->info('[HubSpot] Updating associations', [\n 'team' => $this->team->getUuid(),\n 'activity' => $activity->getUuid(),\n 'associationChanges' => $associationChanges,\n ]);\n\n $this->updateAssociations($activity, $associationChanges);\n }\n\n private function getAssociationDifferences(\n Activity $activity,\n array $oldAssociations,\n array $currentAssociations\n ): array {\n $objects = ['contactIds', 'companyIds', 'dealIds'];\n $changes = [];\n\n foreach ($objects as $object) {\n $oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);\n $currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);\n\n $added = array_diff($currentIds, $oldIds);\n $removed = array_diff($oldIds, $currentIds);\n\n $type = match ($object) {\n 'contactIds' => 'contact',\n 'companyIds' => 'company',\n 'dealIds' => 'deal',\n };\n\n $shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);\n\n if (! empty($removed) && ! $shouldIncludeRemoved) {\n $this->logger->info('[HubSpot] Skipping association removal', [\n 'team' => $this->team->getUuid(),\n 'type' => $type,\n 'oldIds' => $oldIds,\n 'currentIds' => $currentIds,\n 'skippedRemovals' => array_values($removed),\n ]);\n }\n\n if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {\n $changes[$type] = [\n 'added' => array_values($added),\n 'removed' => array_values($removed),\n ];\n }\n }\n\n return $changes;\n }\n\n /**\n * Determines whether removed associations should be synced to HubSpot.\n *\n * For contacts and companies, we always sync removals (original behavior).\n * For deals, we only sync removals when:\n * - There are new deals being added (replacement scenario), OR\n * - The activity has an account associated (indicating intentional association management)\n *\n * This prevents accidentally removing deal associations that exist in HubSpot\n * but not in Jiminny's database (e.g., deals manually added by users in HubSpot).\n */\n private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool\n {\n // Contacts and companies: always sync removals\n if ($type !== 'deal') {\n return true;\n }\n\n // Deals: sync removals if we're adding new deals (replacement)\n if (! empty($added)) {\n return true;\n }\n\n // Deals: sync removals only if activity has an account (intentional management)\n return $activity->hasAccount();\n }\n\n private function normalizeIds(array $ids): array\n {\n $stringIds = array_map('strval', $ids);\n $uniqueIds = array_unique($stringIds);\n sort($uniqueIds);\n\n return $uniqueIds;\n }\n\n private function updateAssociations(Activity $activity, array $associationChanges): void\n {\n $objectType = $activity->isTypeConference() ? 'meeting' : 'call';\n\n foreach ($associationChanges as $type => $changes) {\n if (! empty($changes['added'])) {\n $associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);\n $payload = $this->payloadBuilder->buildAddAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['added'],\n $associationType\n );\n $this->getClient()->addAssociations($objectType, $type, $payload);\n }\n\n if (! empty($changes['removed'])) {\n $payload = $this->payloadBuilder->buildRemoveAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['removed']\n );\n $this->getClient()->removeAssociations($objectType, $type, $payload);\n }\n }\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response\n {\n // If already set by us/client, do not override it\n if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {\n $this->logger->info('[HubSpot] Meeting activity type already set', [\n 'meetingId' => $meetingId,\n 'activityType' => $engagement['activityType'],\n ]);\n\n unset($engagement['activityType']);\n }\n\n $payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);\n\n return $this->getClient()->updateMeeting($meetingId, $payload);\n }\n\n private function hasEngagementActivityType(string $engagementId): bool\n {\n try {\n $engagementData = $this->getClient()->getEngagementData($engagementId);\n if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {\n return true;\n }\n\n return false;\n } catch (\\Exception $exception) {\n Sentry::captureException($exception);\n\n $this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [\n 'engagementId' => $engagementId,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n return true;\n }\n\n private function updateTask(Activity $activity, array $engagement, array $metadata): void\n {\n $this->getClient()->deleteEngagement($activity->getCrmProviderId());\n\n $this->logCrmEngagementManipulation(\n self::ACTION_DELETE,\n ['crmId' => $activity->getCrmProviderId()],\n );\n\n $this->createActivity($activity, $engagement, $metadata);\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createActivity(Activity $activity, array $engagement, array $metadata): void\n {\n $associations = $this->getActivityAssociations($activity);\n $associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);\n\n if ($activity->isTypeConference()) {\n try {\n $hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);\n $activityId = $hsEngagement->data->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n } else {\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n $activityId = $hsEngagement->data->engagement->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n }\n }\n\n private function convertActivityMeetingAssociationsV4(Activity $activity): array\n {\n $associations = [];\n\n $contactIds = $this->getParticipantsIds($activity);\n\n foreach ($contactIds as $contactId) {\n $associations[] = $this->buildAssociation(\n (int) $contactId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]\n );\n }\n\n if ($activity->hasAccount()) {\n $crmProviderId = $activity->getAccount()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]\n );\n }\n }\n\n if ($activity->hasOpportunity()) {\n $crmProviderId = $activity->getOpportunity()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]\n );\n }\n }\n\n return $associations;\n }\n\n private function buildAssociation(int $id, array $categoryToTypeIds): array\n {\n $types = [];\n\n foreach ($categoryToTypeIds as $category => $typeId) {\n $types[] = $this->buildAssociationType($category, $typeId);\n }\n\n return [\n 'to' => ['id' => (string) $id],\n 'types' => $types,\n ];\n }\n\n private function buildAssociationType(string $category, int $typeId): array\n {\n return [\n 'associationCategory' => $category,\n 'associationTypeId' => $typeId,\n ];\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createMeetingV3(array $engagement, array $associations, array $metadata): Response\n {\n $payload = $this->payloadBuilder->getV3MeetingPayload(\n engagement: $engagement,\n metadata: $metadata,\n associations: $associations\n );\n\n return $this->getClient()->createMeeting($payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n private function buildSoftphonePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'CALL',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'toNumber' => $decorator->formatCallToParticipant($activity),\n 'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),\n 'status' => $decorator->convertCallStatus($activity),\n 'durationMilliseconds' => (int) $activity->getDuration() * 1000,\n 'body' => $decorator->generateDescription($activity),\n ];\n\n // For now, this is just a \"disposition\" field to store Call Outcome.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildTextMessagePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'NOTE',\n ];\n\n $metadata = [\n 'body' => $decorator->generateDescription($activity),\n ];\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function getActivityAssociations(Activity $activity): array\n {\n return [\n 'contactIds' => $this->getParticipantsIds($activity),\n 'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],\n 'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],\n 'ownerIds' => [],\n ];\n }\n\n /**\n * Get DecorateActivity instance. Can be overridden in tests for mocking.\n */\n protected function getDecorateActivity(): DecorateActivity\n {\n return new DecorateActivity();\n }\n\n public function saveFollowupActivity(Activity $activity, array $fields): ?string\n {\n if (empty($fields['subject'])) {\n return null;\n }\n\n $associations = $this->getActivityAssociations($activity);\n\n if (empty($fields['timestamp']) === false) {\n $formatTimestamp = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['timestamp']);\n $timestamp = $formatTimestamp->getTimestamp();\n } else {\n $timestamp = strtotime('+1 day');\n }\n\n $reminder = null;\n if (empty($fields['reminder']) === false) {\n $formatReminder = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['reminder']);\n $reminder = $formatReminder->getTimestamp() * 1000;\n }\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $timestamp * 1000,\n 'type' => 'TASK',\n ];\n\n $metadata = [\n 'subject' => $fields['subject'],\n 'body' => '',\n 'status' => 'NOT_STARTED',\n 'taskType' => 'CALL',\n 'reminders' => $reminder ? [$reminder] : [],\n ];\n\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n } catch (BadRequest $e) {\n $this->logger->info('[HubSpot] Failed to create followup task', [\n 'activity' => $activity->getUuid(),\n 'engagement' => $engagement,\n 'metadata' => $metadata,\n 'error' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n // We don't actually create a corresponding activity object on our side yet.\n return (string) $hsEngagement->data->engagement->id;\n }\n\n private function logCrmEngagementManipulation(\n string $action,\n array $engagementData,\n array $data = [],\n array $associations = [],\n ): void {\n $payload = ['team' => $this->team->getUuid()];\n\n if ($action === self::ACTION_CREATE) {\n $payload['engagementData'] = $engagementData;\n $payload['data'] = $data;\n $payload['associations'] = $associations;\n }\n\n if ($action === self::ACTION_UPDATE) {\n $payload['engagementId'] = $engagementData['crmId'];\n $payload['data'] = $data;\n }\n\n if ($action === self::ACTION_DELETE) {\n $payload['engagementId'] = $engagementData['crmId'];\n }\n\n $this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldDataRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DecorateActivity;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationCategoryV4;\nuse Jiminny\\Services\\Crm\\Hubspot\\Utils\\AssociationTypeIdV4;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse Sentry;\n\ntrait WriteCrmTrait\n{\n private const string ACTION_CREATE = 'create';\n private const string ACTION_UPDATE = 'update';\n private const string ACTION_DELETE = 'delete';\n\n /**\n * @inheritdoc\n */\n public function saveActivity(Activity $activity): Activity\n {\n switch ($activity->getCrmType()) {\n case Activity::TYPE_CONFERENCE:\n $activity = $this->buildConferencePayload($activity);\n\n break;\n case Activity::TYPE_SOFTPHONE:\n case Activity::TYPE_SOFTPHONE_INBOUND:\n $activity = $this->buildSoftphonePayload($activity);\n\n break;\n\n case Activity::TYPE_SMS_INBOUND:\n case Activity::TYPE_SMS_OUTBOUND:\n $activity = $this->buildTextMessagePayload($activity);\n\n break;\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildConferencePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatStartTime($activity),\n 'type' => 'MEETING',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'meetingOutcome' => $decorator->convertMeetingOutcome($activity),\n 'internalMeetingNotes' => $decorator->generateDescription($activity),\n ];\n\n // Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.\n if (! $activity->hasCrmProviderId()) {\n if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {\n $metadata['startTime'] = $decorator->formatStartTime($activity);\n $metadata['endTime'] = $decorator->formatEndTime($activity);\n }\n $metadata['title'] = $decorator->generateTitle($activity);\n }\n\n // For now, this is just a \"meetingOutcome\" field for some customers.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function fetchCustomFieldData(Activity $activity, string $objectType): array\n {\n $payload = [];\n\n $fieldDataRepository = app(FieldDataRepository::class);\n $fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);\n\n foreach ($fieldData as $data) {\n // Add the field and value to the payload.\n $payload += [\n $data->getField()->getCrmProviderId() => $data->getValue(),\n ];\n }\n\n return $payload;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity\n {\n if (! $activity->hasCrmProviderId()) {\n $this->createActivity($activity, $engagement, $metadata);\n\n return $activity;\n }\n\n try {\n $oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());\n } catch (HubspotException $e) {\n $this->logger->warning('[HubSpot] Engagement not found', [\n 'activity_id' => $activity->getUuid(),\n 'crm_provider_id' => $activity->getCrmProviderId(),\n 'exception' => $e->getMessage(),\n ]);\n\n // potentially here we can log new activity if it is not found\n\n return $activity;\n }\n\n $type = $oldEngagement['engagement']['type'] ?? null;\n $bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';\n\n $decorator = $this->getDecorateActivity();\n\n if ($type !== 'TASK') {\n $oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldDescription\n );\n\n $oldAssociations = $oldEngagement['associations'] ?? [];\n $this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);\n } else {\n $oldTaskBody = $oldEngagement['metadata']['body'] ?? '';\n $metadata[$bodyAttribute] = $decorator->mergeDescriptions(\n $metadata[$bodyAttribute],\n $oldTaskBody\n );\n $this->updateTask($activity, $engagement, $metadata);\n }\n\n return $activity;\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateNonTaskActivity(\n Activity $activity,\n array $engagement,\n array $metadata,\n array $oldAssociations\n ): void {\n if ($activity->isTypeConference()) {\n $this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);\n } else {\n $this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);\n }\n\n $this->updateActivityAssociations($activity, $oldAssociations);\n\n $this->logCrmEngagementManipulation(\n self::ACTION_UPDATE,\n ['crmId' => $activity->getCrmProviderId()],\n $metadata,\n );\n }\n\n private function updateActivityAssociations(Activity $activity, array $oldAssociations): void\n {\n $currentAssociations = $this->getActivityAssociations($activity);\n $associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);\n\n // No changes - exit\n if (empty($associationChanges)) {\n return;\n }\n\n $this->logger->info('[HubSpot] Updating associations', [\n 'team' => $this->team->getUuid(),\n 'activity' => $activity->getUuid(),\n 'associationChanges' => $associationChanges,\n ]);\n\n $this->updateAssociations($activity, $associationChanges);\n }\n\n private function getAssociationDifferences(\n Activity $activity,\n array $oldAssociations,\n array $currentAssociations\n ): array {\n $objects = ['contactIds', 'companyIds', 'dealIds'];\n $changes = [];\n\n foreach ($objects as $object) {\n $oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);\n $currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);\n\n $added = array_diff($currentIds, $oldIds);\n $removed = array_diff($oldIds, $currentIds);\n\n $type = match ($object) {\n 'contactIds' => 'contact',\n 'companyIds' => 'company',\n 'dealIds' => 'deal',\n };\n\n $shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);\n\n if (! empty($removed) && ! $shouldIncludeRemoved) {\n $this->logger->info('[HubSpot] Skipping association removal', [\n 'team' => $this->team->getUuid(),\n 'type' => $type,\n 'oldIds' => $oldIds,\n 'currentIds' => $currentIds,\n 'skippedRemovals' => array_values($removed),\n ]);\n }\n\n if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {\n $changes[$type] = [\n 'added' => array_values($added),\n 'removed' => array_values($removed),\n ];\n }\n }\n\n return $changes;\n }\n\n /**\n * Determines whether removed associations should be synced to HubSpot.\n *\n * For contacts and companies, we always sync removals (original behavior).\n * For deals, we only sync removals when:\n * - There are new deals being added (replacement scenario), OR\n * - The activity has an account associated (indicating intentional association management)\n *\n * This prevents accidentally removing deal associations that exist in HubSpot\n * but not in Jiminny's database (e.g., deals manually added by users in HubSpot).\n */\n private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool\n {\n // Contacts and companies: always sync removals\n if ($type !== 'deal') {\n return true;\n }\n\n // Deals: sync removals if we're adding new deals (replacement)\n if (! empty($added)) {\n return true;\n }\n\n // Deals: sync removals only if activity has an account (intentional management)\n return $activity->hasAccount();\n }\n\n private function normalizeIds(array $ids): array\n {\n $stringIds = array_map('strval', $ids);\n $uniqueIds = array_unique($stringIds);\n sort($uniqueIds);\n\n return $uniqueIds;\n }\n\n private function updateAssociations(Activity $activity, array $associationChanges): void\n {\n $objectType = $activity->isTypeConference() ? 'meeting' : 'call';\n\n foreach ($associationChanges as $type => $changes) {\n if (! empty($changes['added'])) {\n $associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);\n $payload = $this->payloadBuilder->buildAddAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['added'],\n $associationType\n );\n $this->getClient()->addAssociations($objectType, $type, $payload);\n }\n\n if (! empty($changes['removed'])) {\n $payload = $this->payloadBuilder->buildRemoveAssociationPayload(\n $activity->getCrmProviderId(),\n $changes['removed']\n );\n $this->getClient()->removeAssociations($objectType, $type, $payload);\n }\n }\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response\n {\n // If already set by us/client, do not override it\n if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {\n $this->logger->info('[HubSpot] Meeting activity type already set', [\n 'meetingId' => $meetingId,\n 'activityType' => $engagement['activityType'],\n ]);\n\n unset($engagement['activityType']);\n }\n\n $payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);\n\n return $this->getClient()->updateMeeting($meetingId, $payload);\n }\n\n private function hasEngagementActivityType(string $engagementId): bool\n {\n try {\n $engagementData = $this->getClient()->getEngagementData($engagementId);\n if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {\n return true;\n }\n\n return false;\n } catch (\\Exception $exception) {\n Sentry::captureException($exception);\n\n $this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [\n 'engagementId' => $engagementId,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n return true;\n }\n\n private function updateTask(Activity $activity, array $engagement, array $metadata): void\n {\n $this->getClient()->deleteEngagement($activity->getCrmProviderId());\n\n $this->logCrmEngagementManipulation(\n self::ACTION_DELETE,\n ['crmId' => $activity->getCrmProviderId()],\n );\n\n $this->createActivity($activity, $engagement, $metadata);\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createActivity(Activity $activity, array $engagement, array $metadata): void\n {\n $associations = $this->getActivityAssociations($activity);\n $associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);\n\n if ($activity->isTypeConference()) {\n try {\n $hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);\n $activityId = $hsEngagement->data->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n } else {\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n $activityId = $hsEngagement->data->engagement->id;\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n } catch (BadRequest $exception) {\n Sentry::captureException($exception);\n\n $this->logger->warning('[HubSpot] Error while creating engagements', [\n 'activity' => $activity->getUuid(),\n 'message' => $exception->getMessage(),\n 'engagement' => $engagement,\n 'associations' => $associations,\n 'metadata' => $metadata,\n ]);\n }\n }\n }\n\n private function convertActivityMeetingAssociationsV4(Activity $activity): array\n {\n $associations = [];\n\n $contactIds = $this->getParticipantsIds($activity);\n\n foreach ($contactIds as $contactId) {\n $associations[] = $this->buildAssociation(\n (int) $contactId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]\n );\n }\n\n if ($activity->hasAccount()) {\n $crmProviderId = $activity->getAccount()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]\n );\n }\n }\n\n if ($activity->hasOpportunity()) {\n $crmProviderId = $activity->getOpportunity()?->getCrmProviderId();\n if ($crmProviderId) {\n $associations[] = $this->buildAssociation(\n (int) $crmProviderId,\n [AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]\n );\n }\n }\n\n return $associations;\n }\n\n private function buildAssociation(int $id, array $categoryToTypeIds): array\n {\n $types = [];\n\n foreach ($categoryToTypeIds as $category => $typeId) {\n $types[] = $this->buildAssociationType($category, $typeId);\n }\n\n return [\n 'to' => ['id' => (string) $id],\n 'types' => $types,\n ];\n }\n\n private function buildAssociationType(string $category, int $typeId): array\n {\n return [\n 'associationCategory' => $category,\n 'associationTypeId' => $typeId,\n ];\n }\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function createMeetingV3(array $engagement, array $associations, array $metadata): Response\n {\n $payload = $this->payloadBuilder->getV3MeetingPayload(\n engagement: $engagement,\n metadata: $metadata,\n associations: $associations\n );\n\n return $this->getClient()->createMeeting($payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n private function buildSoftphonePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'CALL',\n ];\n\n if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {\n $engagement['activityType'] = $activity->getActivityType()->getName();\n }\n\n $metadata = [\n 'toNumber' => $decorator->formatCallToParticipant($activity),\n 'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),\n 'status' => $decorator->convertCallStatus($activity),\n 'durationMilliseconds' => (int) $activity->getDuration() * 1000,\n 'body' => $decorator->generateDescription($activity),\n ];\n\n // For now, this is just a \"disposition\" field to store Call Outcome.\n $metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n\n /**\n * @throws HubspotException\n * @throws BadRequest\n */\n private function buildTextMessagePayload(Activity $activity): Activity\n {\n $decorator = $this->getDecorateActivity();\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $decorator->formatCreatedAt($activity),\n 'type' => 'NOTE',\n ];\n\n $metadata = [\n 'body' => $decorator->generateDescription($activity),\n ];\n\n return $this->upsertActivity($activity, $engagement, $metadata);\n }\n\n private function getActivityAssociations(Activity $activity): array\n {\n return [\n 'contactIds' => $this->getParticipantsIds($activity),\n 'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],\n 'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],\n 'ownerIds' => [],\n ];\n }\n\n /**\n * Get DecorateActivity instance. Can be overridden in tests for mocking.\n */\n protected function getDecorateActivity(): DecorateActivity\n {\n return new DecorateActivity();\n }\n\n public function saveFollowupActivity(Activity $activity, array $fields): ?string\n {\n if (empty($fields['subject'])) {\n return null;\n }\n\n $associations = $this->getActivityAssociations($activity);\n\n if (empty($fields['timestamp']) === false) {\n $formatTimestamp = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['timestamp']);\n $timestamp = $formatTimestamp->getTimestamp();\n } else {\n $timestamp = strtotime('+1 day');\n }\n\n $reminder = null;\n if (empty($fields['reminder']) === false) {\n $formatReminder = Carbon::createFromFormat('Y-m-d\\TH:i:s\\Z', $fields['reminder']);\n $reminder = $formatReminder->getTimestamp() * 1000;\n }\n\n $engagement = [\n 'active' => true,\n 'ownerId' => $this->profile->getCrmProviderId(),\n 'timestamp' => $timestamp * 1000,\n 'type' => 'TASK',\n ];\n\n $metadata = [\n 'subject' => $fields['subject'],\n 'body' => '',\n 'status' => 'NOT_STARTED',\n 'taskType' => 'CALL',\n 'reminders' => $reminder ? [$reminder] : [],\n ];\n\n try {\n $hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);\n } catch (BadRequest $e) {\n $this->logger->info('[HubSpot] Failed to create followup task', [\n 'activity' => $activity->getUuid(),\n 'engagement' => $engagement,\n 'metadata' => $metadata,\n 'error' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);\n\n // We don't actually create a corresponding activity object on our side yet.\n return (string) $hsEngagement->data->engagement->id;\n }\n\n private function logCrmEngagementManipulation(\n string $action,\n array $engagementData,\n array $data = [],\n array $associations = [],\n ): void {\n $payload = ['team' => $this->team->getUuid()];\n\n if ($action === self::ACTION_CREATE) {\n $payload['engagementData'] = $engagementData;\n $payload['data'] = $data;\n $payload['associations'] = $associations;\n }\n\n if ($action === self::ACTION_UPDATE) {\n $payload['engagementId'] = $engagementData['crmId'];\n $payload['data'] = $data;\n }\n\n if ($action === self::ACTION_DELETE) {\n $payload['engagementId'] = $engagementData['crmId'];\n }\n\n $this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"8","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"4","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"SELECT\n# DISTINCT\nopp.id as opp_id, opp.uuid, opp.name,\n# COUNT(dr.id) AS deal_risk_count,\n\n# dr.id,\n# cfd.value,\nusr.name AS owner_name,\nopp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,\nusr.uuid as owner_uuid,\nusr.photo_path as owner_photo,\nusr.id AS owner_id,\njt.name as owner_job,\nopp.stage_id, opp.stage_updated_at,\nacc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,\nrt.business_process_id AS pipeline_id\nFROM opportunities opp\nLEFT JOIN record_types rt ON opp.record_type_id = rt.id\nLEFT JOIN users usr ON opp.user_id = usr.id\nLEFT JOIN accounts acc ON opp.account_id = acc.id\nLEFT JOIN job_titles jt ON usr.job_title_id = jt.id\n\n# LEFT JOIN group_deal_risk_types gdrt ON gdrt.group_id = usr.group_id\n# LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id AND dr.group_deal_risk_type_id = gdrt.id\n\nLEFT JOIN crm_field_data cfd ON (cfd.object_id = opp.id and cfd.crm_field_id = 66814)\n\nWHERE opp.user_id IS NOT NULL\nAND opp.deleted_at IS NULL\nAND opp.is_closed = 1\nAND opp.is_won = 0\nAND opp.close_date >= '2024-01-01 00:00:00'\nAND opp.close_date <= '2024-12-31 23:59:59'\nAND usr.team_id = 1\n\n# and opp.id = 4823179\n\nGROUP BY opp.id, cfd.value\nORDER BY\n# cfd.value\n CAST(cfd.value AS UNSIGNED)\n# owner_name\n# (SELECT COUNT(dr.id)\n# FROM deal_risks dr\n# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n# WHERE dr.opportunity_id = opp.id\n# AND dr.is_active = 1\n# AND gdrt.is_enabled = 1)\nDESC\nLIMIT 25\n# OFFSET 0\n;\n\nselect * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';\nselect * from crm_layout_entities where crm_layout_id = 1493;\n\nSELECT * FROM opportunities WHERE id = 4584045;\nSELECT * FROM crm_field_data WHERE object_id = 4584045;\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id = '0061K00000lfb9IQAQ'\n\nSELECT\n opp.id as opportunity_id,\n opp.name,\n COUNT(dr.id) as risk_count\nFROM opportunities opp\nLEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id\nWHERE opp.id IN (\n 6069574,8900938,9507638,9524799,9660490,9662824,9705675,9749758,4158460,4391812,6450439,4448422,5118945,5590675,4584045,7228149,9002408,9165534,9446720,9641778,9665149,9703344,9709280,5747948,4158491,6182565,6263970,5798120,5111315,4536978,6062352,6548383,6072095,6548225,4480986,5011422,6548381,8760540,8917554,9509986,9514392,9569009,9569011,9578490,9713604,5784397,7276612,7288405,8994421,9118219,9608148,9818911,4510535,6479693,5958049,6271674,6550448,4158331,4158483,6126571,6171615,6540943,4897466,5190896,5796182,5932762,8572433,8723698,8892697,9711001,9789264,4549188,6100831,6170064,6260799,6263653,6449936,6530871,6538978,7777651,8059269,8319918,8787049,8901150,9263153,9453207,9514738,9696700,9791062,5752018,6421452,7439134,7878923,9354763,9369285,9514396,9582506,9889949,9890216,4094311,4158495,4158496,6098128,5585661,3872564,6442149,5891604,6164746,6199593,6583474,6519684,9018490,9809006,4496897,5041324,5829430,6198319,6253504,6555763,7242914,7931055,8024125,8797814,8058559,8673347,8892695,8994420,9616219,9714970,9722004,9809439,9818918,6523177,8134147,9002915,9711422,9892713,9901719,9954210,9978435,5800810,6243518,6416114,6222251,6411974,6512456,5791953,6545606,9914780,5805540,6238986,6463838,6547680,9767049,9809437,9810885,9890855,6673493,9902036,4335521,6379871,6503799,6546077,8018765,9907556,9958433,9905855,9916179,9946741,9957877,5563344,6271838,6450815,7641128,7762567,7780592,8684810,8685786,8685787,8685788\n)\nGROUP BY opp.id;\n\nSELECT COUNT(dr.id)\n FROM deal_risks dr\n JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n WHERE dr.opportunity_id = 5563344\n# AND gdrt.group_id = usr.group_id\n\n\n\nEXPLAIN SELECT\n opp.id as opp_id, opp.uuid, opp.name,\n\ncfd.value,\ncfv.sequence,\ncfv.value,\n# MAX(cfd.value) AS max_cfd_value,\nusr.name AS owner_name,\nopp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,\nusr.uuid as owner_uuid,\nusr.photo_path as owner_photo,\nusr.id AS owner_id,\njt.name as owner_job,\nopp.stage_id, opp.stage_updated_at,\nacc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,\nrt.business_process_id AS pipeline_id\nFROM opportunities opp\n\nLEFT JOIN record_types rt ON opp.record_type_id = rt.id\nLEFT JOIN users usr ON opp.user_id = usr.id\nLEFT JOIN accounts acc ON opp.account_id = acc.id\nLEFT JOIN job_titles jt ON usr.job_title_id = jt.id\n\nLEFT JOIN crm_field_data cfd ON cfd.id = (SELECT sub_cfd.id\n FROM crm_field_data sub_cfd\n WHERE sub_cfd.object_id = opp.id\n AND sub_cfd.crm_field_id = 66810\n ORDER BY sub_cfd.updated_at DESC\n LIMIT 1)\n\nLEFT JOIN crm_field_values cfv ON cfv.crm_field_id = 66810 AND cfv.value = cfd.value\n\nWHERE opp.user_id IS NOT NULL\nAND opp.deleted_at IS NULL\nAND opp.is_closed = 0\n# AND opp.is_closed = 1\n# AND opp.is_won = 0\nAND opp.close_date >= '2024-01-01 00:00:00'\nAND opp.close_date <= '2024-12-31 23:59:59'\nAND usr.team_id = 1\n\n# and opp.id = 4823179\n\n# GROUP BY opp.id\nORDER BY\n cfv.sequence DESC,\n# opp.name\n# owner_name\n cfd.value\n# CAST(cfd.value AS UNSIGNED)\n# CAST(MAX(cfd.value) AS UNSIGNED)\n# (SELECT COUNT(dr.id)\n# FROM deal_risks dr\n# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n# WHERE dr.opportunity_id = opp.id\n# AND dr.is_active = 1 AND gdrt.is_enabled = 1)\nDESC\n# LIMIT 75, 25\n;\n\n\n\nSELECT * FROM crm_field_values WHERE crm_field_id = 66810;\n\nSELECT f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value, fd.updated_at, fd.id, fd.created_at\nFROM crm_fields f\nINNER JOIN crm_field_data fd ON fd.crm_field_id = f.id\n WHERE (f.crm_configuration_id = 1)\n AND (f.object_type = 'opportunity')\n# AND (fd.object_id IN (4158331,4158483,4158495,4158496,6069574,6263970,6171615,6540943,6545606,5829430,6198319,6263653,6548381,7242914,8797814,7228149,7439134,7878923,8134147,9002915,9118219,9165534,9354763,9369285,9446720))\n AND (fd.object_id IN (4158460))\n AND (f.crm_provider_id IN ('ForecastCategoryName'))\n ORDER BY fd.object_id ASC, fd.updated_at DESC;\n\nselect * from crm_layouts where crm_configuration_id = 1;\nselect cf.* from crm_fields cf\njoin crm_layout_entities cle on cf.id = cle.crm_field_id\nwhere crm_layout_id = 1493;\n\nSELECT * FROM opportunities WHERE id = 4158460;\nSELECT * FROM crm_field_data WHERE object_id = 4158460;\n\nselect * from users where team_id = 1;\nSELECT * FROM users WHERE id = 7160;\nselect * from role_user where user_id = 3248;\n\nselect * from crm_field_values where crm_field_id = 66824;\n\nSELECT * FROM users WHERE id = 23470;\nselect * from crm_configurations;\n\n# ******************************************\nselect * from teams where id = 1038; # 23521, 966\nselect * from users where team_id = 1038;\nselect * from crm_configurations where id = 966;\nSELECT * FROM activities WHERE uuid_to_bin('e8dc7c34-31f6-4430-bd81-2d9d8f00dd07') = uuid; # 54965762\nSELECT * FROM activities WHERE uuid_to_bin('4b5727c0-69d1-428f-b8d9-b649055166e2') = uuid; # 54965764\nSELECT * FROM participants WHERE activity_id = 54965764;\n\nSELECT id, uuid, title, recording_state FROM activities WHERE uuid_to_bin('355f105d-5606-4379-b0c5-d91daf59f4ce') = uuid; # 54964075\nSELECT * FROM participants WHERE activity_id = 54964075;\nselect f.id, slug, title, tf.team_id from team_features tf JOIN features f on tf.feature_id = f.id\n where tf.team_id = 1038;\n\n\n# ****************************************** PD *************************\nselect * from teams where id = 1029;\nSELECT * FROM crm_configurations WHERE id = 957;\nSELECT * FROM activities WHERE uuid_to_bin('581a33cb-4343-44bd-ac0c-4a5cee71c5ec') = uuid; # 54963423\nSELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433\n\nSELECT * FROM activities WHERE uuid_to_bin('8b6bb2f7-e7a7-4aec-8806-fbfea7093ccd') = uuid; # 54963421\n\n# ****************************************** Close *************************\nselect * from teams where id = 1031;\nSELECT * FROM crm_configurations WHERE id = 959;\nSELECT * FROM activities WHERE uuid_to_bin('59c517e0-96c9-4dad-bfeb-b7bef72f8725') = uuid; # 54963517\n\n\nSELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433\n\nSELECT * FROM activities WHERE uuid_to_bin('d68311e3-ac34-49bd-bf04-88323a7e5352') = uuid; # 54966009\n\n# ****************************************** SF *************************\nSELECT * FROM activities WHERE uuid_to_bin('2bd4cfa3-4eb5-4c77-84a7-9ed46a21c988') = uuid;\n\nselect * from roles;\nselect * from permissions;\nselect * from permission_role where permission_id = 136;\n\nselect * from migrations order by id desc;\n\nselect * from teams where id IN (1, 1037);\nselect * from crm_layouts where crm_configuration_id = 1;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1493;\nSELECT * FROM crm_fields WHERE id IN (1652,1661,66799,66814,66821,66836,66843,66846,66864,84752,182306,323580,380378);\n\nselect * from features;\nselect * from team_features where feature_id = 33;\nselect * from opportunities;\n\nselect * from teams;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1052 and sa.provider = 'hubspot';\n\nSELECT * FROM accounts where id = 11512582;\nselect * from activities where account_id = 11512582;","depth":4,"on_screen":true,"value":"SELECT\n# DISTINCT\nopp.id as opp_id, opp.uuid, opp.name,\n# COUNT(dr.id) AS deal_risk_count,\n\n# dr.id,\n# cfd.value,\nusr.name AS owner_name,\nopp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,\nusr.uuid as owner_uuid,\nusr.photo_path as owner_photo,\nusr.id AS owner_id,\njt.name as owner_job,\nopp.stage_id, opp.stage_updated_at,\nacc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,\nrt.business_process_id AS pipeline_id\nFROM opportunities opp\nLEFT JOIN record_types rt ON opp.record_type_id = rt.id\nLEFT JOIN users usr ON opp.user_id = usr.id\nLEFT JOIN accounts acc ON opp.account_id = acc.id\nLEFT JOIN job_titles jt ON usr.job_title_id = jt.id\n\n# LEFT JOIN group_deal_risk_types gdrt ON gdrt.group_id = usr.group_id\n# LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id AND dr.group_deal_risk_type_id = gdrt.id\n\nLEFT JOIN crm_field_data cfd ON (cfd.object_id = opp.id and cfd.crm_field_id = 66814)\n\nWHERE opp.user_id IS NOT NULL\nAND opp.deleted_at IS NULL\nAND opp.is_closed = 1\nAND opp.is_won = 0\nAND opp.close_date >= '2024-01-01 00:00:00'\nAND opp.close_date <= '2024-12-31 23:59:59'\nAND usr.team_id = 1\n\n# and opp.id = 4823179\n\nGROUP BY opp.id, cfd.value\nORDER BY\n# cfd.value\n CAST(cfd.value AS UNSIGNED)\n# owner_name\n# (SELECT COUNT(dr.id)\n# FROM deal_risks dr\n# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n# WHERE dr.opportunity_id = opp.id\n# AND dr.is_active = 1\n# AND gdrt.is_enabled = 1)\nDESC\nLIMIT 25\n# OFFSET 0\n;\n\nselect * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';\nselect * from crm_layout_entities where crm_layout_id = 1493;\n\nSELECT * FROM opportunities WHERE id = 4584045;\nSELECT * FROM crm_field_data WHERE object_id = 4584045;\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id = '0061K00000lfb9IQAQ'\n\nSELECT\n opp.id as opportunity_id,\n opp.name,\n COUNT(dr.id) as risk_count\nFROM opportunities opp\nLEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id\nWHERE opp.id IN (\n 6069574,8900938,9507638,9524799,9660490,9662824,9705675,9749758,4158460,4391812,6450439,4448422,5118945,5590675,4584045,7228149,9002408,9165534,9446720,9641778,9665149,9703344,9709280,5747948,4158491,6182565,6263970,5798120,5111315,4536978,6062352,6548383,6072095,6548225,4480986,5011422,6548381,8760540,8917554,9509986,9514392,9569009,9569011,9578490,9713604,5784397,7276612,7288405,8994421,9118219,9608148,9818911,4510535,6479693,5958049,6271674,6550448,4158331,4158483,6126571,6171615,6540943,4897466,5190896,5796182,5932762,8572433,8723698,8892697,9711001,9789264,4549188,6100831,6170064,6260799,6263653,6449936,6530871,6538978,7777651,8059269,8319918,8787049,8901150,9263153,9453207,9514738,9696700,9791062,5752018,6421452,7439134,7878923,9354763,9369285,9514396,9582506,9889949,9890216,4094311,4158495,4158496,6098128,5585661,3872564,6442149,5891604,6164746,6199593,6583474,6519684,9018490,9809006,4496897,5041324,5829430,6198319,6253504,6555763,7242914,7931055,8024125,8797814,8058559,8673347,8892695,8994420,9616219,9714970,9722004,9809439,9818918,6523177,8134147,9002915,9711422,9892713,9901719,9954210,9978435,5800810,6243518,6416114,6222251,6411974,6512456,5791953,6545606,9914780,5805540,6238986,6463838,6547680,9767049,9809437,9810885,9890855,6673493,9902036,4335521,6379871,6503799,6546077,8018765,9907556,9958433,9905855,9916179,9946741,9957877,5563344,6271838,6450815,7641128,7762567,7780592,8684810,8685786,8685787,8685788\n)\nGROUP BY opp.id;\n\nSELECT COUNT(dr.id)\n FROM deal_risks dr\n JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n WHERE dr.opportunity_id = 5563344\n# AND gdrt.group_id = usr.group_id\n\n\n\nEXPLAIN SELECT\n opp.id as opp_id, opp.uuid, opp.name,\n\ncfd.value,\ncfv.sequence,\ncfv.value,\n# MAX(cfd.value) AS max_cfd_value,\nusr.name AS owner_name,\nopp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,\nusr.uuid as owner_uuid,\nusr.photo_path as owner_photo,\nusr.id AS owner_id,\njt.name as owner_job,\nopp.stage_id, opp.stage_updated_at,\nacc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,\nrt.business_process_id AS pipeline_id\nFROM opportunities opp\n\nLEFT JOIN record_types rt ON opp.record_type_id = rt.id\nLEFT JOIN users usr ON opp.user_id = usr.id\nLEFT JOIN accounts acc ON opp.account_id = acc.id\nLEFT JOIN job_titles jt ON usr.job_title_id = jt.id\n\nLEFT JOIN crm_field_data cfd ON cfd.id = (SELECT sub_cfd.id\n FROM crm_field_data sub_cfd\n WHERE sub_cfd.object_id = opp.id\n AND sub_cfd.crm_field_id = 66810\n ORDER BY sub_cfd.updated_at DESC\n LIMIT 1)\n\nLEFT JOIN crm_field_values cfv ON cfv.crm_field_id = 66810 AND cfv.value = cfd.value\n\nWHERE opp.user_id IS NOT NULL\nAND opp.deleted_at IS NULL\nAND opp.is_closed = 0\n# AND opp.is_closed = 1\n# AND opp.is_won = 0\nAND opp.close_date >= '2024-01-01 00:00:00'\nAND opp.close_date <= '2024-12-31 23:59:59'\nAND usr.team_id = 1\n\n# and opp.id = 4823179\n\n# GROUP BY opp.id\nORDER BY\n cfv.sequence DESC,\n# opp.name\n# owner_name\n cfd.value\n# CAST(cfd.value AS UNSIGNED)\n# CAST(MAX(cfd.value) AS UNSIGNED)\n# (SELECT COUNT(dr.id)\n# FROM deal_risks dr\n# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id\n# WHERE dr.opportunity_id = opp.id\n# AND dr.is_active = 1 AND gdrt.is_enabled = 1)\nDESC\n# LIMIT 75, 25\n;\n\n\n\nSELECT * FROM crm_field_values WHERE crm_field_id = 66810;\n\nSELECT f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value, fd.updated_at, fd.id, fd.created_at\nFROM crm_fields f\nINNER JOIN crm_field_data fd ON fd.crm_field_id = f.id\n WHERE (f.crm_configuration_id = 1)\n AND (f.object_type = 'opportunity')\n# AND (fd.object_id IN (4158331,4158483,4158495,4158496,6069574,6263970,6171615,6540943,6545606,5829430,6198319,6263653,6548381,7242914,8797814,7228149,7439134,7878923,8134147,9002915,9118219,9165534,9354763,9369285,9446720))\n AND (fd.object_id IN (4158460))\n AND (f.crm_provider_id IN ('ForecastCategoryName'))\n ORDER BY fd.object_id ASC, fd.updated_at DESC;\n\nselect * from crm_layouts where crm_configuration_id = 1;\nselect cf.* from crm_fields cf\njoin crm_layout_entities cle on cf.id = cle.crm_field_id\nwhere crm_layout_id = 1493;\n\nSELECT * FROM opportunities WHERE id = 4158460;\nSELECT * FROM crm_field_data WHERE object_id = 4158460;\n\nselect * from users where team_id = 1;\nSELECT * FROM users WHERE id = 7160;\nselect * from role_user where user_id = 3248;\n\nselect * from crm_field_values where crm_field_id = 66824;\n\nSELECT * FROM users WHERE id = 23470;\nselect * from crm_configurations;\n\n# ******************************************\nselect * from teams where id = 1038; # 23521, 966\nselect * from users where team_id = 1038;\nselect * from crm_configurations where id = 966;\nSELECT * FROM activities WHERE uuid_to_bin('e8dc7c34-31f6-4430-bd81-2d9d8f00dd07') = uuid; # 54965762\nSELECT * FROM activities WHERE uuid_to_bin('4b5727c0-69d1-428f-b8d9-b649055166e2') = uuid; # 54965764\nSELECT * FROM participants WHERE activity_id = 54965764;\n\nSELECT id, uuid, title, recording_state FROM activities WHERE uuid_to_bin('355f105d-5606-4379-b0c5-d91daf59f4ce') = uuid; # 54964075\nSELECT * FROM participants WHERE activity_id = 54964075;\nselect f.id, slug, title, tf.team_id from team_features tf JOIN features f on tf.feature_id = f.id\n where tf.team_id = 1038;\n\n\n# ****************************************** PD *************************\nselect * from teams where id = 1029;\nSELECT * FROM crm_configurations WHERE id = 957;\nSELECT * FROM activities WHERE uuid_to_bin('581a33cb-4343-44bd-ac0c-4a5cee71c5ec') = uuid; # 54963423\nSELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433\n\nSELECT * FROM activities WHERE uuid_to_bin('8b6bb2f7-e7a7-4aec-8806-fbfea7093ccd') = uuid; # 54963421\n\n# ****************************************** Close *************************\nselect * from teams where id = 1031;\nSELECT * FROM crm_configurations WHERE id = 959;\nSELECT * FROM activities WHERE uuid_to_bin('59c517e0-96c9-4dad-bfeb-b7bef72f8725') = uuid; # 54963517\n\n\nSELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433\n\nSELECT * FROM activities WHERE uuid_to_bin('d68311e3-ac34-49bd-bf04-88323a7e5352') = uuid; # 54966009\n\n# ****************************************** SF *************************\nSELECT * FROM activities WHERE uuid_to_bin('2bd4cfa3-4eb5-4c77-84a7-9ed46a21c988') = uuid;\n\nselect * from roles;\nselect * from permissions;\nselect * from permission_role where permission_id = 136;\n\nselect * from migrations order by id desc;\n\nselect * from teams where id IN (1, 1037);\nselect * from crm_layouts where crm_configuration_id = 1;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1493;\nSELECT * FROM crm_fields WHERE id IN (1652,1661,66799,66814,66821,66836,66843,66846,66864,84752,182306,323580,380378);\n\nselect * from features;\nselect * from team_features where feature_id = 33;\nselect * from opportunities;\n\nselect * from teams;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1052 and sa.provider = 'hubspot';\n\nSELECT * FROM accounts where id = 11512582;\nselect * from activities where account_id = 11512582;","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.28224733,"top":1.0,"width":0.024268618,"height":-0.04788506},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3382584868951134281
|
6551498163460912845
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
#12066 on JY-20725-handle Project: faVsco.js, menu
#12066 on JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
8
6
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldDataRepository;
use Jiminny\Services\Crm\Hubspot\DecorateActivity;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationCategoryV4;
use Jiminny\Services\Crm\Hubspot\Utils\AssociationTypeIdV4;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Http\Response;
use SevenShores\Hubspot\Exceptions\HubspotException;
use Sentry;
trait WriteCrmTrait
{
private const string ACTION_CREATE = 'create';
private const string ACTION_UPDATE = 'update';
private const string ACTION_DELETE = 'delete';
/**
* @inheritdoc
*/
public function saveActivity(Activity $activity): Activity
{
switch ($activity->getCrmType()) {
case Activity::TYPE_CONFERENCE:
$activity = $this->buildConferencePayload($activity);
break;
case Activity::TYPE_SOFTPHONE:
case Activity::TYPE_SOFTPHONE_INBOUND:
$activity = $this->buildSoftphonePayload($activity);
break;
case Activity::TYPE_SMS_INBOUND:
case Activity::TYPE_SMS_OUTBOUND:
$activity = $this->buildTextMessagePayload($activity);
break;
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildConferencePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatStartTime($activity),
'type' => 'MEETING',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'meetingOutcome' => $decorator->convertMeetingOutcome($activity),
'internalMeetingNotes' => $decorator->generateDescription($activity),
];
// Only if they are creating a fresh engagement should we update the times, otherwise it will cause a cal sync.
if (! $activity->hasCrmProviderId()) {
if ($activity->hasActualStartTime() && $activity->hasActualEndTime()) {
$metadata['startTime'] = $decorator->formatStartTime($activity);
$metadata['endTime'] = $decorator->formatEndTime($activity);
}
$metadata['title'] = $decorator->generateTitle($activity);
}
// For now, this is just a "meetingOutcome" field for some customers.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function fetchCustomFieldData(Activity $activity, string $objectType): array
{
$payload = [];
$fieldDataRepository = app(FieldDataRepository::class);
$fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);
foreach ($fieldData as $data) {
// Add the field and value to the payload.
$payload += [
$data->getField()->getCrmProviderId() => $data->getValue(),
];
}
return $payload;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function upsertActivity(Activity $activity, array $engagement, array $metadata): Activity
{
if (! $activity->hasCrmProviderId()) {
$this->createActivity($activity, $engagement, $metadata);
return $activity;
}
try {
$oldEngagement = $this->getClient()->getEngagementData($activity->getCrmProviderId());
} catch (HubspotException $e) {
$this->logger->warning('[HubSpot] Engagement not found', [
'activity_id' => $activity->getUuid(),
'crm_provider_id' => $activity->getCrmProviderId(),
'exception' => $e->getMessage(),
]);
// potentially here we can log new activity if it is not found
return $activity;
}
$type = $oldEngagement['engagement']['type'] ?? null;
$bodyAttribute = $activity->isTypeConference() ? 'internalMeetingNotes' : 'body';
$decorator = $this->getDecorateActivity();
if ($type !== 'TASK') {
$oldDescription = $oldEngagement['metadata'][$bodyAttribute] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldDescription
);
$oldAssociations = $oldEngagement['associations'] ?? [];
$this->updateNonTaskActivity($activity, $engagement, $metadata, $oldAssociations);
} else {
$oldTaskBody = $oldEngagement['metadata']['body'] ?? '';
$metadata[$bodyAttribute] = $decorator->mergeDescriptions(
$metadata[$bodyAttribute],
$oldTaskBody
);
$this->updateTask($activity, $engagement, $metadata);
}
return $activity;
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateNonTaskActivity(
Activity $activity,
array $engagement,
array $metadata,
array $oldAssociations
): void {
if ($activity->isTypeConference()) {
$this->updateMeetingV3($activity->getCrmProviderId(), $engagement, $metadata);
} else {
$this->getClient()->updateEngagement($activity->getCrmProviderId(), $engagement, $metadata);
}
$this->updateActivityAssociations($activity, $oldAssociations);
$this->logCrmEngagementManipulation(
self::ACTION_UPDATE,
['crmId' => $activity->getCrmProviderId()],
$metadata,
);
}
private function updateActivityAssociations(Activity $activity, array $oldAssociations): void
{
$currentAssociations = $this->getActivityAssociations($activity);
$associationChanges = $this->getAssociationDifferences($activity, $oldAssociations, $currentAssociations);
// No changes - exit
if (empty($associationChanges)) {
return;
}
$this->logger->info('[HubSpot] Updating associations', [
'team' => $this->team->getUuid(),
'activity' => $activity->getUuid(),
'associationChanges' => $associationChanges,
]);
$this->updateAssociations($activity, $associationChanges);
}
private function getAssociationDifferences(
Activity $activity,
array $oldAssociations,
array $currentAssociations
): array {
$objects = ['contactIds', 'companyIds', 'dealIds'];
$changes = [];
foreach ($objects as $object) {
$oldIds = $this->normalizeIds($oldAssociations[$object] ?? []);
$currentIds = $this->normalizeIds($currentAssociations[$object] ?? []);
$added = array_diff($currentIds, $oldIds);
$removed = array_diff($oldIds, $currentIds);
$type = match ($object) {
'contactIds' => 'contact',
'companyIds' => 'company',
'dealIds' => 'deal',
};
$shouldIncludeRemoved = $this->shouldIncludeRemovedAssociations($type, $added, $activity);
if (! empty($removed) && ! $shouldIncludeRemoved) {
$this->logger->info('[HubSpot] Skipping association removal', [
'team' => $this->team->getUuid(),
'type' => $type,
'oldIds' => $oldIds,
'currentIds' => $currentIds,
'skippedRemovals' => array_values($removed),
]);
}
if (! empty($added) || (! empty($removed) && $shouldIncludeRemoved)) {
$changes[$type] = [
'added' => array_values($added),
'removed' => array_values($removed),
];
}
}
return $changes;
}
/**
* Determines whether removed associations should be synced to HubSpot.
*
* For contacts and companies, we always sync removals (original behavior).
* For deals, we only sync removals when:
* - There are new deals being added (replacement scenario), OR
* - The activity has an account associated (indicating intentional association management)
*
* This prevents accidentally removing deal associations that exist in HubSpot
* but not in Jiminny's database (e.g., deals manually added by users in HubSpot).
*/
private function shouldIncludeRemovedAssociations(string $type, array $added, Activity $activity): bool
{
// Contacts and companies: always sync removals
if ($type !== 'deal') {
return true;
}
// Deals: sync removals if we're adding new deals (replacement)
if (! empty($added)) {
return true;
}
// Deals: sync removals only if activity has an account (intentional management)
return $activity->hasAccount();
}
private function normalizeIds(array $ids): array
{
$stringIds = array_map('strval', $ids);
$uniqueIds = array_unique($stringIds);
sort($uniqueIds);
return $uniqueIds;
}
private function updateAssociations(Activity $activity, array $associationChanges): void
{
$objectType = $activity->isTypeConference() ? 'meeting' : 'call';
foreach ($associationChanges as $type => $changes) {
if (! empty($changes['added'])) {
$associationType = (new AssociationTypeIdV4())->getAssociationType($objectType, $type);
$payload = $this->payloadBuilder->buildAddAssociationPayload(
$activity->getCrmProviderId(),
$changes['added'],
$associationType
);
$this->getClient()->addAssociations($objectType, $type, $payload);
}
if (! empty($changes['removed'])) {
$payload = $this->payloadBuilder->buildRemoveAssociationPayload(
$activity->getCrmProviderId(),
$changes['removed']
);
$this->getClient()->removeAssociations($objectType, $type, $payload);
}
}
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function updateMeetingV3(string $meetingId, array $engagement, array $metadata): Response
{
// If already set by us/client, do not override it
if (isset($engagement['activityType']) && $this->hasEngagementActivityType($meetingId)) {
$this->logger->info('[HubSpot] Meeting activity type already set', [
'meetingId' => $meetingId,
'activityType' => $engagement['activityType'],
]);
unset($engagement['activityType']);
}
$payload = $this->payloadBuilder->getV3MeetingPayload($engagement, $metadata);
return $this->getClient()->updateMeeting($meetingId, $payload);
}
private function hasEngagementActivityType(string $engagementId): bool
{
try {
$engagementData = $this->getClient()->getEngagementData($engagementId);
if (isset($engagementData['engagement']['activityType']) && trim($engagementData['engagement']['activityType']) != '') {
return true;
}
return false;
} catch (\Exception $exception) {
Sentry::captureException($exception);
$this->logger->info('[HubSpot] Exception checking engagement activity type, defaulting to preserve existing', [
'engagementId' => $engagementId,
'exception' => $exception->getMessage(),
]);
}
return true;
}
private function updateTask(Activity $activity, array $engagement, array $metadata): void
{
$this->getClient()->deleteEngagement($activity->getCrmProviderId());
$this->logCrmEngagementManipulation(
self::ACTION_DELETE,
['crmId' => $activity->getCrmProviderId()],
);
$this->createActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createActivity(Activity $activity, array $engagement, array $metadata): void
{
$associations = $this->getActivityAssociations($activity);
$associationsV4 = $this->convertActivityMeetingAssociationsV4($activity);
if ($activity->isTypeConference()) {
try {
$hsEngagement = $this->createMeetingV3($engagement, $associationsV4, $metadata);
$activityId = $hsEngagement->data->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associationsV4);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
} else {
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
$activityId = $hsEngagement->data->engagement->id;
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
$activity->crm_provider_id = $activityId;
$activity->save();
} catch (BadRequest $exception) {
Sentry::captureException($exception);
$this->logger->warning('[HubSpot] Error while creating engagements', [
'activity' => $activity->getUuid(),
'message' => $exception->getMessage(),
'engagement' => $engagement,
'associations' => $associations,
'metadata' => $metadata,
]);
}
}
}
private function convertActivityMeetingAssociationsV4(Activity $activity): array
{
$associations = [];
$contactIds = $this->getParticipantsIds($activity);
foreach ($contactIds as $contactId) {
$associations[] = $this->buildAssociation(
(int) $contactId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_CONTACT]
);
}
if ($activity->hasAccount()) {
$crmProviderId = $activity->getAccount()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_COMPANY]
);
}
}
if ($activity->hasOpportunity()) {
$crmProviderId = $activity->getOpportunity()?->getCrmProviderId();
if ($crmProviderId) {
$associations[] = $this->buildAssociation(
(int) $crmProviderId,
[AssociationCategoryV4::HUBSPOT_DEFINED => AssociationTypeIdV4::MEETING_TO_DEAL]
);
}
}
return $associations;
}
private function buildAssociation(int $id, array $categoryToTypeIds): array
{
$types = [];
foreach ($categoryToTypeIds as $category => $typeId) {
$types[] = $this->buildAssociationType($category, $typeId);
}
return [
'to' => ['id' => (string) $id],
'types' => $types,
];
}
private function buildAssociationType(string $category, int $typeId): array
{
return [
'associationCategory' => $category,
'associationTypeId' => $typeId,
];
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function createMeetingV3(array $engagement, array $associations, array $metadata): Response
{
$payload = $this->payloadBuilder->getV3MeetingPayload(
engagement: $engagement,
metadata: $metadata,
associations: $associations
);
return $this->getClient()->createMeeting($payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
private function buildSoftphonePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'CALL',
];
if ($activity->hasActivityType() && $this->matchesCrmType($activity->getActivityType())) {
$engagement['activityType'] = $activity->getActivityType()->getName();
}
$metadata = [
'toNumber' => $decorator->formatCallToParticipant($activity),
'fromNumber' => $activity->getFromParticipant()?->getPhoneNumber(),
'status' => $decorator->convertCallStatus($activity),
'durationMilliseconds' => (int) $activity->getDuration() * 1000,
'body' => $decorator->generateDescription($activity),
];
// For now, this is just a "disposition" field to store Call Outcome.
$metadata = array_merge($metadata, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));
return $this->upsertActivity($activity, $engagement, $metadata);
}
/**
* @throws HubspotException
* @throws BadRequest
*/
private function buildTextMessagePayload(Activity $activity): Activity
{
$decorator = $this->getDecorateActivity();
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $decorator->formatCreatedAt($activity),
'type' => 'NOTE',
];
$metadata = [
'body' => $decorator->generateDescription($activity),
];
return $this->upsertActivity($activity, $engagement, $metadata);
}
private function getActivityAssociations(Activity $activity): array
{
return [
'contactIds' => $this->getParticipantsIds($activity),
'companyIds' => $activity->hasAccount() ? [$activity->getAccount()->getCrmProviderId()] : [],
'dealIds' => $activity->hasOpportunity() ? [$activity->getOpportunity()->getCrmProviderId()] : [],
'ownerIds' => [],
];
}
/**
* Get DecorateActivity instance. Can be overridden in tests for mocking.
*/
protected function getDecorateActivity(): DecorateActivity
{
return new DecorateActivity();
}
public function saveFollowupActivity(Activity $activity, array $fields): ?string
{
if (empty($fields['subject'])) {
return null;
}
$associations = $this->getActivityAssociations($activity);
if (empty($fields['timestamp']) === false) {
$formatTimestamp = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['timestamp']);
$timestamp = $formatTimestamp->getTimestamp();
} else {
$timestamp = strtotime('+1 day');
}
$reminder = null;
if (empty($fields['reminder']) === false) {
$formatReminder = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $fields['reminder']);
$reminder = $formatReminder->getTimestamp() * 1000;
}
$engagement = [
'active' => true,
'ownerId' => $this->profile->getCrmProviderId(),
'timestamp' => $timestamp * 1000,
'type' => 'TASK',
];
$metadata = [
'subject' => $fields['subject'],
'body' => '',
'status' => 'NOT_STARTED',
'taskType' => 'CALL',
'reminders' => $reminder ? [$reminder] : [],
];
try {
$hsEngagement = $this->getClient()->createEngagement($engagement, $associations, $metadata);
} catch (BadRequest $e) {
$this->logger->info('[HubSpot] Failed to create followup task', [
'activity' => $activity->getUuid(),
'engagement' => $engagement,
'metadata' => $metadata,
'error' => $e->getMessage(),
]);
return null;
}
$this->logCrmEngagementManipulation(self::ACTION_CREATE, $engagement, $metadata, $associations);
// We don't actually create a corresponding activity object on our side yet.
return (string) $hsEngagement->data->engagement->id;
}
private function logCrmEngagementManipulation(
string $action,
array $engagementData,
array $data = [],
array $associations = [],
): void {
$payload = ['team' => $this->team->getUuid()];
if ($action === self::ACTION_CREATE) {
$payload['engagementData'] = $engagementData;
$payload['data'] = $data;
$payload['associations'] = $associations;
}
if ($action === self::ACTION_UPDATE) {
$payload['engagementId'] = $engagementData['crmId'];
$payload['data'] = $data;
}
if ($action === self::ACTION_DELETE) {
$payload['engagementId'] = $engagementData['crmId'];
}
$this->logger->info('[HubSpot] Engagement manipulated (' . $action . ')', $payload);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
8
1
3
4
Previous Highlighted Error
Next Highlighted Error
SELECT
# DISTINCT
opp.id as opp_id, opp.uuid, opp.name,
# COUNT(dr.id) AS deal_risk_count,
# dr.id,
# cfd.value,
usr.name AS owner_name,
opp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,
usr.uuid as owner_uuid,
usr.photo_path as owner_photo,
usr.id AS owner_id,
jt.name as owner_job,
opp.stage_id, opp.stage_updated_at,
acc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,
rt.business_process_id AS pipeline_id
FROM opportunities opp
LEFT JOIN record_types rt ON opp.record_type_id = rt.id
LEFT JOIN users usr ON opp.user_id = usr.id
LEFT JOIN accounts acc ON opp.account_id = acc.id
LEFT JOIN job_titles jt ON usr.job_title_id = jt.id
# LEFT JOIN group_deal_risk_types gdrt ON gdrt.group_id = usr.group_id
# LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id AND dr.group_deal_risk_type_id = gdrt.id
LEFT JOIN crm_field_data cfd ON (cfd.object_id = opp.id and cfd.crm_field_id = 66814)
WHERE opp.user_id IS NOT NULL
AND opp.deleted_at IS NULL
AND opp.is_closed = 1
AND opp.is_won = 0
AND opp.close_date >= '2024-01-01 00:00:00'
AND opp.close_date <= '2024-12-31 23:59:59'
AND usr.team_id = 1
# and opp.id = 4823179
GROUP BY opp.id, cfd.value
ORDER BY
# cfd.value
CAST(cfd.value AS UNSIGNED)
# owner_name
# (SELECT COUNT(dr.id)
# FROM deal_risks dr
# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
# WHERE dr.opportunity_id = opp.id
# AND dr.is_active = 1
# AND gdrt.is_enabled = 1)
DESC
LIMIT 25
# OFFSET 0
;
select * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';
select * from crm_layout_entities where crm_layout_id = 1493;
SELECT * FROM opportunities WHERE id = 4584045;
SELECT * FROM crm_field_data WHERE object_id = 4584045;
SELECT * FROM opportunities where team_id = 1 and crm_provider_id = '0061K00000lfb9IQAQ'
SELECT
opp.id as opportunity_id,
opp.name,
COUNT(dr.id) as risk_count
FROM opportunities opp
LEFT JOIN deal_risks dr ON dr.opportunity_id = opp.id
WHERE opp.id IN (
6069574,8900938,9507638,9524799,9660490,9662824,9705675,9749758,4158460,4391812,6450439,4448422,5118945,5590675,4584045,7228149,9002408,9165534,9446720,9641778,9665149,9703344,9709280,5747948,4158491,6182565,6263970,5798120,5111315,4536978,6062352,6548383,6072095,6548225,4480986,5011422,6548381,8760540,8917554,9509986,9514392,9569009,9569011,9578490,9713604,5784397,7276612,7288405,8994421,9118219,9608148,9818911,4510535,6479693,5958049,6271674,6550448,4158331,4158483,6126571,6171615,6540943,4897466,5190896,5796182,5932762,8572433,8723698,8892697,9711001,9789264,4549188,6100831,6170064,6260799,6263653,6449936,6530871,6538978,7777651,8059269,8319918,8787049,8901150,9263153,9453207,9514738,9696700,9791062,5752018,6421452,7439134,7878923,9354763,9369285,9514396,9582506,9889949,9890216,4094311,4158495,4158496,6098128,5585661,3872564,6442149,5891604,6164746,6199593,6583474,6519684,9018490,9809006,4496897,5041324,5829430,6198319,6253504,6555763,7242914,7931055,8024125,8797814,8058559,8673347,8892695,8994420,9616219,9714970,9722004,9809439,9818918,6523177,8134147,9002915,9711422,9892713,9901719,9954210,9978435,5800810,6243518,6416114,6222251,6411974,6512456,5791953,6545606,9914780,5805540,6238986,6463838,6547680,9767049,9809437,9810885,9890855,6673493,9902036,4335521,6379871,6503799,6546077,8018765,9907556,9958433,9905855,9916179,9946741,9957877,5563344,6271838,6450815,7641128,7762567,7780592,8684810,8685786,8685787,8685788
)
GROUP BY opp.id;
SELECT COUNT(dr.id)
FROM deal_risks dr
JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
WHERE dr.opportunity_id = 5563344
# AND gdrt.group_id = usr.group_id
EXPLAIN SELECT
opp.id as opp_id, opp.uuid, opp.name,
cfd.value,
cfv.sequence,
cfv.value,
# MAX(cfd.value) AS max_cfd_value,
usr.name AS owner_name,
opp.value, opp.currency_code, opp.close_date, opp.remotely_created_at, opp.is_closed, opp.is_won,
usr.uuid as owner_uuid,
usr.photo_path as owner_photo,
usr.id AS owner_id,
jt.name as owner_job,
opp.stage_id, opp.stage_updated_at,
acc.name AS acc_name, opp.stage_updated_at, acc.crm_provider_id AS acc_provider_id, opp.crm_provider_id AS opp_provider_id,
rt.business_process_id AS pipeline_id
FROM opportunities opp
LEFT JOIN record_types rt ON opp.record_type_id = rt.id
LEFT JOIN users usr ON opp.user_id = usr.id
LEFT JOIN accounts acc ON opp.account_id = acc.id
LEFT JOIN job_titles jt ON usr.job_title_id = jt.id
LEFT JOIN crm_field_data cfd ON cfd.id = (SELECT sub_cfd.id
FROM crm_field_data sub_cfd
WHERE sub_cfd.object_id = opp.id
AND sub_cfd.crm_field_id = 66810
ORDER BY sub_cfd.updated_at DESC
LIMIT 1)
LEFT JOIN crm_field_values cfv ON cfv.crm_field_id = 66810 AND cfv.value = cfd.value
WHERE opp.user_id IS NOT NULL
AND opp.deleted_at IS NULL
AND opp.is_closed = 0
# AND opp.is_closed = 1
# AND opp.is_won = 0
AND opp.close_date >= '2024-01-01 00:00:00'
AND opp.close_date <= '2024-12-31 23:59:59'
AND usr.team_id = 1
# and opp.id = 4823179
# GROUP BY opp.id
ORDER BY
cfv.sequence DESC,
# opp.name
# owner_name
cfd.value
# CAST(cfd.value AS UNSIGNED)
# CAST(MAX(cfd.value) AS UNSIGNED)
# (SELECT COUNT(dr.id)
# FROM deal_risks dr
# JOIN group_deal_risk_types gdrt ON dr.group_deal_risk_type_id = gdrt.id
# WHERE dr.opportunity_id = opp.id
# AND dr.is_active = 1 AND gdrt.is_enabled = 1)
DESC
# LIMIT 75, 25
;
SELECT * FROM crm_field_values WHERE crm_field_id = 66810;
SELECT f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value, fd.updated_at, fd.id, fd.created_at
FROM crm_fields f
INNER JOIN crm_field_data fd ON fd.crm_field_id = f.id
WHERE (f.crm_configuration_id = 1)
AND (f.object_type = 'opportunity')
# AND (fd.object_id IN (4158331,4158483,4158495,4158496,6069574,6263970,6171615,6540943,6545606,5829430,6198319,6263653,6548381,7242914,8797814,7228149,7439134,7878923,8134147,9002915,9118219,9165534,9354763,9369285,9446720))
AND (fd.object_id IN (4158460))
AND (f.crm_provider_id IN ('ForecastCategoryName'))
ORDER BY fd.object_id ASC, fd.updated_at DESC;
select * from crm_layouts where crm_configuration_id = 1;
select cf.* from crm_fields cf
join crm_layout_entities cle on cf.id = cle.crm_field_id
where crm_layout_id = 1493;
SELECT * FROM opportunities WHERE id = 4158460;
SELECT * FROM crm_field_data WHERE object_id = 4158460;
select * from users where team_id = 1;
SELECT * FROM users WHERE id = 7160;
select * from role_user where user_id = 3248;
select * from crm_field_values where crm_field_id = 66824;
SELECT * FROM users WHERE id = 23470;
select * from crm_configurations;
# [PASSWORD_DOTS]
select * from teams where id = 1038; # 23521, 966
select * from users where team_id = 1038;
select * from crm_configurations where id = 966;
SELECT * FROM activities WHERE uuid_to_bin('e8dc7c34-31f6-4430-bd81-2d9d8f00dd07') = uuid; # 54965762
SELECT * FROM activities WHERE uuid_to_bin('4b5727c0-69d1-428f-b8d9-b649055166e2') = uuid; # 54965764
SELECT * FROM participants WHERE activity_id = 54965764;
SELECT id, uuid, title, recording_state FROM activities WHERE uuid_to_bin('355f105d-5606-4379-b0c5-d91daf59f4ce') = uuid; # 54964075
SELECT * FROM participants WHERE activity_id = 54964075;
select f.id, slug, title, tf.team_id from team_features tf JOIN features f on tf.feature_id = f.id
where tf.team_id = 1038;
# [PASSWORD_DOTS] PD [PASSWORD_DOTS]
select * from teams where id = 1029;
SELECT * FROM crm_configurations WHERE id = 957;
SELECT * FROM activities WHERE uuid_to_bin('581a33cb-4343-44bd-ac0c-4a5cee71c5ec') = uuid; # 54963423
SELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433
SELECT * FROM activities WHERE uuid_to_bin('8b6bb2f7-e7a7-4aec-8806-fbfea7093ccd') = uuid; # 54963421
# [PASSWORD_DOTS] Close [PASSWORD_DOTS]
select * from teams where id = 1031;
SELECT * FROM crm_configurations WHERE id = 959;
SELECT * FROM activities WHERE uuid_to_bin('59c517e0-96c9-4dad-bfeb-b7bef72f8725') = uuid; # 54963517
SELECT * FROM activities WHERE uuid_to_bin('07238011-25fa-418e-838b-fb21e82b9ea2') = uuid; # 54963433
SELECT * FROM activities WHERE uuid_to_bin('d68311e3-ac34-49bd-bf04-88323a7e5352') = uuid; # 54966009
# [PASSWORD_DOTS] SF [PASSWORD_DOTS]
SELECT * FROM activities WHERE uuid_to_bin('2bd4cfa3-4eb5-4c77-84a7-9ed46a21c988') = uuid;
select * from roles;
select * from permissions;
select * from permission_role where permission_id = 136;
select * from migrations order by id desc;
select * from teams where id IN (1, 1037);
select * from crm_layouts where crm_configuration_id = 1;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 1493;
SELECT * FROM crm_fields WHERE id IN (1652,1661,66799,66814,66821,66836,66843,66846,66864,84752,182306,323580,380378);
select * from features;
select * from team_features where feature_id = 33;
select * from opportunities;
select * from teams;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1052 and sa.provider = 'hubspot';
SELECT * FROM accounts where id = 11512582;
select * from activities where account_id = 11512582;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
45185
|
NULL
|
NULL
|
NULL
|
|
45190
|
1621
|
32
|
2026-05-14T14:24:16.570406+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-14/1778 /Users/lukas/.screenpipe/data/data/2026-05-14/1778768656570_m1.jpg...
|
PhpStorm
|
faVsco.js – WriteCrmTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorRun PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelplhlFV faVsco.jsv#12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTest100% <D8• Thu 14 May 17:24:16* :QProject v© ReportController.php© AutomatedReportGenerated.phpTrackAutomatedReportGeneratedEvent.phpPlaybackController.phpcustom.log€ ConnectionStateTraUserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.php© CrmHelperReposito© FilterJoinedParticip• DeleteCrmEntityTrait.phpDeleteAccountJob.php© ImportActivityTypes.phpT OpportunitySyncab© CrmHelperRepository.php© AccountController.phpT IntegrationAppTrait.php.env.stagingv HubspotDetachActivityObject.phpRematchActivityOnCrmObjectDetach.php© MatchActivityCrmData.php18D AccountSyncStrate› D ActionsHandleHubspotRateLimit.phpD ContactSyncStrate!› O DTO© HubspotPaginationService.phptrait WriteCrmTraitprivate function buildConferencePayLoad(AAllClassesFilesSymbols1955Q-sfser› O Fields› D JournalMetadatav OpportunitySyncSti86878889return $this->upsertActivity($activit› O Concerns2 usages© HubspotLastMor90private function fetchCustomFieldData(Act© HubspotLastMor106© HubspotLastMor107 E© HubspotLastMor108© HubspotLastMor© HubspotSingleS:109110© HubspotSyncStr/*** @throws HubspotException* @throws BadRequest*/3 usages© HubspotWebhoc111private function upsertActivity(Activityv D Pagination|158© HubspotPaginati159© PaginationConfig160© PaginationState.161> D ProspectSearchStra162› [ Redisv D ServiceTraits163€ OpportunitySync164© SyncCrmEntities165+ SyncFieldsTrait./166T WriteCrmTrait.pl167D Utils168/*** @throws HubspotException* @throws BadRequest*/1 usageprivate function updateNonTaskActivity(Activity Sactivity,array $engagement,array Smetadata,array $oldAssociations): void {...}D Webhook183@ RatchSvnrCnllertnWorkspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)SendReportJob.php=laravel.logWriteCrmTrait.php X© ActivityPlaybookTrait.phpSF [jiminny@localhost].envA HS_local [jiminny@localho:WClient.php4 console [QAI PROD] XActionsTextProject Files vSubmitAudioFileService.php .../app/.../AssemblyAl/Services/SubmitAudioFileService.php© SubmitAudioFileServiceTest.php .../.../AssemblyAl/SubmitAudioFileServiceTest.phpGenerateSpeechFromSilenceService.php .../.../Service/GenerateSpeechFromSilenceService.phpC GenerateSpeechFromSilenceServiceTest.php .../.../Service/GenerateSpeechFromSilenceServiceTest© SubmitAudioFileForLanguageDetectionService.php .../SubmitAudioFileForLanguageDetectionService© SubmitAudioFileForLanguageDetectionServiceTest.php .../tests/Unit/Compon...tectionServiceTest.pl© SyncCrmFieldService.php .../app/Component/AiAutomation/SyncCrmFieldService.php© AiScorecardFilterServiceTest.php .../tests/.../AiCallScoring/Services/AiScorecardFilterServiceTest.ph© AiScorecardFilterService.php .../app/Component/AiCallScoring/Services/AiScorecardFilterService.phO1S 1_elWIre:ie:EN228— 229_230— 231232- 233 V234Open In Right SplitJouLal,on u.id= sa.:1..n<->1: on t.L_id = 1052 andIMaccounts wheiactivitiesW Windsurf Teams108:32UTF-8Co 4 spaces...
|
NULL
|
5432217443116260450
|
NULL
|
visual_change
|
ocr
|
NULL
|
PhpStormFileEditViewNavigateCodeLaravelRefactorRun PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelplhlFV faVsco.jsv#12066 on JY-20725-handle-HS-search-rate-limit k vHandleHubspotRateLimitTest100% <D8• Thu 14 May 17:24:16* :QProject v© ReportController.php© AutomatedReportGenerated.phpTrackAutomatedReportGeneratedEvent.phpPlaybackController.phpcustom.log€ ConnectionStateTraUserAutomatedReportsController.phpPlanhatService.phpAutomatedReportResult.php© CrmHelperReposito© FilterJoinedParticip• DeleteCrmEntityTrait.phpDeleteAccountJob.php© ImportActivityTypes.phpT OpportunitySyncab© CrmHelperRepository.php© AccountController.phpT IntegrationAppTrait.php.env.stagingv HubspotDetachActivityObject.phpRematchActivityOnCrmObjectDetach.php© MatchActivityCrmData.php18D AccountSyncStrate› D ActionsHandleHubspotRateLimit.phpD ContactSyncStrate!› O DTO© HubspotPaginationService.phptrait WriteCrmTraitprivate function buildConferencePayLoad(AAllClassesFilesSymbols1955Q-sfser› O Fields› D JournalMetadatav OpportunitySyncSti86878889return $this->upsertActivity($activit› O Concerns2 usages© HubspotLastMor90private function fetchCustomFieldData(Act© HubspotLastMor106© HubspotLastMor107 E© HubspotLastMor108© HubspotLastMor© HubspotSingleS:109110© HubspotSyncStr/*** @throws HubspotException* @throws BadRequest*/3 usages© HubspotWebhoc111private function upsertActivity(Activityv D Pagination|158© HubspotPaginati159© PaginationConfig160© PaginationState.161> D ProspectSearchStra162› [ Redisv D ServiceTraits163€ OpportunitySync164© SyncCrmEntities165+ SyncFieldsTrait./166T WriteCrmTrait.pl167D Utils168/*** @throws HubspotException* @throws BadRequest*/1 usageprivate function updateNonTaskActivity(Activity Sactivity,array $engagement,array Smetadata,array $oldAssociations): void {...}D Webhook183@ RatchSvnrCnllertnWorkspace associated with branch 'JY-20725-handle-HS-search-rate-limit' has been restored // Rollback // Configure... (today 16:17)SendReportJob.php=laravel.logWriteCrmTrait.php X© ActivityPlaybookTrait.phpSF [jiminny@localhost].envA HS_local [jiminny@localho:WClient.php4 console [QAI PROD] XActionsTextProject Files vSubmitAudioFileService.php .../app/.../AssemblyAl/Services/SubmitAudioFileService.php© SubmitAudioFileServiceTest.php .../.../AssemblyAl/SubmitAudioFileServiceTest.phpGenerateSpeechFromSilenceService.php .../.../Service/GenerateSpeechFromSilenceService.phpC GenerateSpeechFromSilenceServiceTest.php .../.../Service/GenerateSpeechFromSilenceServiceTest© SubmitAudioFileForLanguageDetectionService.php .../SubmitAudioFileForLanguageDetectionService© SubmitAudioFileForLanguageDetectionServiceTest.php .../tests/Unit/Compon...tectionServiceTest.pl© SyncCrmFieldService.php .../app/Component/AiAutomation/SyncCrmFieldService.php© AiScorecardFilterServiceTest.php .../tests/.../AiCallScoring/Services/AiScorecardFilterServiceTest.ph© AiScorecardFilterService.php .../app/Component/AiCallScoring/Services/AiScorecardFilterService.phO1S 1_elWIre:ie:EN228— 229_230— 231232- 233 V234Open In Right SplitJouLal,on u.id= sa.:1..n<->1: on t.L_id = 1052 andIMaccounts wheiactivitiesW Windsurf Teams108:32UTF-8Co 4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|